Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Phlex::Rails::Actions #104

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions lib/phlex/rails/actions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

module Phlex::Rails
# Include in controllers to map action names to class names. This makes it possible to
# embed Phlex components directly into Rails controllers without having to go through
# other templating systems like Erb.
#
# Instance methods will be assigned to views that have `attr_accessor` methods.
#
# Consider a blog post controller:
#
# ```ruby
# class PostsController < ApplicationController
# include Phlex::Rails::Actions
#
# before_action :load_post
#
# class Show < ApplicationComponent
# attr_accessor :post
#
# def template(&)
# h1 { @post.title }
# div(class: "prose") { @post.body }
# end
# end
#
# private
# def load_post
# @post = Post.find(params[:id])
# end
# end
# ```
#
# The `@post` variable gets set in the `Show` view class via `Show#post=`.
module Actions
extend ActiveSupport::Concern

class_methods do
# Finds a class on the controller with the same name as the action. For example,
# `def index` would find the `Index` constant on the controller class to render
# for the action `index`.
def phlex_action_class(action:)
action_class = action.to_s.camelcase
const_get action_class if const_defined? action_class
end
end

protected

# Assigns the instance variables that are set in the controller to setter method
# on Phlex. For example, if a controller defines @users and a Phlex class has
# `attr_writer :users`, `attr_accessor :user`, or `def users=`, it will be automatically
# set by this method.
def assign_phlex_accessors(phlex_view)
phlex_view.tap do |view|
view_assigns.each do |variable, value|
attr_writer_name = "#{variable}="
view.send attr_writer_name, value if view.respond_to? attr_writer_name
end
end
end

# Initializers a Phlex view based on the action name, then assigns `view_assigns`
# to the view.
def phlex_action(action)
assign_phlex_accessors self.class.phlex_action_class(action: action).new
end

# Phlex action for the current action.
def phlex
phlex_action(action_name)
end

# Try rendering with the regular Rails rendering methods; if those don't work
# then try finding the Phlex class that corresponds with the action_name. If that's
# found then tell Rails to call `default_phlex_render`.
def method_for_action(action_name)
super || if self.class.phlex_action_class action: action_name
"default_phlex_render"
end
end

# Renders a Phlex view for the given action, if it's present.
def default_phlex_render
render phlex
end
end
end
70 changes: 70 additions & 0 deletions spec/helpers/actions_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require "spec_helper"

RSpec.describe Phlex::Rails::Actions, type: :controller do
class PostsController < ActionController::Base
include Phlex::Rails::Actions

before_action :load_post

class Show < Phlex::HTML
attr_accessor :post

def template
div { @post }
end
end

def edit
render plain: "Hello, Rails"
end

class Bye < Phlex::HTML
def template
plain "Bye, Phlex"
end
end

def bye
render plain: "Bye, Controller Method"
end

private

def load_post
@post = "Hello, Phlex"
end
end

before(:each) do
@controller = PostsController.new
@routes = ActionDispatch::Routing::RouteSet.new
@routes.draw do
get "show", to: "posts#show"
get "edit", to: "posts#edit"
get "bye", to: "posts#bye"
end
end

describe "render Phlex view class" do
it "renders the Phlex view for the given action" do
get :show
expect(response.body).to include("Hello, Phlex")
end
end

describe "render controller method" do
it "renders the controller method for the given action" do
get :edit
expect(response.body).to include("Hello, Rails")
end
end

describe "render controller method over Phlex class" do
it "renders the controller method for the given action" do
get :bye
expect(response.body).to include("Bye, Controller Method")
end
end
end
Loading