phlexible 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6c45035fcfe50f425a2876e79f5973399b74b76f5923357dd02c355575d0eac
4
- data.tar.gz: 49e7ddc371aaad6e53316cdd4aa5bb0bfb76cda8f6fa1052779e40306d1e339a
3
+ metadata.gz: de2a41c9efa577146733f7e842f503818c594d726995a8f62fd39f5e87749b61
4
+ data.tar.gz: 89306b9e9ca8b83ed58a4b598197cabe3c745c554645c0555b684c8dc653ac39
5
5
  SHA512:
6
- metadata.gz: 9149698fef1b20d16ca297db60e847172a7454dcab4a53e2798a9cbe38f04ab5b87124453861c312f8002365a895b4e97b76494541568cd3c8c1714c5d99cf4b
7
- data.tar.gz: 7e4782383a6f3f542b460739c2e3ed7d27dbf677ab2dab2b73794bb12f0298e19833e63e329ab744e510a750f0db3465546ae5a6704503755c6fa54df034ec48
6
+ metadata.gz: 87c4b0ece2ba0ce51a1b8d865a48fe09dfcd41cec04ee9f862fb05c8e82140946ebabf71056fb863fd38364bf1a80e836d2024a2dd6d80893a50c4f637231e7a
7
+ data.tar.gz: 2fdfd0d7c354df97d8d932bc4eb695876bb87a6434a54b16ce0de729050225acbdbb564e04feb7b6315dc2bde0ee5987456ba67ed684bbfc660c7fae04723356
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.7
2
+ TargetRubyVersion: 3.0
3
3
  SuggestExtensions: false
4
4
  NewCops: enable
5
5
 
@@ -14,3 +14,5 @@ Lint/ConstantDefinitionInBlock:
14
14
  Style/ClassAndModuleChildren:
15
15
  Exclude:
16
16
  - fixtures/**/*
17
+ Metrics/MethodLength:
18
+ Max: 15
data/Gemfile CHANGED
@@ -6,6 +6,7 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'combustion'
9
+ gem 'phlex-testing-nokogiri'
9
10
  gem 'rake', '~> 13.0'
10
11
  gem 'rubocop', '~> 1.21'
11
12
  gem 'sus', '~> 0.16.0'
data/Gemfile.lock CHANGED
@@ -123,6 +123,9 @@ GEM
123
123
  phlex (>= 1, < 2)
124
124
  rails (>= 6.1, < 8)
125
125
  zeitwerk (~> 2)
126
+ phlex-testing-nokogiri (0.1.0)
127
+ nokogiri (~> 1.13)
128
+ phlex (>= 0.5)
126
129
  racc (1.6.2)
127
130
  rack (2.2.5)
128
131
  rack-test (2.0.2)
@@ -188,6 +191,7 @@ PLATFORMS
188
191
 
189
192
  DEPENDENCIES
190
193
  combustion
194
+ phlex-testing-nokogiri
191
195
  phlexible!
192
196
  rake (~> 13.0)
193
197
  rubocop (~> 1.21)
data/README.md CHANGED
@@ -16,14 +16,61 @@ If bundler is not being used to manage dependencies, install the gem by executin
16
16
 
17
17
  ### Rails
18
18
 
19
- #### `AnchorElement`
19
+ #### `ActionController::ImplicitRender`
20
+
21
+ Adds support for default and `action_missing` rendering of Phlex views. So instead of this:
22
+
23
+ ```ruby
24
+ class UsersController
25
+ def index
26
+ render Views::Users::Index.new
27
+ end
28
+ end
29
+ ```
30
+
31
+ You can do this:
32
+
33
+ ```ruby
34
+ class UsersController
35
+ include Phlexible::Rails::ActionController::ImplicitRender
36
+ end
37
+ ```
38
+
39
+ #### `Responder`
40
+
41
+ If you use [Responders](https://github.com/heartcombo/responders), Phlexible provides a responder to
42
+ support implicit rendering similar to `ActionController::ImplicitRender` above. It will render the
43
+ Phlex view using `respond_with` if one exists, and fall back to default rendering.
44
+
45
+ Just include it in your ApplicationResponder:
46
+
47
+ ```ruby
48
+ class ApplicationResponder < ActionController::Responder
49
+ include Phlexible::Rails::Responder
50
+ end
51
+ ```
52
+
53
+ Then simply `respond_with` in your action method as normal:
54
+
55
+ ```ruby
56
+ class UsersController < ApplicationController
57
+ def new
58
+ respond_with User.new
59
+ end
60
+ end
61
+ ```
62
+
63
+ This responder requires the use of `ActionController::ImplicitRender`, so dont't forget to include
64
+ that in your `ApplicationController`.
65
+
66
+ #### `AElement`
20
67
 
21
68
  No need to call Rails `link_to` helper, when you can simply render an anchor tag directly with
22
69
  Phlex. But unfortunately that means you lose some of the magic that `link_to` provides. Especially
23
70
  the automatic resolution of URL's and Rails routes.
24
71
 
25
- The `Phlexible::Rails::AnchorElement` module redefines the `a` tag, and passes through the `href`
26
- attribute to Rails `url_for` helper. So you can do this:
72
+ The `Phlexible::Rails::AElement` module passes through the `href` attribute to Rails `url_for`
73
+ helper. So you can do this:
27
74
 
28
75
  ```ruby
29
76
  Rails.application.routes.draw do
@@ -33,14 +80,48 @@ end
33
80
 
34
81
  ```ruby
35
82
  class MyView < Phlex::HTML
36
- include Phlexible::Rails::AnchorElement
83
+ include Phlexible::Rails::AElement
37
84
 
38
- def template
39
- a(href: :articles) { 'View articles' }
40
- end
85
+ def template
86
+ a(href: :articles) { 'View articles' }
87
+ end
41
88
  end
42
89
  ```
43
90
 
91
+ #### 'ButtonTo`
92
+
93
+ Generates a form containing a single button that submits to the URL created by the set of options.
94
+
95
+ It is similar to Rails `button_to` helper, which accepts the value/content of the button as the
96
+ first argument, and a URL or route helper as the second argument.
97
+
98
+ ```ruby
99
+ Phlexible::Rails::ButtonTo.new 'My Button', :root
100
+ ```
101
+
102
+ Alternatively you can pass a block; the result of which will be used as the value of the button.
103
+
104
+ ```ruby
105
+ Phlexible::Rails::ButtonTo.new(:root) { 'Go Home 👉' }
106
+ ```
107
+
108
+ The url argument accepts the same options as Rails `url_for`.
109
+
110
+ The form submits a POST request by default. You can specify a different HTTP verb via the :method
111
+ option.
112
+
113
+ ```ruby
114
+ Phlexible::Rails::ButtonTo.new 'My Button', :root, method: :patch
115
+ ```
116
+
117
+ ##### Options
118
+
119
+ - `:class` - Specify the HTML class name of the button (not the form).
120
+ - `:form_class` - Specify the HTML class name of the form (default: 'button_to').
121
+ - `:data` - This option can be used to add custom data attributes.
122
+ - `:method` - Symbol of the HTTP verb. Supported verbs are :post (default), :get, :delete, :patch,
123
+ and :put.
124
+
44
125
  ### `AliasView`
45
126
 
46
127
  Create an alias at a given `element`, to the given view class.
@@ -49,11 +130,11 @@ So instead of:
49
130
 
50
131
  ```ruby
51
132
  class MyView < Phlex::HTML
52
- def template
53
- div do
54
- render My::Awesome::Component.new
55
- end
133
+ def template
134
+ div do
135
+ render My::Awesome::Component.new
56
136
  end
137
+ end
57
138
  end
58
139
  ```
59
140
 
@@ -61,18 +142,32 @@ You can instead do:
61
142
 
62
143
  ```ruby
63
144
  class MyView < Phlex::HTML
64
- extend Phlexible::AliasView
145
+ extend Phlexible::AliasView
65
146
 
66
- alias_view :awesome, -> { My::Awesome::Component }
147
+ alias_view :awesome, -> { My::Awesome::Component }
67
148
 
68
- def template
69
- div do
70
- awesome
71
- end
149
+ def template
150
+ div do
151
+ awesome
72
152
  end
153
+ end
154
+ end
155
+ ```
156
+
157
+ ### PageTitle
158
+
159
+ Helper to assist in defining page titles within Phlex views. Also includes support for nested views,
160
+ where each desendent view class will have its title prepended to the page title. Simply assign the
161
+ title to the `page_title` class variable:
162
+
163
+ ```ruby
164
+ class MyView
165
+ self.page_title = 'My Title'
73
166
  end
74
167
  ```
75
168
 
169
+ Then call the `page_title` method in the `<head>` of your page.
170
+
76
171
  ## Development
77
172
 
78
173
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Views::Articles::Link < Phlex::HTML
4
- include Phlexible::Rails::AnchorElement
4
+ include Phlexible::Rails::AElement
5
5
 
6
6
  def template
7
- a(href: :root) { 'A link to root' }
7
+ a(href: :root, class: :foo) { 'A link to root' }
8
8
  end
9
9
  end
@@ -30,8 +30,9 @@ module Phlexible
30
30
  #
31
31
  module AliasView
32
32
  def alias_view(element, view_class)
33
- define_method element do |*args, **kwargs, &block|
34
- render view_class.call.new(*args, **kwargs, &block)
33
+ define_method element do |*args, **kwargs, &blk|
34
+ render view_class.call.new(*args, **kwargs), &blk
35
+ # view_class.call.new(*args, **kwargs).call(@_target, view_context: @_view_context, parent: self, &blk)
35
36
  end
36
37
  end
37
38
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexible
4
+ #
5
+ # Helper to assist in defining page titles within Phlex views. Also includes support for nested
6
+ # views, where each desendent view class will have its title prepended to the page title. Simply
7
+ # assign the title to the `page_title` class variable:
8
+ #
9
+ # class MyView
10
+ # self.page_title = 'My Title'
11
+ # end
12
+ #
13
+ # Then call the `page_title` method in the <head> of your page.
14
+ #
15
+ module PageTitle
16
+ def self.included(base)
17
+ base.class_eval do
18
+ self.class.attr_accessor :page_title
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def page_title
25
+ title = []
26
+
27
+ klass = self.class
28
+ while klass.respond_to?(:page_title)
29
+ title << if klass.page_title.is_a?(Proc)
30
+ instance_exec(&klass.page_title)
31
+ else
32
+ klass.page_title
33
+ end
34
+
35
+ klass = klass.superclass
36
+ end
37
+
38
+ title.compact.join(' - ')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexible
4
+ module Rails
5
+ # Calls `url_for` for the `href` attribute.
6
+ module AElement
7
+ def a(href:, **kwargs, &block)
8
+ super(href: helpers.url_for(href), **kwargs, &block)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adds support for default and action_missing rendering of Phlex views. So instead of this:
4
+ #
5
+ # class UsersController
6
+ # def index
7
+ # render Views::Users::Index.new
8
+ # end
9
+ # end
10
+ #
11
+ # You can do this:
12
+ #
13
+ # class UsersController
14
+ # end
15
+ #
16
+ module Phlexible
17
+ module Rails
18
+ module ActionController
19
+ module ImplicitRender
20
+ NUFFIN = 'NUFFIN'
21
+
22
+ def default_render
23
+ render_view_class || super
24
+ end
25
+
26
+ def render_view_class(view_options = NUFFIN, render_options = {})
27
+ klass = render_options&.key?(:action) ? phlex_view(render_options[:action]) : phlex_view
28
+ return unless klass
29
+
30
+ render view_options == NUFFIN ? klass.new : klass.new(view_options), render_options
31
+ end
32
+
33
+ private
34
+
35
+ def method_for_action(action_name)
36
+ if action_method?(action_name)
37
+ action_name
38
+ elsif phlex_view
39
+ '_handle_view_class'
40
+ elsif respond_to?(:action_missing, true)
41
+ '_handle_action_missing'
42
+ end
43
+ end
44
+
45
+ def _handle_view_class(*_args)
46
+ render_view_class
47
+ end
48
+
49
+ def phlex_view(action_name = @_action_name)
50
+ "views/#{controller_path}/#{action_name}".classify.safe_constantize
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generates a form containing a single button that submits to the URL created by the set of options.
4
+ # Similar to Rails `button_to` helper.
5
+ #
6
+ # The form submits a POST request by default. You can specify a different HTTP verb via the :method
7
+ # option.
8
+ module Phlexible
9
+ module Rails
10
+ module ButtonToConcerns
11
+ BUTTON_TAG_METHOD_VERBS = %w[patch put delete].freeze
12
+ DEFAULT_OPTIONS = { method: 'post', form_class: 'button_to' }.freeze
13
+
14
+ def initialize(name = nil, url = nil, options = nil)
15
+ @name = name
16
+ @url = url
17
+ @options = options
18
+ end
19
+
20
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
21
+ def template(&block)
22
+ if block_given?
23
+ @options = @url
24
+ @url = @name
25
+ @name = nil
26
+ end
27
+
28
+ action = helpers.url_for(@url)
29
+ @options = DEFAULT_OPTIONS.merge((@options || {}).symbolize_keys)
30
+
31
+ method = (@options.delete(:method).presence || method_for_options(@options)).to_s
32
+ form_method = method == 'get' ? 'get' : 'post'
33
+
34
+ form action: action, class: @options.delete(:form_class), method: form_method do
35
+ method_tag method
36
+ form_method == 'post' && token_input(action, method.empty? ? 'post' : method)
37
+
38
+ block_given? ? button(**button_attrs, &block) : button(**button_attrs) { @name }
39
+ end
40
+ end
41
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
42
+
43
+ private
44
+
45
+ def button_attrs
46
+ {
47
+ type: 'submit',
48
+ **@options
49
+ }
50
+ end
51
+
52
+ def method_for_options(options)
53
+ if options.is_a?(Array)
54
+ method_for_options(options.last)
55
+ elsif options.respond_to?(:persisted?)
56
+ options.persisted? ? :patch : :post
57
+ elsif options.respond_to?(:to_model)
58
+ method_for_options(options.to_model)
59
+ end
60
+ end
61
+
62
+ def token_input(action, method)
63
+ return unless helpers.protect_against_forgery?
64
+
65
+ name = helpers.request_forgery_protection_token.to_s
66
+ value = helpers.form_authenticity_token(form_options: { action: action, method: method })
67
+
68
+ input type: 'hidden', name: name, value: value, autocomplete: 'off'
69
+ end
70
+
71
+ def method_tag(method)
72
+ return unless BUTTON_TAG_METHOD_VERBS.include?(method)
73
+
74
+ input type: 'hidden', name: '_method', value: method.to_s, autocomplete: 'off'
75
+ end
76
+ end
77
+
78
+ class ButtonTo < Phlex::HTML
79
+ include ButtonToConcerns
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexible
4
+ module Rails
5
+ module Responder
6
+ # Overridden to support implicit rendering of phlex views.
7
+ def default_render
8
+ if @default_response
9
+ @default_response.call(options)
10
+ elsif !get? && has_errors?
11
+ render_phlex_view options.merge(status: :unprocessable_entity)
12
+ else
13
+ render_phlex_view options
14
+ end
15
+ end
16
+
17
+ # Render the Phlex view with the current resource. Falls back to default controller rendering if
18
+ # no Phlex view exists.
19
+ #
20
+ # @see Phlexible::Rails::ActionController::ImplicitRender#render_view_class
21
+ def render_phlex_view(options)
22
+ controller.render_view_class(@resource, options) || controller.render(options)
23
+ end
24
+ alias render render_phlex_view
25
+ end
26
+ end
27
+ end
@@ -4,6 +4,14 @@ require 'phlex-rails'
4
4
 
5
5
  module Phlexible
6
6
  module Rails
7
- autoload :AnchorElement, 'phlexible/rails/anchor_element'
7
+ autoload :Responder, 'phlexible/rails/responder'
8
+ autoload :AElement, 'phlexible/rails/a_element'
9
+
10
+ autoload :ButtonTo, 'phlexible/rails/button_to'
11
+ autoload :ButtonToConcerns, 'phlexible/rails/button_to'
12
+
13
+ module ActionController
14
+ autoload :ImplicitRender, 'phlexible/rails/action_controller/implicit_render'
15
+ end
8
16
  end
9
17
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlexible
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/phlexible.rb CHANGED
@@ -5,5 +5,6 @@ require 'phlex'
5
5
 
6
6
  module Phlexible
7
7
  autoload :AliasView, 'phlexible/alias_view'
8
+ autoload :PageTitle, 'phlexible/page_title'
8
9
  autoload :Rails, 'phlexible/rails'
9
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlexible
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Moss
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-10 00:00:00.000000000 Z
11
+ date: 2023-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex
@@ -38,8 +38,8 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.4'
41
- description: A bunch of helpers and goodies intended to make life with [Phlex](https://phlex.fun)
42
- even easier!
41
+ description: A bunch of helpers and goodies intended to make life with Phlex even
42
+ easier!
43
43
  email:
44
44
  - joel@developwithstyle.com
45
45
  executables: []
@@ -67,8 +67,12 @@ files:
67
67
  - fixtures/rails_helper.rb
68
68
  - lib/phlexible.rb
69
69
  - lib/phlexible/alias_view.rb
70
+ - lib/phlexible/page_title.rb
70
71
  - lib/phlexible/rails.rb
71
- - lib/phlexible/rails/anchor_element.rb
72
+ - lib/phlexible/rails/a_element.rb
73
+ - lib/phlexible/rails/action_controller/implicit_render.rb
74
+ - lib/phlexible/rails/button_to.rb
75
+ - lib/phlexible/rails/responder.rb
72
76
  - lib/phlexible/version.rb
73
77
  homepage: https://github.com/joelmoss/phlexible
74
78
  licenses:
@@ -86,7 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
90
  requirements:
87
91
  - - ">="
88
92
  - !ruby/object:Gem::Version
89
- version: 2.7.0
93
+ version: 3.0.0
90
94
  required_rubygems_version: !ruby/object:Gem::Requirement
91
95
  requirements:
92
96
  - - ">="
@@ -96,6 +100,5 @@ requirements: []
96
100
  rubygems_version: 3.4.1
97
101
  signing_key:
98
102
  specification_version: 4
99
- summary: A bunch of helpers and goodies intended to make life with [Phlex](https://phlex.fun)
100
- even easier!
103
+ summary: A bunch of helpers and goodies intended to make life with Phlex even easier!
101
104
  test_files: []
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexible
4
- module Rails
5
- module AnchorElement
6
- # @override Calls `url_for` for the :href attribute.
7
- def a(**attributes, &block)
8
- attributes[:href] = helpers.url_for(attributes[:href]) if attributes.key?(:href)
9
-
10
- super(**attributes, &block)
11
- end
12
- end
13
- end
14
- end