superform 0.4.0 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -1
- data/README.md +61 -2
- data/lib/superform/rails.rb +162 -45
- data/lib/superform/version.rb +1 -1
- data/lib/superform.rb +106 -21
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e95163f7344e0731cc859cf60a4bc1486bede4fe3cc88c86a0bd98497ecdf369
|
4
|
+
data.tar.gz: ebd8c4ea43a8958a4d6c70f4886aec84dd20a08a5b51563d18b2d65a4e2afff1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d03e967c4e018cb6902ad360719b5c98c4f7ab698a3dd9b6b61d3f3e4fde98231242085657ad9da20258335b34e52c5df7087d9ff27b503fa3a37b69a0c9eab
|
7
|
+
data.tar.gz: b859c82d78000486ab7b51070b22d4611f634513a5ba4f8f1f0c551e75ca3ced2549255858b1c56bc7fc48341ea35a84cc084c9be41af041840d45cb862fea6b
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -36,6 +36,7 @@ class Posts::Form < ApplicationForm
|
|
36
36
|
def template(&)
|
37
37
|
row field(:title).input
|
38
38
|
row field(:body).textarea
|
39
|
+
row field(:blog).select Blog.select(:id, :title)
|
39
40
|
end
|
40
41
|
end
|
41
42
|
```
|
@@ -106,6 +107,64 @@ Then render it from Erb.
|
|
106
107
|
|
107
108
|
Much better!
|
108
109
|
|
110
|
+
## Form field guide
|
111
|
+
|
112
|
+
Superform tries to strike a balance between "being as close to HTML forms as possible" and not requiring a lot of boilerplate to create forms. This example is contrived, but it shows all the different ways you can render a form.
|
113
|
+
|
114
|
+
In practice, many of the calls below you'd put inside of a method. This cuts down on the number of `render` calls in your HTML code and further reduces boilerplate.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# Everything below is intentionally verbose!
|
118
|
+
class SignupForm < ApplicationForm
|
119
|
+
def template
|
120
|
+
# The most basic type of input, which will be autofocused.
|
121
|
+
render field(:name).input.focus
|
122
|
+
|
123
|
+
# Input field with a lot more options on it.
|
124
|
+
render field(:email).input(type: :email, placeholder: "We will sell this to third parties", required: true)
|
125
|
+
|
126
|
+
# You can put fields in a block if that's your thing.
|
127
|
+
render field(:reason) do |f|
|
128
|
+
div do
|
129
|
+
f.label { "Why should we care about you?" }
|
130
|
+
f.textarea(row: 3, col: 80)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Let's get crazy with Selects. They can accept values as simple as 2 element arrays.
|
135
|
+
div do
|
136
|
+
render field(:contact).label { "Would you like us to spam you to death?" }
|
137
|
+
render field(:contact).select(
|
138
|
+
[true, "Yes"], # <option value="true">Yes</option>
|
139
|
+
[false, "No"], # <option value="false">No</option>
|
140
|
+
"Hell no", # <option value="Hell no">Hell no</option>
|
141
|
+
nil # <option></option>
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
div do
|
146
|
+
render field(:source).label { "How did you hear about us?" }
|
147
|
+
render field(:source).select do |s|
|
148
|
+
# Pretend WebSources is an ActiveRecord scope with a "Social" category that has "Facebook, X, etc"
|
149
|
+
# and a "Search" category with "AltaVista, Yahoo, etc."
|
150
|
+
WebSources.select(:id, :name).group_by(:category) do |category, sources|
|
151
|
+
s.optgroup(label: category) do
|
152
|
+
s.options(sources)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
div do
|
159
|
+
render field(:agreement).label { "Check this box if you agree to give us your first born child" }
|
160
|
+
render field(:agreement).input(type: :checkbox, value: true)
|
161
|
+
end
|
162
|
+
|
163
|
+
render button { "Submit" }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
109
168
|
## Extending Superforms
|
110
169
|
|
111
170
|
The best part? If you have forms with a completely different look and feel, you can extend the forms just like you would a Ruby class:
|
@@ -140,11 +199,11 @@ class Admin::Users::Form < AdminForm
|
|
140
199
|
end
|
141
200
|
```
|
142
201
|
|
143
|
-
Since Superforms are just Ruby objects, you can organize them however you want. You can keep your view component classes embedded in your Superform file if you prefer for
|
202
|
+
Since Superforms are just Ruby objects, you can organize them however you want. You can keep your view component classes embedded in your Superform file if you prefer for everything to be in one place, keep the forms in the `app/views/forms/*.rb` folder and the components in `app/views/forms/**/*_component.rb`, use Ruby's `include` and `extend` features to modify different form classes, or put them in a gem and share them with an entire organization or open source community. It's just Ruby code!
|
144
203
|
|
145
204
|
## Automatic strong parameters
|
146
205
|
|
147
|
-
Guess what? Superform eliminates
|
206
|
+
Guess what? Superform eliminates the need for Strong Parameters in Rails by assigning the values of the `params` hash _through_ your form via the `assign` method. Here's what it looks like.
|
148
207
|
|
149
208
|
```ruby
|
150
209
|
class PostsController < ApplicationController
|
data/lib/superform/rails.rb
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
module Superform
|
2
2
|
module Rails
|
3
|
-
|
3
|
+
# The `ApplicationComponent` is the superclass for all components in your application.
|
4
|
+
Component = ::ApplicationComponent
|
5
|
+
|
6
|
+
# A Phlex::HTML view module that accepts a model and sets a `Superform::Namespace`
|
7
|
+
# with the `Object#model_name` as the key and maps the object to form fields
|
8
|
+
# and namespaces.
|
9
|
+
#
|
10
|
+
# The `Form::Field` is a class that's meant to be extended so you can customize the `Form` inputs
|
11
|
+
# to your applications needs. Defaults for the `input`, `button`, `label`, and `textarea` tags
|
12
|
+
# are provided.
|
13
|
+
#
|
14
|
+
# The `Form` component also handles Rails authenticity tokens via the `authenticity_toklen_field`
|
15
|
+
# method and the HTTP verb via the `_method_field`.
|
16
|
+
class Form < Component
|
4
17
|
attr_reader :model
|
5
18
|
|
6
19
|
delegate \
|
@@ -12,6 +25,26 @@ module Superform
|
|
12
25
|
:serialize,
|
13
26
|
to: :@namespace
|
14
27
|
|
28
|
+
# The Field class is designed to be extended to create custom forms. To override,
|
29
|
+
# in your subclass you may have something like this:
|
30
|
+
#
|
31
|
+
# ```ruby
|
32
|
+
# class MyForm
|
33
|
+
# class MyLabel < LabelComponent
|
34
|
+
# def template(&content)
|
35
|
+
# label(form: @field.dom.name, class: "text-bold", &content)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# class Field < Field
|
40
|
+
# def label(**attributes)
|
41
|
+
# MyLabel.new(self, attributes: **attributes)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# ```
|
46
|
+
#
|
47
|
+
# Now all calls to `label` will have the `text-bold` class applied to it.
|
15
48
|
class Field < Superform::Field
|
16
49
|
def button(**attributes)
|
17
50
|
Components::ButtonComponent.new(self, attributes: attributes)
|
@@ -29,32 +62,41 @@ module Superform
|
|
29
62
|
Components::TextareaComponent.new(self, attributes: attributes)
|
30
63
|
end
|
31
64
|
|
65
|
+
def select(*collection, **attributes, &)
|
66
|
+
Components::SelectField.new(self, attributes: attributes, collection: collection, &)
|
67
|
+
end
|
68
|
+
|
32
69
|
def title
|
33
70
|
key.to_s.titleize
|
34
71
|
end
|
35
72
|
end
|
36
73
|
|
37
|
-
def initialize(model, action: nil, method: nil)
|
74
|
+
def initialize(model, action: nil, method: nil, **attributes)
|
38
75
|
@model = model
|
39
76
|
@action = action
|
40
77
|
@method = method
|
78
|
+
@attributes = attributes
|
41
79
|
@namespace = Namespace.root(model.model_name.param_key, object: model, field_class: self.class::Field)
|
42
80
|
end
|
43
81
|
|
44
82
|
def around_template(&)
|
45
|
-
|
83
|
+
form_tag do
|
46
84
|
authenticity_token_field
|
47
85
|
_method_field
|
48
86
|
super
|
49
87
|
end
|
50
88
|
end
|
51
89
|
|
90
|
+
def form_tag(&)
|
91
|
+
form action: form_action, method: form_method, **@attributes, &
|
92
|
+
end
|
93
|
+
|
52
94
|
def template(&block)
|
53
95
|
yield_content(&block)
|
54
96
|
end
|
55
97
|
|
56
|
-
def submit(value = submit_value)
|
57
|
-
input(
|
98
|
+
def submit(value = submit_value, **attributes)
|
99
|
+
input **attributes.merge(
|
58
100
|
name: "commit",
|
59
101
|
type: "submit",
|
60
102
|
value: value
|
@@ -62,42 +104,41 @@ module Superform
|
|
62
104
|
end
|
63
105
|
|
64
106
|
protected
|
107
|
+
def authenticity_token_field
|
108
|
+
input(
|
109
|
+
name: "authenticity_token",
|
110
|
+
type: "hidden",
|
111
|
+
value: helpers.form_authenticity_token
|
112
|
+
)
|
113
|
+
end
|
65
114
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
def _method_field
|
75
|
-
input(
|
76
|
-
name: "_method",
|
77
|
-
type: "hidden",
|
78
|
-
value: _method_field_value
|
79
|
-
)
|
80
|
-
end
|
115
|
+
def _method_field
|
116
|
+
input(
|
117
|
+
name: "_method",
|
118
|
+
type: "hidden",
|
119
|
+
value: _method_field_value
|
120
|
+
)
|
121
|
+
end
|
81
122
|
|
82
|
-
|
83
|
-
|
84
|
-
|
123
|
+
def _method_field_value
|
124
|
+
@method || @model.persisted? ? "patch" : "post"
|
125
|
+
end
|
85
126
|
|
86
|
-
|
87
|
-
|
88
|
-
|
127
|
+
def submit_value
|
128
|
+
"#{resource_action.to_s.capitalize} #{@model.model_name}"
|
129
|
+
end
|
89
130
|
|
90
|
-
|
91
|
-
|
92
|
-
|
131
|
+
def resource_action
|
132
|
+
@model.persisted? ? :update : :create
|
133
|
+
end
|
93
134
|
|
94
|
-
|
95
|
-
|
96
|
-
|
135
|
+
def form_action
|
136
|
+
@action ||= helpers.url_for(action: resource_action)
|
137
|
+
end
|
97
138
|
|
98
|
-
|
99
|
-
|
100
|
-
|
139
|
+
def form_method
|
140
|
+
@method.to_s.downcase == "get" ? "get" : "post"
|
141
|
+
end
|
101
142
|
end
|
102
143
|
|
103
144
|
module StrongParameters
|
@@ -112,8 +153,41 @@ module Superform
|
|
112
153
|
end
|
113
154
|
end
|
114
155
|
|
156
|
+
# Accept a collection of objects and map them to options suitable for form controls, like `select > options`
|
157
|
+
class OptionMapper
|
158
|
+
include Enumerable
|
159
|
+
|
160
|
+
def initialize(collection)
|
161
|
+
@collection = collection
|
162
|
+
end
|
163
|
+
|
164
|
+
def each(&options)
|
165
|
+
@collection.each do |object|
|
166
|
+
case object
|
167
|
+
in ActiveRecord::Relation => relation
|
168
|
+
active_record_relation_options_enumerable(relation).each(&options)
|
169
|
+
in id, value
|
170
|
+
options.call id, value
|
171
|
+
in value
|
172
|
+
options.call value, value.to_s
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def active_record_relation_options_enumerable(relation)
|
178
|
+
Enumerator.new do |collection|
|
179
|
+
relation.each do |object|
|
180
|
+
attributes = object.attributes
|
181
|
+
id = attributes.delete(relation.primary_key)
|
182
|
+
value = attributes.values.join(" ")
|
183
|
+
collection << [ id, value ]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
115
189
|
module Components
|
116
|
-
class
|
190
|
+
class BaseComponent < Component
|
117
191
|
attr_reader :field, :dom
|
118
192
|
|
119
193
|
delegate :dom, to: :field
|
@@ -139,9 +213,16 @@ module Superform
|
|
139
213
|
end
|
140
214
|
end
|
141
215
|
|
142
|
-
class
|
143
|
-
def
|
144
|
-
|
216
|
+
class FieldComponent < BaseComponent
|
217
|
+
def field_attributes
|
218
|
+
{ id: dom.id, name: dom.name }
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class LabelComponent < BaseComponent
|
223
|
+
def template(&content)
|
224
|
+
content ||= Proc.new { field.key.to_s.titleize }
|
225
|
+
label(**attributes, &content)
|
145
226
|
end
|
146
227
|
|
147
228
|
def field_attributes
|
@@ -150,8 +231,9 @@ module Superform
|
|
150
231
|
end
|
151
232
|
|
152
233
|
class ButtonComponent < FieldComponent
|
153
|
-
def template(&
|
154
|
-
|
234
|
+
def template(&content)
|
235
|
+
content ||= Proc.new { button_text }
|
236
|
+
button(**attributes, &content)
|
155
237
|
end
|
156
238
|
|
157
239
|
def button_text
|
@@ -189,13 +271,48 @@ module Superform
|
|
189
271
|
end
|
190
272
|
|
191
273
|
class TextareaComponent < FieldComponent
|
192
|
-
def template(&)
|
193
|
-
|
274
|
+
def template(&content)
|
275
|
+
content ||= Proc.new { dom.value }
|
276
|
+
textarea(**attributes, &content)
|
194
277
|
end
|
278
|
+
end
|
195
279
|
|
196
|
-
|
197
|
-
|
280
|
+
class SelectField < FieldComponent
|
281
|
+
def initialize(*, collection: [], **, &)
|
282
|
+
super(*, **, &)
|
283
|
+
@collection = collection
|
198
284
|
end
|
285
|
+
|
286
|
+
def template(&options)
|
287
|
+
if block_given?
|
288
|
+
select(**attributes, &options)
|
289
|
+
else
|
290
|
+
select(**attributes) { options(*@collection) }
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def options(*collection)
|
295
|
+
map_options(collection).each do |key, value|
|
296
|
+
option(selected: field.value == key, value: key) { value }
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def blank_option(&)
|
301
|
+
option(selected: field.value.nil?, &)
|
302
|
+
end
|
303
|
+
|
304
|
+
def true_option(&)
|
305
|
+
option(selected: field.value == true, value: true.to_s, &)
|
306
|
+
end
|
307
|
+
|
308
|
+
def false_option(&)
|
309
|
+
option(selected: field.value == false, value: false.to_s, &)
|
310
|
+
end
|
311
|
+
|
312
|
+
protected
|
313
|
+
def map_options(collection)
|
314
|
+
OptionMapper.new(collection)
|
315
|
+
end
|
199
316
|
end
|
200
317
|
end
|
201
318
|
end
|
data/lib/superform/version.rb
CHANGED
data/lib/superform.rb
CHANGED
@@ -1,26 +1,44 @@
|
|
1
|
+
require "zeitwerk"
|
2
|
+
|
1
3
|
module Superform
|
2
|
-
|
4
|
+
Loader = Zeitwerk::Loader.for_gem.tap do |loader|
|
5
|
+
loader.ignore "#{__dir__}/generators"
|
6
|
+
loader.setup
|
7
|
+
end
|
3
8
|
|
4
|
-
|
9
|
+
class Error < StandardError; end
|
5
10
|
|
11
|
+
# Generates DOM IDs, names, etc. for a Field, Namespace, or Node based on
|
12
|
+
# norms that were established by Rails. These can be used outsidef or Rails in
|
13
|
+
# other Ruby web frameworks since it has now dependencies on Rails.
|
6
14
|
class DOM
|
7
15
|
def initialize(field:)
|
8
16
|
@field = field
|
9
17
|
end
|
10
18
|
|
19
|
+
# Converts the value of the field to a String, which is required to work
|
20
|
+
# with Phlex. Assumes that `Object#to_s` emits a format suitable for the web form.
|
11
21
|
def value
|
12
22
|
@field.value.to_s
|
13
23
|
end
|
14
24
|
|
25
|
+
# Walks from the current node to the parent node, grabs the names, and seperates
|
26
|
+
# them with a `_` for a DOM ID. One limitation of this approach is if multiple forms
|
27
|
+
# exist on the same page, the ID may be duplicate.
|
15
28
|
def id
|
16
29
|
lineage.map(&:key).join("_")
|
17
30
|
end
|
18
31
|
|
32
|
+
# The `name` attribute of a node, which is influenced by Rails (not sure where Rails got
|
33
|
+
# it from). All node names, except the parent node, are wrapped in a `[]` and collections
|
34
|
+
# are left empty. For example, `user[addresses][][street]` would be created for a form with
|
35
|
+
# data shaped like `{user: {addresses: [{street: "Sesame Street"}]}}`.
|
19
36
|
def name
|
20
37
|
root, *names = keys
|
21
38
|
names.map { |name| "[#{name}]" }.unshift(root).join
|
22
39
|
end
|
23
40
|
|
41
|
+
# Emit the id, name, and value in an HTML tag-ish that doesnt have an element.
|
24
42
|
def inspect
|
25
43
|
"<id=#{id.inspect} name=#{name.inspect} value=#{value.inspect}/>"
|
26
44
|
end
|
@@ -34,11 +52,15 @@ module Superform
|
|
34
52
|
end
|
35
53
|
end
|
36
54
|
|
55
|
+
# One-liner way of walking from the current node all the way up to the parent.
|
37
56
|
def lineage
|
38
57
|
Enumerator.produce(@field, &:parent).take_while(&:itself).reverse
|
39
58
|
end
|
40
59
|
end
|
41
60
|
|
61
|
+
|
62
|
+
# Superclass for Namespace and Field classes. Not much to it other than it has a `name`
|
63
|
+
# and `parent` node attribute. Think of it as a tree.
|
42
64
|
class Node
|
43
65
|
attr_reader :key, :parent
|
44
66
|
|
@@ -48,6 +70,13 @@ module Superform
|
|
48
70
|
end
|
49
71
|
end
|
50
72
|
|
73
|
+
# A Namespace maps and object to values, but doesn't actually have a value itself. For
|
74
|
+
# example, a `User` object or ActiveRecord model could be passed into the `:user` namespace.
|
75
|
+
# To access the values on a Namespace, the `field` can be called for single values.
|
76
|
+
#
|
77
|
+
# Additionally, to access namespaces within a namespace, such as if a `User has_many :addresses` in
|
78
|
+
# ActiveRecord, the `namespace` method can be called which will return another Namespace object and
|
79
|
+
# set the current Namespace as the parent.
|
51
80
|
class Namespace < Node
|
52
81
|
include Enumerable
|
53
82
|
|
@@ -61,28 +90,76 @@ module Superform
|
|
61
90
|
yield self if block_given?
|
62
91
|
end
|
63
92
|
|
93
|
+
# Creates a `Namespace` child instance with the parent set to the current instance, adds to
|
94
|
+
# the `@children` Hash to ensure duplicate child namespaces aren't created, then calls the
|
95
|
+
# method on the `@object` to get the child object to pass into that namespace.
|
96
|
+
#
|
97
|
+
# For example, if a `User#permission` returns a `Permission` object, we could map that to a
|
98
|
+
# form like this:
|
99
|
+
#
|
100
|
+
# ```ruby
|
101
|
+
# Superform :user, object: User.new do |form|
|
102
|
+
# form.namespace :permission do |permission|
|
103
|
+
# form.field :role
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
# ```
|
64
107
|
def namespace(key, &block)
|
65
108
|
create_child(key, self.class, object: object_for(key: key), &block)
|
66
109
|
end
|
67
110
|
|
111
|
+
# Maps the `Object#proprety` and `Object#property=` to a field in a web form that can be
|
112
|
+
# read and set by the form. For example, a User form might look like this:
|
113
|
+
#
|
114
|
+
# ```ruby
|
115
|
+
# Superform :user, object: User.new do |form|
|
116
|
+
# form.field :email
|
117
|
+
# form.field :name
|
118
|
+
# end
|
119
|
+
# ```
|
68
120
|
def field(key)
|
69
|
-
create_child(key, @field_class, object: object)
|
70
|
-
|
71
|
-
|
72
|
-
def collection(key, &block)
|
73
|
-
create_child(key, NamespaceCollection, &block)
|
121
|
+
create_child(key, @field_class, object: object).tap do |field|
|
122
|
+
yield field if block_given?
|
123
|
+
end
|
74
124
|
end
|
75
125
|
|
126
|
+
# Wraps an array of objects in Namespace classes. For example, if `User#addresses` returns
|
127
|
+
# an enumerable or array of `Address` classes:
|
128
|
+
#
|
129
|
+
# ```ruby
|
130
|
+
# Superform :user, object: User.new do |form|
|
131
|
+
# form.field :email
|
132
|
+
# form.field :name
|
133
|
+
# form.collection :addresses do |address|
|
134
|
+
# address.field(:street)
|
135
|
+
# address.field(:state)
|
136
|
+
# address.field(:zip)
|
137
|
+
# end
|
138
|
+
# end
|
139
|
+
# ```
|
140
|
+
# The object within the block is a `Namespace` object that maps each object within the enumerable
|
141
|
+
# to another `Namespace` or `Field`.
|
142
|
+
def collection(key, &)
|
143
|
+
create_child(key, NamespaceCollection, &)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Creates a Hash of Hashes and Arrays that represent the fields and collections of the Superform.
|
147
|
+
# This can be used to safely update ActiveRecord objects without the need for Strong Parameters.
|
148
|
+
# You will want to make sure that all the fields displayed in the form are ones that you're OK updating
|
149
|
+
# from the generated hash.
|
76
150
|
def serialize
|
77
151
|
each_with_object Hash.new do |child, hash|
|
78
152
|
hash[child.key] = child.serialize
|
79
153
|
end
|
80
154
|
end
|
81
155
|
|
156
|
+
# Iterates through the children of the current namespace, which could be `Namespace` or `Field`
|
157
|
+
# objects.
|
82
158
|
def each(&)
|
83
159
|
@children.values.each(&)
|
84
160
|
end
|
85
161
|
|
162
|
+
# Assigns a hash to the current namespace and children namespace.
|
86
163
|
def assign(hash)
|
87
164
|
each do |child|
|
88
165
|
child.assign hash[child.key]
|
@@ -90,22 +167,30 @@ module Superform
|
|
90
167
|
self
|
91
168
|
end
|
92
169
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
def create_child(key, child_class, **options, &block)
|
99
|
-
fetch(key) { child_class.new(key, parent: self, **options, &block) }
|
170
|
+
# Creates a root Namespace, which is essentially a form.
|
171
|
+
def self.root(*, **, &)
|
172
|
+
new(*, parent: nil, **, &)
|
100
173
|
end
|
101
174
|
|
102
|
-
|
103
|
-
@children[key] ||= build.call
|
104
|
-
end
|
175
|
+
protected
|
105
176
|
|
177
|
+
# Calls the corresponding method on the object for the `key` name, if it exists. For example
|
178
|
+
# if the `key` is `email` on `User`, this method would call `User#email` if the method is
|
179
|
+
# present.
|
180
|
+
#
|
181
|
+
# This method could be overwritten if the mapping between the `@object` and `key` name is not
|
182
|
+
# a method call. For example, a `Hash` would be accessed via `user[:email]` instead of `user.send(:email)`
|
106
183
|
def object_for(key:)
|
107
184
|
@object.send(key) if @object.respond_to? key
|
108
185
|
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
# Checks if the child exists. If it does then it returns that. If it doesn't, it will
|
190
|
+
# build the child.
|
191
|
+
def create_child(key, child_class, **, &)
|
192
|
+
@children.fetch(key) { @children[key] = child_class.new(key, parent: self, **, &) }
|
193
|
+
end
|
109
194
|
end
|
110
195
|
|
111
196
|
class Field < Node
|
@@ -168,8 +253,8 @@ module Superform
|
|
168
253
|
|
169
254
|
private
|
170
255
|
|
171
|
-
def build_field(**
|
172
|
-
@field.class.new(@index += 1, parent: @field, **
|
256
|
+
def build_field(**)
|
257
|
+
@field.class.new(@index += 1, parent: @field, **)
|
173
258
|
end
|
174
259
|
end
|
175
260
|
|
@@ -208,8 +293,8 @@ module Superform
|
|
208
293
|
end
|
209
294
|
end
|
210
295
|
|
211
|
-
def build_namespace(index, **
|
212
|
-
parent.class.new(index, parent: self,
|
296
|
+
def build_namespace(index, **)
|
297
|
+
parent.class.new(index, parent: self, **, &@template)
|
213
298
|
end
|
214
299
|
|
215
300
|
def parent_collection
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: superform
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brad Gessler
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex-rails
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: zeitwerk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.6'
|
27
41
|
description: A better way to customize and build forms for your Rails application
|
28
42
|
email:
|
29
43
|
- bradgessler@gmail.com
|