view_partial_form_builder 0.1.0 → 0.1.5

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: e30eab928ee84a6217afbabc1c746aa7a7906efa5417fc323f6b78e476aa3fbb
4
- data.tar.gz: 21af6978419ac00cebba9989b86943eb2d4d102397d880cee50acb8a7ef11c7c
3
+ metadata.gz: 1d1550e43fb981483d25dedf89ab64478b5dd2c5803b4aa7345ae6d7efba4fe2
4
+ data.tar.gz: d8bb992d8d959d2288c761d764275af9e470081145884b6bfcff7b7dda3fdc45
5
5
  SHA512:
6
- metadata.gz: 848d4ddf155cb750a63400e9a5aaa3ea062a4d01b3041294c6ddbf64c3570882e28052d6b55066c731320daae6b43e7fa53c47f1a89924603bab50c3ba3885be
7
- data.tar.gz: 05e69629609a81b0563bc5a234c3305dc0355e6f0b458d6aea41e75d49009d26a1e274a9ce7e4ccf6b50aa4ff90afa79c30220f44d97a4c754d9988fd9476046
6
+ metadata.gz: 121978b8280e71e3cf114291cf4874949c9323f7b909ab7cd93602f1a3fb00b143c2028ed1188623c8b926efe8b3a27e67005947599d66b8e1cb33c98d8c589e
7
+ data.tar.gz: 9e56d40123f55fac7ee53b3bd3e087ccc35bdf3ca709485cad0d636fb41f7f684744dbc068d7af13bcdc6aa246e9707373c557f792d86a1d3fee1cd76468e4f0
data/README.md CHANGED
@@ -87,7 +87,7 @@ generating HTML through Rails' helpers:
87
87
  <%# app/views/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,31 +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
119
  * `&block` - a callable, `yield`-able block if the helper method was passed one
124
120
 
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
- ```
143
-
144
121
  #### Handling DOMTokenList attributes
145
122
 
146
123
  An [HTML element's `class` attribute][mdn-class] is treated by browsers as a
@@ -158,16 +135,12 @@ When rendering a field's DOMTokenList-backed attributes (like `class` or
158
135
  controllers][stimulus-controller]), transforming and combining singular `String`
159
136
  instances into lists of token can be very useful.
160
137
 
161
- To simplify those scenarios, a partial's template-local optional attributes are
162
- made available with the `#merge_token_lists` method.
163
-
164
138
  These optional attributes are available through the `options` or `html_options`
165
139
  partial-local variables. Their name will depend on the partial's corresponding
166
140
  [`ActionView::Helpers::FormBuilder`][FormBuilder] interface.
167
141
 
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:
142
+ To "merge" attributes together, you can combine Ruby's `String` interpolation
143
+ and `Hash#delete`:
171
144
 
172
145
  ```html+erb
173
146
  <%# app/views/users/new.html.erb %>
@@ -177,7 +150,11 @@ Given call to `form.text_field` and a corresponding partial declaration:
177
150
 
178
151
  <# app/views/form_builder/_text_field.html.erb %>
179
152
 
180
- <%= form.text_field(*arguments, **options.merge_token_lists(class: "text-field")) %>
153
+ <%= form.text_field(
154
+ method,
155
+ class: "text-field #{options.delete(:class)}",
156
+ **options
157
+ ) %>
181
158
  ```
182
159
 
183
160
  The resulting HTML `<input>` element will merge have its [`class`
@@ -192,7 +169,6 @@ values:
192
169
  [button]: https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-button
193
170
  [local_assigns]: https://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns
194
171
  [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
172
  [mdn-class]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class
197
173
  [DOMTokenList]: https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList
198
174
  [classList]: https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
@@ -210,7 +186,7 @@ block-local `form` variable:
210
186
  ```erb
211
187
  <%# app/views/users/form_builder/_email_field.html.erb %>
212
188
 
213
- <%= form.default.email_field(*arguments, **options) %>
189
+ <%= form.default.email_field(method, options) %>
214
190
  ```
215
191
 
216
192
  When passing a `model:` or `scope:` to calls to [`form_with`][form_with],
@@ -225,21 +201,128 @@ For example, when calling `form_with(model: User.new)`, a partial declared in
225
201
  <%# app/views/users/form_builder/_password_field.html.erb %>
226
202
 
227
203
  <div class="password-field-wrapper">
228
- <%= form.password_field(*arguments, **options) %>
204
+ <%= form.password_field(method, options) %>
229
205
  </div>
230
206
  ```
231
207
 
232
- If you'd like to render a specific partial for a field, you can declare the name
233
- as the `partial:` option:
208
+ If you'd like to render a specific partial for a field, make sure that you pass
209
+ along the `form:` (along with any other partial-local variables) as part of the
210
+ `render` call's `locals:` option:
211
+
234
212
 
235
213
  ```erb
236
214
  <%# app/views/users/new.html.erb %>
237
215
 
238
216
  <%= form_with(model: User.new) do |form| %>
239
- <%= form.email_field(:email, partial: "emails/my_special_text_field") %>
217
+ <%= render("emails/my_special_email_field", {
218
+ form: form,
219
+ method: :email,
220
+ options: { class: "user-email" },
221
+ ) %>
240
222
  <% end %>
223
+
224
+ <%# app/views/emails/_my_special_email_field.html.erb %>
225
+
226
+ <%= form.email_field(
227
+ method,
228
+ class: "my-special-email #{options.delete(:class)},
229
+ **options
230
+ ) %>
241
231
  ```
242
232
 
233
+ #### Composing partials
234
+
235
+ Layering partials on top of one another can be useful to share foundational
236
+ styles and configuration across your fields. For instance, consider an
237
+ administrative interface that shares styles with a consumer facing site, but has
238
+ additional bells and whistles.
239
+
240
+ Declare the consumer facing inputs (in this example, `<input type="search">`):
241
+
242
+ ```html+erb
243
+ <%# app/views/form_builder/_search_field.html.erb %>
244
+
245
+ <%= form.search_field(
246
+ method,
247
+ class: "
248
+ search-field
249
+ #{options.delete(:class}
250
+ ",
251
+ "data-controller": "
252
+ input->search#executeQuery
253
+ #{options.delete(:"data-controller")}
254
+ ",
255
+ **options
256
+ ) %>
257
+ ```
258
+
259
+ Then, declare the administrative interface's inputs, in terms of overriding the
260
+ foundation built by the more general definitions:
261
+
262
+ ```html+erb
263
+ <%# app/views/admin/form_builder/_search_field.html.erb %>
264
+
265
+ <%= form.search_field(
266
+ method,
267
+ class: "
268
+ search-field--admin
269
+ #{options.delete(:class}
270
+ ",
271
+ "data-controller": "
272
+ focus->admin-search#clearResults
273
+ #{options.delete(:"data-controller")}
274
+ ",
275
+ ) %>
276
+ ```
277
+
278
+ The rendered `admin/form_builder/search_field` partial combines options and
279
+ arguments from both partials:
280
+
281
+ ```html
282
+ <input
283
+ type="search"
284
+ class="
285
+ search-field
286
+ search-field--admin
287
+ "
288
+ data-controller="
289
+ input->search#executeQuery
290
+ focus->admin-search#clearResults
291
+ "
292
+ >
293
+ ```
294
+
295
+ When constructing fields within a `form_with(model: ...)` block, partials will
296
+ use the `model:` instance's [`tableize`-d model name][tableize] to resolve
297
+ partials.
298
+
299
+ For example, `posts/form_builder/_text_field.html.erb` will be resolved ahead of
300
+ `form_builder/_text_field.html.erb`:
301
+
302
+ ```html+erb
303
+ <%# app/views/posts/form_builder/_text_field.html.erb %>
304
+
305
+ <%= form.text_field(method, class: "post-text #{options.delete(:class)}", **options) %>
306
+
307
+ <%# app/views/application/form_builder/_text_field.html.erb %>
308
+
309
+ <%= form.text_field(method, class: "text #{options.delete(:class)}", **options) %>
310
+ ```
311
+
312
+ The rendered `posts/form_builder/text_field` partial could combine options and
313
+ arguments from both partials:
314
+
315
+ ```html
316
+ <input type="text" class="post-text text">
317
+ ```
318
+
319
+ Models declared within modules will be delimited with `/`. For example,
320
+ `Special::Post` instances would first resolve partials within the
321
+ `app/views/special/posts/form_builder` directory, before falling back to
322
+ `app/views/application/form_builder`.
323
+
324
+ [tableize]: https://api.rubyonrails.org/classes/String.html#method-i-tableize
325
+
243
326
  ### Configuration
244
327
 
245
328
  View partials lookup and resolution will be scoped to the
@@ -1,4 +1,4 @@
1
- require "view_partial_form_builder/lookup_context"
1
+ require "view_partial_form_builder/lookup_override"
2
2
  require "view_partial_form_builder/html_attributes"
3
3
 
4
4
  module ViewPartialFormBuilder
@@ -14,9 +14,9 @@ module ViewPartialFormBuilder
14
14
  @template,
15
15
  options,
16
16
  )
17
- @lookup_context = LookupContext.new(
18
- overridden_context: @template.lookup_context,
19
- object_name: object_name,
17
+ @lookup_override = LookupOverride.new(
18
+ prefixes: @template.lookup_context.prefixes,
19
+ object_name: object&.model_name || object_name,
20
20
  view_partial_directory: ViewPartialFormBuilder.view_partial_directory,
21
21
  )
22
22
  end
@@ -173,6 +173,17 @@ module ViewPartialFormBuilder
173
173
  render_partial("time_zone_select", locals, fallback: -> { super })
174
174
  end
175
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
+
176
187
  def hidden_field(method, **options)
177
188
  @emitted_hidden_id = true if method == :id
178
189
 
@@ -247,50 +258,90 @@ module ViewPartialFormBuilder
247
258
 
248
259
  private
249
260
 
250
- attr_reader :lookup_context
251
-
252
261
  def render_partial(field, locals, fallback:, &block)
253
- return fallback.call if about_to_recurse_infinitely?(field)
254
-
255
262
  options = locals.fetch(:options, {})
256
263
  partial_override = options.delete(:partial)
257
- locals = objectify_options(options).merge(locals, form: self)
258
-
259
- lookup_context.override do
260
- if partial_override.present?
261
- render(partial_override, locals, &block)
262
- elsif partial_exists?(field)
263
- render(field, locals, &block)
264
- else
265
- fallback.call
266
- end
264
+ options_as_locals = objectify_options(options)
265
+ locals = DeprecatedHash.new(
266
+ options_as_locals.merge(locals, form: self),
267
+ deprecated_keys: options_as_locals.keys + [:arguments],
268
+ )
269
+
270
+ partial = if partial_override.present?
271
+ ActiveSupport::Deprecation.new("0.2.0", "ViewPartialFormBuilder").warn(<<~WARNING)
272
+ Providing a `partial:` option for a form field is deprecated.
273
+ WARNING
274
+
275
+ find_partial(partial_override, locals, prefixes: [])
276
+ else
277
+ find_partial(field, locals, prefixes: prefixes_after(field))
278
+ end
279
+
280
+ if partial.nil? || about_to_recurse_infinitely?(partial)
281
+ fallback.call
282
+ else
283
+ partial.render(@template, locals, &block)
267
284
  end
268
285
  end
269
286
 
270
- def partial_exists?(template_name)
287
+ def find_partial(template_name, locals, prefixes:)
271
288
  template_is_partial = true
272
289
 
273
- lookup_context.template_exists?(
290
+ @template.lookup_context.find_all(
274
291
  template_name,
275
- lookup_context.prefixes,
292
+ prefixes,
276
293
  template_is_partial,
277
- )
294
+ locals.keys,
295
+ ).first.tap do |partial|
296
+ root_directory = ViewPartialFormBuilder.view_partial_directory
297
+
298
+ if partial&.virtual_path == "#{root_directory}/_#{template_name}"
299
+ ActiveSupport::Deprecation.new("0.2.0", "ViewPartialFormBuilder").warn(<<~WARNING.strip)
300
+ Declare root-level partials in app/views/application/#{root_directory}/, not app/views/#{root_directory}/.
301
+ WARNING
302
+ end
303
+ end
304
+ end
305
+
306
+ def prefixes_after(template_name)
307
+ prefixes = @lookup_override.prefixes
308
+ current_prefix = current_virtual_path.delete_suffix("/_#{template_name}")
309
+
310
+ if prefixes.include?(current_prefix)
311
+ prefixes.from(prefixes.index(current_prefix).to_i + 1)
312
+ else
313
+ prefixes
314
+ end
315
+ end
316
+
317
+ def about_to_recurse_infinitely?(partial)
318
+ partial.virtual_path == current_virtual_path
278
319
  end
279
320
 
280
- def render(partial_name, locals, &block)
281
- if block.present?
282
- @template.render(layout: partial_name, locals: locals, &block)
321
+ def current_virtual_path
322
+ if (current_template = @template.instance_values["current_template"])
323
+ current_template.virtual_path.to_s
283
324
  else
284
- @template.render(partial: partial_name, locals: locals)
325
+ @template.instance_values["virtual_path"].to_s
285
326
  end
286
327
  end
328
+ end
287
329
 
288
- def about_to_recurse_infinitely?(field)
289
- @template.instance_eval do
290
- *, partial = @virtual_path.split("/")
330
+ class DeprecatedHash < Hash
331
+ def initialize(hash, deprecated_keys: [])
332
+ super()
333
+ merge!(hash)
334
+ @deprecated_keys = deprecated_keys
335
+ end
291
336
 
292
- partial == "_#{field}"
337
+ def [](key)
338
+ if @deprecated_keys.include?(key)
339
+ ActiveSupport::Deprecation.new("0.2.0", "ViewPartialFormBuilder").warn <<~WARNING
340
+ Accessing `#{key}` from partials is deprecated.
341
+ WARNING
293
342
  end
343
+
344
+ super
294
345
  end
295
346
  end
296
347
  end
@@ -1,5 +1,13 @@
1
1
  module ViewPartialFormBuilder
2
2
  class HtmlAttributes
3
+ deprecate merge_token_lists: <<~'WARNING', deprecator: ActiveSupport::Deprecation.new("0.2.0", "ViewPartialFormBuilder")
4
+
5
+ As an alternative, merge arguments through String interpolation:
6
+
7
+ <%= form.label(method, class: "label #{options.delete(:class)}", **options) %>
8
+
9
+ WARNING
10
+
3
11
  def initialize(**attributes)
4
12
  @attributes = attributes
5
13
  end
@@ -1,29 +1,12 @@
1
1
  module ViewPartialFormBuilder
2
- class LookupContext
3
- delegate_missing_to :overridden_context
4
-
5
- def initialize(overridden_context:, object_name:, view_partial_directory:)
6
- @object_name = object_name.to_s.pluralize
7
- @overridden_context = overridden_context
8
- @prefixes = overridden_context.prefixes.dup.freeze
2
+ class LookupOverride
3
+ def initialize(prefixes:, object_name:, view_partial_directory:)
4
+ @object_name = object_name.to_s.tableize
5
+ @prefixes = prefixes
9
6
  @view_partial_directory = view_partial_directory
10
7
  end
11
8
 
12
- def override(&block)
13
- previous_prefixes = overridden_context.prefixes
14
-
15
- overridden_context.prefixes = prefix_overrides
16
-
17
- yield
18
- ensure
19
- overridden_context.prefixes = previous_prefixes
20
- end
21
-
22
- private
23
-
24
- attr_reader :overridden_context, :object_name, :view_partial_directory
25
-
26
- def prefix_overrides
9
+ def prefixes
27
10
  *overridden_prefixes, root_prefix = @prefixes.dup
28
11
 
29
12
  prefixes = [
@@ -49,5 +32,9 @@ module ViewPartialFormBuilder
49
32
 
50
33
  prefixes.uniq
51
34
  end
35
+
36
+ private
37
+
38
+ attr_reader :object_name, :view_partial_directory
52
39
  end
53
40
  end
@@ -1,3 +1,3 @@
1
1
  module ViewPartialFormBuilder
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.5'
3
3
  end
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.0
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Doyle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-12 00:00:00.000000000 Z
11
+ date: 2020-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -94,7 +94,7 @@ files:
94
94
  - lib/view_partial_form_builder/engine.rb
95
95
  - lib/view_partial_form_builder/form_builder.rb
96
96
  - lib/view_partial_form_builder/html_attributes.rb
97
- - lib/view_partial_form_builder/lookup_context.rb
97
+ - lib/view_partial_form_builder/lookup_override.rb
98
98
  - lib/view_partial_form_builder/version.rb
99
99
  homepage: https://github.com/seanpdoyle/view_partial_form_builder
100
100
  licenses: