phlexible 2.1.0 → 3.0.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.
@@ -0,0 +1,140 @@
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
+ #
9
+ # Additional arguments are passed through to the button element, with a few exceptions:
10
+ # - :method - Symbol of HTTP verb. Supported verbs are :post, :get, :delete, :patch, and :put.
11
+ # Default is :post.
12
+ # - :form_attributes - Hash of HTML attributes to be rendered on the form tag.
13
+ # - :form_class - This controls the class of the form within which the submit button will be placed.
14
+ # Default is 'button_to'. @deprecated: use :form_attributes instead if you want to override this.
15
+ # - :params - Hash of parameters to be rendered as hidden fields within the form.
16
+ module Phlexible
17
+ module Rails
18
+ module ButtonToConcerns
19
+ extend ActiveSupport::Concern
20
+
21
+ included do
22
+ include Phlex::Rails::Helpers::URLFor
23
+ register_value_helper :protect_against_forgery?
24
+ register_value_helper :request_forgery_protection_token
25
+ register_value_helper :form_authenticity_token
26
+ end
27
+
28
+ BUTTON_TAG_METHOD_VERBS = %w[patch put delete].freeze
29
+ DEFAULT_OPTIONS = { method: 'post', form_class: 'button_to', params: {} }.freeze
30
+
31
+ def initialize(url, options = nil)
32
+ @url = url
33
+ @options = options
34
+ end
35
+
36
+ def view_template(&block)
37
+ action = url_for(@url)
38
+ @options = DEFAULT_OPTIONS.merge((@options || {}).symbolize_keys)
39
+
40
+ method = (@options.delete(:method).presence || method_for_options(@options)).to_s
41
+ form_method = method == 'get' ? 'get' : 'post'
42
+
43
+ form action: action, method: form_method, **form_attributes do
44
+ method_tag method
45
+ form_method == 'post' && token_input(action, method.empty? ? 'post' : method)
46
+ param_inputs
47
+
48
+ block_given? ? button(**button_attrs, &block) : button(**button_attrs) { @name }
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def form_attributes
55
+ {
56
+ class: @options.delete(:form_class), # @deprecated
57
+ **(@options.delete(:form_attributes) || {})
58
+ }
59
+ end
60
+
61
+ def button_attrs
62
+ {
63
+ type: 'submit',
64
+ **@options
65
+ }
66
+ end
67
+
68
+ def method_for_options(options)
69
+ if options.is_a?(Array)
70
+ method_for_options(options.last)
71
+ elsif options.respond_to?(:persisted?)
72
+ options.persisted? ? :patch : :post
73
+ elsif options.respond_to?(:to_model)
74
+ method_for_options(options.to_model)
75
+ end
76
+ end
77
+
78
+ def token_input(action, method)
79
+ return unless protect_against_forgery?
80
+
81
+ name = request_forgery_protection_token.to_s
82
+ value = form_authenticity_token(form_options: { action: action, method: method })
83
+
84
+ input type: 'hidden', name: name, value: value, autocomplete: 'off'
85
+ end
86
+
87
+ def method_tag(method)
88
+ return unless BUTTON_TAG_METHOD_VERBS.include?(method)
89
+
90
+ input type: 'hidden', name: '_method', value: method.to_s, autocomplete: 'off'
91
+ end
92
+
93
+ def param_inputs
94
+ return unless (params = @options.delete(:params))
95
+
96
+ to_form_params(params).each do |param|
97
+ input type: 'hidden', name: param[:name], value: param[:value], autocomplete: 'off'
98
+ end
99
+ end
100
+
101
+ # Returns an array of hashes each containing :name and :value keys suitable for use as the
102
+ # names and values of form input fields:
103
+ #
104
+ # to_form_params(name: 'David', nationality: 'Danish')
105
+ # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
106
+ #
107
+ # to_form_params(country: { name: 'Denmark' })
108
+ # # => [{name: 'country[name]', value: 'Denmark'}]
109
+ #
110
+ # to_form_params(countries: ['Denmark', 'Sweden']})
111
+ # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
112
+ #
113
+ # An optional namespace can be passed to enclose key names:
114
+ #
115
+ # to_form_params({ name: 'Denmark' }, 'country')
116
+ # # => [{name: 'country[name]', value: 'Denmark'}]
117
+ def to_form_params(attribute, namespace = nil)
118
+ attribute = attribute.to_h if attribute.respond_to?(:permitted?)
119
+
120
+ params = []
121
+ case attribute
122
+ when Hash
123
+ attribute.each do |key, value|
124
+ prefix = namespace ? "#{namespace}[#{key}]" : key
125
+ params.push(*to_form_params(value, prefix))
126
+ end
127
+ when Array
128
+ array_prefix = "#{namespace}[]"
129
+ attribute.each do |value|
130
+ params.push(*to_form_params(value, array_prefix))
131
+ end
132
+ else
133
+ params << { name: namespace.to_s, value: attribute.to_param }
134
+ end
135
+
136
+ params.sort_by { |pair| pair[:name] }
137
+ end
138
+ end
139
+ end
140
+ end
@@ -23,9 +23,13 @@
23
23
  module Phlexible
24
24
  module Rails
25
25
  module ControllerVariables
26
+ include ViewAssigns
27
+
26
28
  def self.included(klass)
27
29
  klass.class_attribute :__controller_variables__, instance_predicate: false, default: Set.new
28
30
  klass.extend ClassMethods
31
+ klass.include Callbacks
32
+ klass.before_template :define_controller_variables
29
33
  end
30
34
 
31
35
  class UndefinedVariable < NameError
@@ -36,31 +40,32 @@ module Phlexible
36
40
  end
37
41
  end
38
42
 
39
- def before_template # rubocop:disable Metrics
40
- if respond_to?(:__controller_variables__)
41
- view_assigns = helpers.controller.view_assigns
42
- view = @view
43
-
44
- vars = (view&.__controller_variables__ || Set.new) + __controller_variables__
45
- vars.each do |k, v|
46
- allow_undefined = true
47
- if k.ends_with?('!')
48
- allow_undefined = false
49
- k = k.chop
50
- end
43
+ private
44
+
45
+ def define_controller_variables
46
+ return unless respond_to?(:__controller_variables__)
51
47
 
52
- raise ControllerVariables::UndefinedVariable, k if !allow_undefined && !view_assigns.key?(k)
48
+ view_assigns = view_assigns()
49
+ view = @view
53
50
 
54
- instance_variable_set(:"@#{v}", view_assigns[k])
55
- view&.instance_variable_set(:"@#{v}", view_assigns[k])
51
+ vars = (view&.__controller_variables__ || Set.new) + __controller_variables__
52
+ vars.each do |k, v|
53
+ allow_undefined = true
54
+ if k.ends_with?('!')
55
+ allow_undefined = false
56
+ k = k.chop
56
57
  end
57
- end
58
58
 
59
- super
59
+ raise ControllerVariables::UndefinedVariable, k if !allow_undefined && !view_assigns.key?(k)
60
+
61
+ instance_variable_set(:"@#{v}", view_assigns[k])
62
+ view&.instance_variable_set(:"@#{v}", view_assigns[k])
63
+ end
60
64
  end
61
65
 
62
66
  module ClassMethods
63
- def controller_variable(*names, **kwargs) # rubocop:disable Metrics/*
67
+ # /*
68
+ def controller_variable(*names, **kwargs)
64
69
  if names.empty? && kwargs.empty?
65
70
  raise ArgumentError, 'You must provide at least one variable name and/or a hash of ' \
66
71
  'variable names and options.'
@@ -3,8 +3,10 @@
3
3
  module Phlexible
4
4
  module Rails
5
5
  class MetaTagsComponent < Phlex::HTML
6
+ include ViewAssigns
7
+
6
8
  def view_template
7
- helpers.controller.view_assigns['meta_tags']&.each do |name, content|
9
+ view_assigns['meta_tags']&.each do |name, content|
8
10
  meta name: name, content: content.is_a?(String) ? content : content.to_json
9
11
  end
10
12
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexible
4
+ module Rails
5
+ module ViewAssigns
6
+ def view_assigns
7
+ if respond_to?(:view_context)
8
+ # Phlex 2
9
+ view_context.controller.view_assigns
10
+ else
11
+ # Phlex 1
12
+ helpers.controller.view_assigns
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlexible
4
- VERSION = '2.1.0'
4
+ VERSION = '3.0.0'
5
5
  end
data/lib/phlexible.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'phlexible/version'
4
- require 'phlex'
4
+ require 'zeitwerk'
5
+ require 'rails'
6
+ require 'phlex-rails'
7
+
8
+ loader = Zeitwerk::Loader.for_gem
9
+ loader.setup
5
10
 
6
11
  module Phlexible
7
- autoload :AliasView, 'phlexible/alias_view'
8
- autoload :PageTitle, 'phlexible/page_title'
9
- autoload :Rails, 'phlexible/rails'
10
12
  end
data/phlexible.gemspec CHANGED
@@ -30,6 +30,8 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ['lib']
32
32
 
33
- spec.add_dependency 'phlex', '>= 1.10.0', '< 2.0.0'
34
- spec.add_dependency 'phlex-rails', '>= 1.2.1', '< 2.0.0'
33
+ spec.add_dependency 'phlex', '>= 1.10.0', '< 3.0.0'
34
+ spec.add_dependency 'phlex-rails', '>= 1.2.0', '< 3.0.0'
35
+ spec.add_dependency 'rails', '>= 7.2.0', '< 9.0.0'
36
+ spec.add_dependency 'zeitwerk', '~> 2.7.2'
35
37
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlexible
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Moss
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 2025-02-05 00:00:00.000000000 Z
11
+ date: 2025-04-22 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: phlex
@@ -18,7 +19,7 @@ dependencies:
18
19
  version: 1.10.0
19
20
  - - "<"
20
21
  - !ruby/object:Gem::Version
21
- version: 2.0.0
22
+ version: 3.0.0
22
23
  type: :runtime
23
24
  prerelease: false
24
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -28,27 +29,61 @@ dependencies:
28
29
  version: 1.10.0
29
30
  - - "<"
30
31
  - !ruby/object:Gem::Version
31
- version: 2.0.0
32
+ version: 3.0.0
32
33
  - !ruby/object:Gem::Dependency
33
34
  name: phlex-rails
34
35
  requirement: !ruby/object:Gem::Requirement
35
36
  requirements:
36
37
  - - ">="
37
38
  - !ruby/object:Gem::Version
38
- version: 1.2.1
39
+ version: 1.2.0
39
40
  - - "<"
40
41
  - !ruby/object:Gem::Version
41
- version: 2.0.0
42
+ version: 3.0.0
42
43
  type: :runtime
43
44
  prerelease: false
44
45
  version_requirements: !ruby/object:Gem::Requirement
45
46
  requirements:
46
47
  - - ">="
47
48
  - !ruby/object:Gem::Version
48
- version: 1.2.1
49
+ version: 1.2.0
49
50
  - - "<"
50
51
  - !ruby/object:Gem::Version
51
- version: 2.0.0
52
+ version: 3.0.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: rails
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 7.2.0
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: 9.0.0
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 7.2.0
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: 9.0.0
73
+ - !ruby/object:Gem::Dependency
74
+ name: zeitwerk
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: 2.7.2
80
+ type: :runtime
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: 2.7.2
52
87
  description: A bunch of helpers and goodies intended to make life with Phlex even
53
88
  easier!
54
89
  email:
@@ -58,14 +93,16 @@ extensions: []
58
93
  extra_rdoc_files: []
59
94
  files:
60
95
  - ".rubocop.yml"
96
+ - ".ruby-version"
97
+ - Appraisals
61
98
  - CODE_OF_CONDUCT.md
62
99
  - Gemfile
63
100
  - Gemfile.lock
64
101
  - LICENSE.txt
65
102
  - README.md
66
103
  - Rakefile
104
+ - config/render_helper.rb
67
105
  - config/sus.rb
68
- - config/view_helper.rb
69
106
  - fixtures/dummy/app/controllers/articles_controller.rb
70
107
  - fixtures/dummy/app/views/articles/index.html.erb
71
108
  - fixtures/dummy/app/views/articles/link.rb
@@ -77,17 +114,24 @@ files:
77
114
  - fixtures/dummy/log/.gitignore
78
115
  - fixtures/dummy/public/favicon.ico
79
116
  - fixtures/rails_helper.rb
117
+ - gemfiles/.bundle/config
118
+ - gemfiles/phlex_1.gemfile
119
+ - gemfiles/phlex_1.gemfile.lock
120
+ - gemfiles/phlex_2.gemfile
121
+ - gemfiles/phlex_2.gemfile.lock
80
122
  - lib/phlexible.rb
81
123
  - lib/phlexible/alias_view.rb
124
+ - lib/phlexible/callbacks.rb
82
125
  - lib/phlexible/page_title.rb
83
- - lib/phlexible/rails.rb
84
126
  - lib/phlexible/rails/a_element.rb
85
127
  - lib/phlexible/rails/action_controller/implicit_render.rb
86
128
  - lib/phlexible/rails/action_controller/meta_tags.rb
87
129
  - lib/phlexible/rails/button_to.rb
130
+ - lib/phlexible/rails/button_to_concerns.rb
88
131
  - lib/phlexible/rails/controller_variables.rb
89
132
  - lib/phlexible/rails/meta_tags_component.rb
90
133
  - lib/phlexible/rails/responder.rb
134
+ - lib/phlexible/rails/view_assigns.rb
91
135
  - lib/phlexible/version.rb
92
136
  - phlexible.gemspec
93
137
  homepage: https://github.com/joelmoss/phlexible
@@ -98,6 +142,7 @@ metadata:
98
142
  source_code_uri: https://github.com/joelmoss/phlexible
99
143
  changelog_uri: https://github.com/joelmoss/phlexible/releases
100
144
  rubygems_mfa_required: 'true'
145
+ post_install_message:
101
146
  rdoc_options: []
102
147
  require_paths:
103
148
  - lib
@@ -112,7 +157,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
157
  - !ruby/object:Gem::Version
113
158
  version: '0'
114
159
  requirements: []
115
- rubygems_version: 3.6.3
160
+ rubygems_version: 3.5.22
161
+ signing_key:
116
162
  specification_version: 4
117
163
  summary: A bunch of helpers and goodies intended to make life with Phlex even easier!
118
164
  test_files: []
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ViewHelper
4
- def self.extended(parent)
5
- parent.class_exec do
6
- let(:output) { example.call }
7
- let(:example) { view.new }
8
- end
9
- end
10
-
11
- def view(&block)
12
- let :view do
13
- Class.new(Phlex::HTML, &block)
14
- end
15
- end
16
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'phlex-rails'
4
-
5
- module Phlexible
6
- module Rails
7
- autoload :ControllerVariables, 'phlexible/rails/controller_variables'
8
- autoload :Responder, 'phlexible/rails/responder'
9
- autoload :AElement, 'phlexible/rails/a_element'
10
-
11
- autoload :MetaTagsComponent, 'phlexible/rails/meta_tags_component'
12
- autoload :ButtonTo, 'phlexible/rails/button_to'
13
- autoload :ButtonToConcerns, 'phlexible/rails/button_to'
14
-
15
- module ActionController
16
- autoload :ImplicitRender, 'phlexible/rails/action_controller/implicit_render'
17
- autoload :MetaTags, 'phlexible/rails/action_controller/meta_tags'
18
- end
19
- end
20
- end