view_partial_form_builder 0.1.4 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99ab630e8601d66da9873d48a3c1abbb3611df65e8aa57cb165b3397f2d2b085
4
- data.tar.gz: d1a221e7ce27c1e3c48b6dad21c9cf5c72c67db3da17cf8d83a072d7691f5211
3
+ metadata.gz: c09b26fa824aff0b6fc36212577a96ca2daecefdf5dda0614ab000ea5882d4a3
4
+ data.tar.gz: dfaccd8fd95ec8049e4518079b67af9962fa1b2ffb28125233f400f109bf054f
5
5
  SHA512:
6
- metadata.gz: 44950718466b81b4928ffe33060cc7a90cce087702e8d3830d189bd2e5f1ec1b8fd0bdca68845f94ffdda02ec15febe8fa847bc78dc613e00682f28eab7a072c
7
- data.tar.gz: 1bae64c4d9601d0a265b442bc9de563c691a2d76753414901aac0db176757bc132ff22ed1e92e890d3415b2cc277d705b4cffe1830fe97c7a846176a59803f1f
6
+ metadata.gz: 6f3381c4245c6bd84e61c79d3d28b9ff195a4fa632f657be10153b7c58af1ce29ff2253556858450a9464b6b2b39a82aab83e53cc1327a046d77b2f21348be48
7
+ data.tar.gz: e0e2f622b47ed7bd4db4873f5864746c0663d64fed8583475a75ab4744b60567edcad91156cf721921aa4ee36d5a370a4116efd5b7c0b06839c597ae99825d40
data/README.md CHANGED
@@ -37,7 +37,7 @@ Next, declare view partials that correspond to the [`FormBuilder`][FormBuilder]
37
37
  helper method you'd like to have more control over:
38
38
 
39
39
  ```html+erb
40
- <%# app/views/form_builder/_text_field.html.erb %>
40
+ <%# app/views/application/form_builder/_text_field.html.erb %>
41
41
 
42
42
  <input
43
43
  type="text"
@@ -48,7 +48,7 @@ helper method you'd like to have more control over:
48
48
  <% end %>
49
49
  >
50
50
 
51
- <%# app/views/form_builder/_email_field.html.erb %>
51
+ <%# app/views/application/form_builder/_email_field.html.erb %>
52
52
 
53
53
  <input
54
54
  type="email"
@@ -59,7 +59,7 @@ helper method you'd like to have more control over:
59
59
  <% end %>
60
60
  >
61
61
 
62
- <%# app/views/form_builder/_button.html.erb %>
62
+ <%# app/views/application/form_builder/_button.html.erb %>
63
63
 
64
64
  <button
65
65
  class="button button--primary"
@@ -76,7 +76,7 @@ You'll have local access to the `FormBuilder` instance as the template-local
76
76
  generating HTML through Rails' helpers:
77
77
 
78
78
  ```html+erb
79
- <%# app/views/form_builder/_email_field.html.erb %>
79
+ <%# app/views/application/form_builder/_email_field.html.erb %>
80
80
 
81
81
  <div class="email-field-wrapper">
82
82
  <%= form.email_field(method, required: true, **options)) %>
@@ -84,10 +84,10 @@ generating HTML through Rails' helpers:
84
84
  ```
85
85
 
86
86
  ```html+erb
87
- <%# app/views/form_builder/_button.html.erb %>
87
+ <%# app/views/application/form_builder/_button.html.erb %>
88
88
 
89
89
  <div class="button-wrapper">
90
- <%= form.button(*arguments, **options, &block) %>
90
+ <%= form.button(value, options, &block) %>
91
91
  </div>
92
92
  ```
93
93
 
@@ -116,30 +116,8 @@ In addition, each view partial receives:
116
116
  * `form` - a reference to the instance of `ViewPartialFormBuilder`, which is a
117
117
  descendant of [`ActionView::Helpers::FormBuilder`][FormBuilder]
118
118
 
119
- * `arguments` - an Array containing the arguments the helper received, in the
120
- order they were received. This can be useful to pass to the view partial's
121
- helper by [splatting them][splat] out.
122
-
123
- * `&block` - a callable, `yield`-able block if the helper method was passed one
124
-
125
- In cases when a [`ActionView::Helpers::FormBuilder` helper
126
- method][FormBuilder]'s last arguments are options (either `Hash` instances or
127
- [keyword arguments][]), they're omitted from the `arguments` array.
128
-
129
- If you want to pass-through all arguments, options, and block parameters, you
130
- can splat them out:
131
-
132
- ```html+erb
133
- <%# app/views/form_builder/_label.html.erb %>
134
-
135
- <%= form.label(*arguments, **options, &block) %>
136
- ```
137
-
138
- ```html+erb
139
- <%# app/views/form_builder/_select.html.erb %>
140
-
141
- <%= form.select(*arguments, **html_options, &block) %>
142
- ```
119
+ * `block` - the block if the helper method was passed one. Forward it along to
120
+ field helpers as `&block`.
143
121
 
144
122
  #### Handling DOMTokenList attributes
145
123
 
@@ -158,16 +136,12 @@ When rendering a field's DOMTokenList-backed attributes (like `class` or
158
136
  controllers][stimulus-controller]), transforming and combining singular `String`
159
137
  instances into lists of token can be very useful.
160
138
 
161
- To simplify those scenarios, a partial's template-local optional attributes are
162
- made available with the `#merge_token_lists` method.
163
-
164
139
  These optional attributes are available through the `options` or `html_options`
165
140
  partial-local variables. Their name will depend on the partial's corresponding
166
141
  [`ActionView::Helpers::FormBuilder`][FormBuilder] interface.
167
142
 
168
- Calls to `#merge_token_lists` will merge the key-value pairs and return a new
169
- Hash-like structure. The attribute's value will be transformed into an `Array`.
170
- Given call to `form.text_field` and a corresponding partial declaration:
143
+ To "merge" attributes together, you can combine Ruby's `String` interpolation
144
+ and `Hash#delete`:
171
145
 
172
146
  ```html+erb
173
147
  <%# app/views/users/new.html.erb %>
@@ -175,9 +149,13 @@ Given call to `form.text_field` and a corresponding partial declaration:
175
149
  <%= form.text_field(:name, class: "text-field--modifier") %>
176
150
  <% end %>
177
151
 
178
- <# app/views/form_builder/_text_field.html.erb %>
152
+ <# app/views/application/form_builder/_text_field.html.erb %>
179
153
 
180
- <%= form.text_field(*arguments, **options.merge_token_lists(class: "text-field")) %>
154
+ <%= form.text_field(
155
+ method,
156
+ class: "text-field #{options.delete(:class)}",
157
+ **options
158
+ ) %>
181
159
  ```
182
160
 
183
161
  The resulting HTML `<input>` element will merge have its [`class`
@@ -192,7 +170,6 @@ values:
192
170
  [button]: https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-button
193
171
  [local_assigns]: https://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns
194
172
  [splat]: https://ruby-doc.org/core-2.2.0/doc/syntax/calling_methods_rdoc.html#label-Array+to+Arguments+Conversion
195
- [keyword arguments]: https://thoughtbot.com/blog/ruby-2-keyword-arguments
196
173
  [mdn-class]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class
197
174
  [DOMTokenList]: https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList
198
175
  [classList]: https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
@@ -210,7 +187,7 @@ block-local `form` variable:
210
187
  ```erb
211
188
  <%# app/views/users/form_builder/_email_field.html.erb %>
212
189
 
213
- <%= form.default.email_field(*arguments, **options) %>
190
+ <%= form.default.email_field(method, options) %>
214
191
  ```
215
192
 
216
193
  When passing a `model:` or `scope:` to calls to [`form_with`][form_with],
@@ -219,44 +196,64 @@ look up path.
219
196
 
220
197
  For example, when calling `form_with(model: User.new)`, a partial declared in
221
198
  `app/views/users/form_builder/` would take precedent over a partial declared in
222
- `app/views/form_builder/`.
199
+ `app/views/application/form_builder/`.
223
200
 
224
201
  ```erb
225
202
  <%# app/views/users/form_builder/_password_field.html.erb %>
226
203
 
227
204
  <div class="password-field-wrapper">
228
- <%= form.password_field(*arguments, **options) %>
205
+ <%= form.password_field(method, options) %>
229
206
  </div>
230
207
  ```
231
208
 
232
- If you'd like to render a specific partial for a field, you can declare the name
233
- as the `partial:` option:
209
+ If you'd like to render a specific partial for a field, make sure that you pass
210
+ along the `form:` (along with any other partial-local variables) as part of the
211
+ `render` call's `locals:` option:
212
+
234
213
 
235
214
  ```erb
236
215
  <%# app/views/users/new.html.erb %>
237
216
 
238
217
  <%= form_with(model: User.new) do |form| %>
239
- <%= form.email_field(:email, partial: "emails/my_special_text_field") %>
218
+ <%= render("emails/my_special_email_field", {
219
+ form: form,
220
+ method: :email,
221
+ options: { class: "user-email" },
222
+ ) %>
240
223
  <% end %>
224
+
225
+ <%# app/views/emails/_my_special_email_field.html.erb %>
226
+
227
+ <%= form.email_field(
228
+ method,
229
+ class: "my-special-email #{options.delete(:class)},
230
+ **options
231
+ ) %>
241
232
  ```
242
233
 
243
234
  #### Composing partials
244
235
 
245
- Passing a `partial:` key can be useful for layering partials on top of one
246
- another. For instance, consider an administrative interface that shares styles
247
- with a consumer facing site, but has additional bells and whistles.
236
+ Layering partials on top of one another can be useful to share foundational
237
+ styles and configuration across your fields. For instance, consider an
238
+ administrative interface that shares styles with a consumer facing site, but has
239
+ additional bells and whistles.
248
240
 
249
241
  Declare the consumer facing inputs (in this example, `<input type="search">`):
250
242
 
251
243
  ```html+erb
252
- <%# app/views/form_builder/_search_field.html.erb %>
244
+ <%# app/views/application/form_builder/_search_field.html.erb %>
253
245
 
254
246
  <%= form.search_field(
255
- *arguments,
256
- **options.merge_token_lists(
257
- class: "search-field",
258
- "data-controller": "input->search#executeQuery",
259
- ),
247
+ method,
248
+ class: "
249
+ search-field
250
+ #{options.delete(:class)}
251
+ ",
252
+ "data-controller": "
253
+ input->search#executeQuery
254
+ #{options.delete(:"data-controller")}
255
+ ",
256
+ **options
260
257
  ) %>
261
258
  ```
262
259
 
@@ -264,20 +261,23 @@ Then, declare the administrative interface's inputs, in terms of overriding the
264
261
  foundation built by the more general definitions:
265
262
 
266
263
  ```html+erb
267
- <%# app/views/admin/form_builder/_search_field.html.erb %>
264
+ <%# app/views/admin/application/form_builder/_search_field.html.erb %>
268
265
 
269
266
  <%= form.search_field(
270
- *arguments,
271
- partial: "form_builder/search_field",
272
- **options.merge_token_lists(
273
- class: "search-field--admin",
274
- "data-controller": "focus->admin-search#clearResults",
275
- ),
267
+ method,
268
+ class: "
269
+ search-field--admin
270
+ #{options.delete(:class}
271
+ ",
272
+ "data-controller": "
273
+ focus->admin-search#clearResults
274
+ #{options.delete(:"data-controller")}
275
+ ",
276
276
  ) %>
277
277
  ```
278
278
 
279
- The rendered `admin/form_builder/search_field` partial combines options and
280
- arguments from both partials:
279
+ The rendered `admin/application/form_builder/search_field` partial combines
280
+ options and arguments from both partials:
281
281
 
282
282
  ```html
283
283
  <input
@@ -327,7 +327,7 @@ Models declared within modules will be delimited with `/`. For example,
327
327
  ### Configuration
328
328
 
329
329
  View partials lookup and resolution will be scoped to the
330
- `app/views/form_builder` directory.
330
+ `app/views/application/form_builder` directory.
331
331
 
332
332
  To override this destination to another directory (for example,
333
333
  `app/views/fields`, or `app/views/users/fields`), set
@@ -1,9 +1,17 @@
1
- require "view_partial_form_builder/form_builder"
2
-
3
1
  module ViewPartialFormBuilder
4
2
  class Engine < ::Rails::Engine
5
3
  ActiveSupport.on_load(:action_controller_base) do
6
4
  default_form_builder ViewPartialFormBuilder::FormBuilder
7
5
  end
6
+
7
+ ActiveSupport.on_load(:view_partial_form_builder) do
8
+ if Rails::VERSION::MAJOR > 7
9
+ self.aliased_field_helpers = {
10
+ checkbox: [:check_box],
11
+ collection_checkboxes: [:collection_check_boxes],
12
+ rich_textarea: [:rich_text_area]
13
+ }
14
+ end
15
+ end
8
16
  end
9
17
  end
@@ -1,313 +1,15 @@
1
- require "view_partial_form_builder/lookup_override"
2
- require "view_partial_form_builder/html_attributes"
3
-
4
1
  module ViewPartialFormBuilder
5
2
  class FormBuilder < ActionView::Helpers::FormBuilder
3
+ class_attribute :aliased_field_helpers, default: {}
4
+
6
5
  attr_reader :default
7
6
 
8
7
  def initialize(*)
9
8
  super
10
-
11
- @default = ActionView::Helpers::FormBuilder.new(
12
- object_name,
13
- object,
14
- @template,
15
- options,
16
- )
17
- @lookup_override = LookupOverride.new(
18
- prefixes: @template.lookup_context.prefixes,
19
- object_name: object&.model_name || object_name,
20
- view_partial_directory: ViewPartialFormBuilder.view_partial_directory,
21
- )
22
- end
23
-
24
- def label(method, text = nil, **options, &block)
25
- locals = {
26
- method: method,
27
- text: text,
28
- options: HtmlAttributes.new(options),
29
- block: block,
30
- arguments: [method, text],
31
- }
32
-
33
- render_partial("label", locals, fallback: -> { super }, &block)
34
- end
35
-
36
- def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
37
- locals = {
38
- method: method,
39
- options: HtmlAttributes.new(options),
40
- checked_value: checked_value,
41
- unchecked_value: unchecked_value,
42
- arguments: [method, options, checked_value, unchecked_value],
43
- }
44
-
45
- render_partial("check_box", locals, fallback: -> { super })
46
- end
47
-
48
- def radio_button(method, tag_value, **options)
49
- locals = {
50
- method: method,
51
- tag_value: tag_value,
52
- options: HtmlAttributes.new(options),
53
- arguments: [method, tag_value],
54
- }
55
-
56
- render_partial("radio_button", locals, fallback: -> { super })
57
- end
58
-
59
- def select(method, choices = nil, options = {}, **html_options, &block)
60
- html_options = @default_html_options.merge(html_options)
61
-
62
- locals = {
63
- method: method,
64
- choices: choices,
65
- options: options,
66
- html_options: HtmlAttributes.new(html_options),
67
- block: block,
68
- arguments: [method, choices, options],
69
- }
70
-
71
- render_partial("select", locals, fallback: -> { super }, &block)
72
- end
73
-
74
- def collection_select(method, collection, value_method, text_method, options = {}, **html_options)
75
- html_options = @default_html_options.merge(html_options)
76
-
77
- locals = {
78
- method: method,
79
- collection: collection,
80
- value_method: value_method,
81
- text_method: text_method,
82
- options: options,
83
- html_options: html_options,
84
- arguments: [method, collection, value_method, text_method, options],
85
- }
86
-
87
- render_partial("collection_select", locals, fallback: -> { super })
88
- end
89
-
90
- def collection_check_boxes(method, collection, value_method, text_method, options = {}, **html_options, &block)
91
- html_options = @default_html_options.merge(html_options)
92
-
93
- locals = {
94
- method: method,
95
- collection: collection,
96
- value_method: value_method,
97
- text_method: text_method,
98
- options: options,
99
- html_options: HtmlAttributes.new(html_options),
100
- block: block,
101
- arguments: [
102
- method,
103
- collection,
104
- value_method,
105
- text_method,
106
- options,
107
- ],
108
- }
109
-
110
- render_partial("collection_check_boxes", locals, fallback: -> { super }, &block)
111
- end
112
-
113
- def collection_radio_buttons(method, collection, value_method, text_method, options = {}, **html_options, &block)
114
- html_options = @default_html_options.merge(html_options)
115
-
116
- locals = {
117
- method: method,
118
- collection: collection,
119
- value_method: value_method,
120
- text_method: text_method,
121
- options: options,
122
- html_options: HtmlAttributes.new(html_options),
123
- block: block,
124
- arguments: [
125
- method,
126
- collection,
127
- value_method,
128
- text_method,
129
- options,
130
- ],
131
- }
132
-
133
- render_partial("collection_radio_buttons", locals, fallback: -> { super }, &block)
134
- end
135
-
136
- def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, **html_options)
137
- html_options = @default_html_options.merge(html_options)
138
-
139
- locals = {
140
- method: method,
141
- collection: collection,
142
- group_method: group_method,
143
- group_label_method: group_label_method,
144
- option_key_method: option_key_method,
145
- option_value_method: option_value_method,
146
- html_options: HtmlAttributes.new(html_options),
147
- options: options,
148
- arguments: [
149
- method,
150
- collection,
151
- group_method,
152
- group_label_method,
153
- option_key_method,
154
- option_value_method,
155
- options,
156
- ],
157
- }
158
-
159
- render_partial("grouped_collection_select", locals, fallback: -> { super })
160
- end
161
-
162
- def time_zone_select(method, priority_zones = nil, options = {}, **html_options)
163
- html_options = @default_html_options.merge(html_options)
164
-
165
- locals = {
166
- method: method,
167
- priority_zones: priority_zones,
168
- html_options: HtmlAttributes.new(html_options),
169
- options: options,
170
- arguments: [method, priority_zones, options],
171
- }
172
-
173
- render_partial("time_zone_select", locals, fallback: -> { super })
174
- end
175
-
176
- def date_select(method, options = {}, **html_options)
177
- locals = {
178
- method: method,
179
- options: options,
180
- html_options: HtmlAttributes.new(html_options),
181
- arguments: [method, options, html_options],
182
- }
183
-
184
- render_partial("date_select", locals, fallback: -> { super })
185
- end
186
-
187
- def hidden_field(method, **options)
188
- @emitted_hidden_id = true if method == :id
189
-
190
- locals = {
191
- method: method,
192
- options: HtmlAttributes.new(options),
193
- arguments: [method],
194
- }
195
-
196
- render_partial("hidden_field", locals, fallback: -> { super })
197
- end
198
-
199
- def file_field(method, **options)
200
- self.multipart = true
201
-
202
- locals = {
203
- method: method,
204
- options: HtmlAttributes.new(options),
205
- arguments: [method],
206
- }
207
-
208
- render_partial("file_field", locals, fallback: -> { super })
209
- end
210
-
211
- def submit(value = nil, **options)
212
- value, options = nil, value if value.is_a?(Hash)
213
- value ||= submit_default_value
214
-
215
- locals = {
216
- value: value,
217
- options: HtmlAttributes.new(options),
218
- arguments: [value],
219
- }
220
-
221
- render_partial("submit", locals, fallback: -> { super })
222
- end
223
-
224
- def button(value = nil, **options)
225
- value, options = nil, value if value.is_a?(Hash)
226
- value ||= submit_default_value
227
-
228
- locals = {
229
- value: value,
230
- options: HtmlAttributes.new(options),
231
- arguments: [value],
232
- }
233
-
234
- render_partial("button", locals, fallback: -> { super })
235
- end
236
-
237
- DYNAMICALLY_DECLARED = (
238
- field_helpers +
239
- [:rich_text_area] -
240
- [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]
241
- )
242
-
243
- DYNAMICALLY_DECLARED.each do |selector|
244
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
245
- def #{selector}(method, **options)
246
- render_partial(
247
- "#{selector}",
248
- {
249
- method: method,
250
- options: HtmlAttributes.new(options),
251
- arguments: [method],
252
- },
253
- fallback: -> { super },
254
- )
255
- end
256
- RUBY
257
- end
258
-
259
- private
260
-
261
- def render_partial(field, locals, fallback:, &block)
262
- options = locals.fetch(:options, {})
263
- partial_override = options.delete(:partial)
264
- locals = objectify_options(options).merge(locals, form: self)
265
-
266
- partial = if partial_override.present?
267
- find_partial(partial_override, locals, prefixes: [])
268
- else
269
- find_partial(field, locals, prefixes: prefixes_after(field))
270
- end
271
-
272
- if partial.nil? || about_to_recurse_infinitely?(partial)
273
- fallback.call
274
- else
275
- partial.render(@template, locals, &block)
276
- end
277
- end
278
-
279
- def find_partial(template_name, locals, prefixes:)
280
- template_is_partial = true
281
-
282
- @template.lookup_context.find_all(
283
- template_name,
284
- prefixes,
285
- template_is_partial,
286
- locals.keys,
287
- ).first
288
- end
289
-
290
- def prefixes_after(template_name)
291
- prefixes = @lookup_override.prefixes
292
- current_prefix = current_virtual_path.delete_suffix("/_#{template_name}")
293
-
294
- if prefixes.include?(current_prefix)
295
- prefixes.from(prefixes.index(current_prefix).to_i + 1)
296
- else
297
- prefixes
298
- end
299
- end
300
-
301
- def about_to_recurse_infinitely?(partial)
302
- partial.virtual_path == current_virtual_path
303
- end
304
-
305
- def current_virtual_path
306
- if (current_template = @template.instance_values["current_template"])
307
- current_template.virtual_path.to_s
308
- else
309
- @template.instance_values["virtual_path"].to_s
310
- end
9
+ @default = dup
10
+ @template = TemplateProxy.new(builder: self, template: @template)
311
11
  end
312
12
  end
13
+
14
+ ActiveSupport.run_load_hooks(:view_partial_form_builder, FormBuilder)
313
15
  end
@@ -11,28 +11,24 @@ module ViewPartialFormBuilder
11
11
 
12
12
  prefixes = [
13
13
  "#{object_name}/#{view_partial_directory}",
14
- object_name,
15
- view_partial_directory,
16
14
  "#{root_prefix}/#{view_partial_directory}",
17
- root_prefix,
18
15
  ]
19
16
 
20
17
  overridden_prefixes.reverse_each do |prefix|
21
- namespace, *files = prefix.split("/")
22
-
23
- prefixes.unshift(prefix)
24
-
25
- if namespace.present?
26
- prefixes.unshift("#{namespace}/#{view_partial_directory}")
27
- end
28
-
29
18
  prefixes.unshift("#{prefix}/#{view_partial_directory}")
30
19
  end
31
20
 
32
-
33
21
  prefixes.uniq
34
22
  end
35
23
 
24
+ def prefixes_after(current_prefix)
25
+ if prefixes.include?(current_prefix)
26
+ prefixes.from(prefixes.index(current_prefix).to_i + 1)
27
+ else
28
+ prefixes
29
+ end
30
+ end
31
+
36
32
  private
37
33
 
38
34
  attr_reader :object_name, :view_partial_directory
@@ -0,0 +1,120 @@
1
+ module ViewPartialFormBuilder
2
+ class TemplateProxy
3
+ delegate :field_id, :field_name, to: :@template
4
+
5
+ def initialize(builder:, template:)
6
+ @template = template
7
+ @builder = builder
8
+ end
9
+
10
+ def button_tag(value, options, &block)
11
+ render(:button, arguments: [value, options], block: block) do
12
+ @template.button_tag(value, options, &block)
13
+ end
14
+ end
15
+
16
+ def submit_tag(value, options)
17
+ render(:submit, arguments: [value, options]) do
18
+ @template.submit_tag(value, options)
19
+ end
20
+ end
21
+
22
+ def label(object_name, method, content_or_options = nil, options = nil, &block)
23
+ if content_or_options.is_a?(Hash)
24
+ options.merge! content_or_options
25
+ content = nil
26
+ else
27
+ content = content_or_options
28
+ end
29
+
30
+ render(:label, arguments: [method, content, options], block: block) do
31
+ @template.label(object_name, method, content, options, &block)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def method_missing(name, *arguments, &block)
38
+ if @builder.respond_to?(name)
39
+ arguments_after_object_name = arguments.from(1)
40
+
41
+ render(name, arguments: arguments_after_object_name, block: block) do
42
+ @template.public_send(name, *arguments, &block)
43
+ end
44
+ elsif @template.respond_to?(name)
45
+ @template.public_send(name, *arguments, &block)
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def respond_to_missing?(name, include_private = false)
52
+ @builder.respond_to_missing?(name) || @template.respond_to_missing?(name)
53
+ end
54
+
55
+ def render(partial_name, arguments:, block: nil, &fallback)
56
+ locals = extract_partial_locals(partial_name, *arguments).merge(
57
+ form: @builder,
58
+ block: block
59
+ )
60
+
61
+ partial = find_partial(partial_name, locals)
62
+
63
+ if partial.nil? || about_to_recurse_infinitely?(partial)
64
+ fallback.call
65
+ else
66
+ partial.render(@template, locals, &block)
67
+ end
68
+ end
69
+
70
+ def extract_partial_locals(form_method, *arguments)
71
+ parameters = @builder.method(form_method).parameters
72
+
73
+ parameters.each_with_index.each_with_object({}) { |(tuple, index), locals|
74
+ _type, parameter = tuple
75
+
76
+ locals[parameter] = arguments[index]
77
+ }
78
+ end
79
+
80
+ def find_partial(template_name, locals)
81
+ current_prefix = current_virtual_path.delete_suffix("/_#{template_name}")
82
+ template_is_partial = true
83
+
84
+ partials = template_names_for(template_name).lazy.map do |name|
85
+ @template.lookup_context.find_all(
86
+ name,
87
+ lookup_override.prefixes_after(current_prefix),
88
+ template_is_partial,
89
+ locals.keys
90
+ ).first
91
+ end
92
+
93
+ partials.find(&:present?)
94
+ end
95
+
96
+ def template_names_for(template_name)
97
+ [template_name, *@builder.aliased_field_helpers[template_name]].compact
98
+ end
99
+
100
+ def about_to_recurse_infinitely?(partial)
101
+ partial.virtual_path == current_virtual_path
102
+ end
103
+
104
+ def current_virtual_path
105
+ if (current_template = @template.instance_values["current_template"])
106
+ current_template.virtual_path.to_s
107
+ else
108
+ @template.instance_values["virtual_path"].to_s
109
+ end
110
+ end
111
+
112
+ def lookup_override
113
+ LookupOverride.new(
114
+ prefixes: @template.lookup_context.prefixes,
115
+ object_name: @builder.object.try(:model_name) || @builder.object_name,
116
+ view_partial_directory: ViewPartialFormBuilder.view_partial_directory
117
+ )
118
+ end
119
+ end
120
+ end
@@ -1,3 +1,3 @@
1
1
  module ViewPartialFormBuilder
2
- VERSION = '0.1.4'
2
+ VERSION = "0.2.2"
3
3
  end
@@ -1,5 +1,9 @@
1
- require "view_partial_form_builder/engine"
1
+ require "zeitwerk"
2
+ loader = Zeitwerk::Loader.for_gem
3
+ loader.setup
2
4
 
3
5
  module ViewPartialFormBuilder
4
6
  mattr_accessor :view_partial_directory, default: "form_builder"
5
7
  end
8
+
9
+ loader.eager_load
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_partial_form_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Doyle
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-06 00:00:00.000000000 Z
11
+ date: 2024-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -16,36 +16,22 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.0.0
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.0.0
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: railties
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 4.0.0
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: 4.0.0
41
- - !ruby/object:Gem::Dependency
42
- name: minitest-around
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - ">="
46
32
  - !ruby/object:Gem::Version
47
33
  version: '0'
48
- type: :development
34
+ type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
@@ -53,33 +39,19 @@ dependencies:
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
- name: activemodel
42
+ name: zeitwerk
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - ">="
60
46
  - !ruby/object:Gem::Version
61
- version: 4.0.0
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: 4.0.0
69
- - !ruby/object:Gem::Dependency
70
- name: activerecord-nulldb-adapter
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
47
+ version: 2.4.0
48
+ type: :runtime
77
49
  prerelease: false
78
50
  version_requirements: !ruby/object:Gem::Requirement
79
51
  requirements:
80
52
  - - ">="
81
53
  - !ruby/object:Gem::Version
82
- version: '0'
54
+ version: 2.4.0
83
55
  description: A Rails form builder where all designer-facing configuration is via templates.
84
56
  email:
85
57
  - sean.p.doyle24@gmail.com
@@ -93,14 +65,14 @@ files:
93
65
  - lib/view_partial_form_builder.rb
94
66
  - lib/view_partial_form_builder/engine.rb
95
67
  - lib/view_partial_form_builder/form_builder.rb
96
- - lib/view_partial_form_builder/html_attributes.rb
97
68
  - lib/view_partial_form_builder/lookup_override.rb
69
+ - lib/view_partial_form_builder/template_proxy.rb
98
70
  - lib/view_partial_form_builder/version.rb
99
71
  homepage: https://github.com/seanpdoyle/view_partial_form_builder
100
72
  licenses:
101
73
  - MIT
102
74
  metadata: {}
103
- post_install_message:
75
+ post_install_message:
104
76
  rdoc_options: []
105
77
  require_paths:
106
78
  - lib
@@ -115,8 +87,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
87
  - !ruby/object:Gem::Version
116
88
  version: '0'
117
89
  requirements: []
118
- rubygems_version: 3.0.3
119
- signing_key:
90
+ rubygems_version: 3.5.16
91
+ signing_key:
120
92
  specification_version: 4
121
93
  summary: Construct <form> element fields by combining ActionView::Helpers::FormBuilder
122
94
  with Rails View Partials
@@ -1,47 +0,0 @@
1
- module ViewPartialFormBuilder
2
- class HtmlAttributes
3
- def initialize(**attributes)
4
- @attributes = attributes
5
- end
6
-
7
- def merge_token_lists(**token_list_attributes)
8
- merge(
9
- token_list_attributes.reduce({}) do |merged, (key, value)|
10
- token_list = Array(attributes.delete(key)).unshift(value)
11
-
12
- merged.merge(key => token_list.flatten.uniq)
13
- end
14
- )
15
- end
16
-
17
- def to_h
18
- attributes.to_h
19
- end
20
-
21
- def to_hash
22
- attributes.to_hash
23
- end
24
-
25
- def method_missing(method_name, *arguments, &block)
26
- if attributes.respond_to?(method_name)
27
- return_value = attributes.public_send(method_name, *arguments, &block)
28
-
29
- if return_value.kind_of?(Hash)
30
- HtmlAttributes.new(return_value)
31
- else
32
- return_value
33
- end
34
- else
35
- super
36
- end
37
- end
38
-
39
- def respond_to_missing?(method_name, include_private = false)
40
- attributes.respond_to?(method_name) || super
41
- end
42
-
43
- private
44
-
45
- attr_reader :attributes
46
- end
47
- end