Skip to content

Commit

Permalink
Merge branch 'controller_vars'
Browse files Browse the repository at this point in the history
* controller_vars:
  chore: MetaTags docs
  fix(ControllerVariables): accept mix of names and hash
  fix: Allow redefining controller variables
  ControllerVariables > ControllerAttributes
  • Loading branch information
joelmoss committed Feb 2, 2024
2 parents 2061a8f + 1d530fc commit ed5cac1
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 109 deletions.
63 changes: 54 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ class UsersController
end
```

#### `ControllerAttributes`
#### `ControllerVariables`

Include this module in your Phlex views to get access to the controller's instance variables. It provides an explicit interface for accessing controller instance variables from the view.
Include this module in your Phlex views to get access to the controller's instance variables. It provides an explicit interface for accessing controller instance variables from within the view.

```ruby
class Views::Users::Index < Views::Base
include Phlexible::Rails::ControllerAttributes
include Phlexible::Rails::ControllerVariables

controller_attribute :first_name, :last_name
controller_variable :first_name, :last_name

def template
h1 { "#{@first_name} #{@last_name}" }
Expand All @@ -54,13 +54,29 @@ end

##### Options

- `attr_reader:` - If set to `true`, an `attr_reader` will be defined for the given attributes.
- `alias:` - If set, the given attribute will be aliased to the given alias value.
`controller_variable` accepts one or many symbols, or a hash of symbols to options.

- `as:` - If set, the given attribute will be renamed to the given value. Helpful to avoid naming conflicts.
- `allow_undefined:` - By default, if the instance variable is not defined in the controller, an
exception will be raised. If this option is to `true`, an error will not be raised.

You can also pass a hash of attributes to `controller_variable`, where the key is the controller
attribute, and the value is the renamed value, or options hash.

```ruby
controller_attribute :users, attr_reader: true, alias: :my_users
class Views::Users::Index < Views::Base
include Phlexible::Rails::ControllerVariables

controller_variable last_name: :surname, first_name: { as: :given_name, allow_undefined: true }

def template
h1 { "#{@given_name} #{@surname}" }
end
end
```

Please note that defining a variable with the same name as an existing variable in the view will be overwritten.

#### `Responder`

If you use [Responders](https://github.com/heartcombo/responders), Phlexible provides a responder to support implicit rendering similar to `ActionController::ImplicitRender` above. It will render the Phlex view using `respond_with` if one exists, and fall back to default rendering.
Expand Down Expand Up @@ -90,7 +106,7 @@ end

This responder requires the use of `ActionController::ImplicitRender`, so don't forget to include that in your `ApplicationController`.

If you use `ControllerAttributes` in your view, and define a `resource` attribute, the responder will pass that to your view.
If you use `ControllerVariables` in your view, and define a `resource` attribute, the responder will pass that to your view.

#### `AElement`

Expand Down Expand Up @@ -138,7 +154,36 @@ Phlexible::Rails::ButtonTo.new(:root, method: :patch) { 'My Button' }
- `:form_attributes` - Hash of HTML attributes for the form tag.
- `:data` - This option can be used to add custom data attributes.
- `:params` - Hash of parameters to be rendered as hidden fields within the form.
- `:method` - Symbol of the HTTP verb. Supported verbs are :post (default), :get, :delete, :patch, and :put.
- `:method` - Symbol of the HTTP verb. Supported verbs are :post (default), :get, :delete, :patch,
and :put.

#### `MetaTags`

A super simple way to define and render meta tags in your Phlex views. Just render the
`Phlexible::Rails::MetaTagsComponent` component in the head element of your page, and define the
meta tags using the `meta_tag` method in your controllers.

```ruby
class MyController < ApplicationController
meta_tag :description, 'My description'
meta_tag :keywords, 'My keywords'
end
```

```ruby
class MyView < Phlex::HTML
def template
html do
head do
render Phlexible::Rails::MetaTagsComponent
end
body do
# ...
end
end
end
end
```

### `AliasView`

Expand Down
7 changes: 7 additions & 0 deletions fixtures/dummy/app/views/articles/show.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Views::Articles::Show < Phlex::HTML
include Phlexible::Rails::ControllerVariables

def template; end
end
2 changes: 1 addition & 1 deletion lib/phlexible/rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

module Phlexible
module Rails
autoload :ControllerAttributes, 'phlexible/rails/controller_attributes'
autoload :ControllerVariables, 'phlexible/rails/controller_variables'
autoload :Responder, 'phlexible/rails/responder'
autoload :AElement, 'phlexible/rails/a_element'

Expand Down
16 changes: 2 additions & 14 deletions lib/phlexible/rails/action_controller/implicit_render.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,12 @@ def default_render
render_plex_view({ action: action_name }) || super
end

def assign_phlex_accessors(pview)
pview.tap do |view|
if view.respond_to?(:__controller_attributes__)
view.__controller_attributes__.each do |attr|
raise ControllerAttributes::UndefinedVariable, attr unless view_assigns.key?(attr.to_s)

view.instance_variable_set :"@#{attr}", view_assigns[attr.to_s]
end
end
end
end

def method_for_action(action_name)
super || ('default_phlex_render' if phlex_view(action_name))
end

def default_phlex_render
render assign_phlex_accessors(phlex_view(action_name).new)
render phlex_view(action_name).new
end

# @param options [Hash] At a minimum this may contain an `:action` key, which will be used
Expand All @@ -54,7 +42,7 @@ def render_plex_view(options)

return unless (view = phlex_view(options[:action]))

render assign_phlex_accessors(view.new), options
render view.new, options
end

private
Expand Down
85 changes: 0 additions & 85 deletions lib/phlexible/rails/controller_attributes.rb

This file was deleted.

101 changes: 101 additions & 0 deletions lib/phlexible/rails/controller_variables.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# frozen_string_literal: true

# Include this module in your Phlex views to get access to the controller's instance variables. It
# provides an explicit interface for accessing controller instance variables from the view. Simply
# call `controller_variable` with the name of any controller instance variable you want to access
# in your view.
#
# @example
# class Views::Users::Index < Views::Base
# controller_variable :user_name
#
# def template
# h1 { @user_name }
# end
# end
#
# Options
# - `as:` - If set, the given attribute will be renamed to the given value. Helpful to avoid
# naming conflicts.
# - `allow_undefined:` - If set to `true`, the view will not raise an error if the controller
# instance variable is not defined.
#
module Phlexible
module Rails
module ControllerVariables
def self.included(klass)
klass.class_attribute :__controller_variables__, instance_predicate: false, default: Set.new
klass.extend ClassMethods
end

class UndefinedVariable < NameError
def initialize(name)
@variable_name = name
super "Attempted to expose controller variable `#{@variable_name}`, but instance " \
'variable is not defined in the controller.'
end
end

def before_template
if respond_to?(:__controller_variables__)
view_assigns = helpers.controller.view_assigns

__controller_variables__.each do |k, v|
allow_undefined = true
if k.ends_with?('!')
allow_undefined = false
k = k.chop
end

raise ControllerVariables::UndefinedVariable, k if !allow_undefined && !view_assigns.key?(k)

instance_variable_set(:"@#{v}", view_assigns[k])
end
end

super
end

module ClassMethods
def controller_variable(*names, **kwargs) # rubocop:disable Metrics/*
if names.empty? && kwargs.empty?
raise ArgumentError, 'You must provide at least one variable name and/or a hash of ' \
'variable names and options.'
end

allow_undefined = kwargs.delete(:allow_undefined)
as = kwargs.delete(:as)

if names.count > 1 && as
raise ArgumentError, 'You cannot provide the `as:` option when passing multiple ' \
'variable names.'
end

names.each do |name|
name_as = as || name
name = "#{name}!" unless allow_undefined

self.__controller_variables__ += { name.to_s => name_as.to_s }
end

kwargs.each do |k, v|
if v.is_a?(Hash)
name = v.key?(:as) ? v[:as].to_s : k.to_s

if v.key?(:allow_undefined)
k = "#{k}!" unless v[:allow_undefined]
elsif !allow_undefined
k = "#{k}!"
end
else
name = v.to_s
k = "#{k}!" unless allow_undefined
end

self.__controller_variables__ += { k.to_s => name }
end
end
end
end
end
end
Loading

0 comments on commit ed5cac1

Please sign in to comment.