view_partial_form_builder 0.1.4 → 0.2.2

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: 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