Skip to content

Commit a1999bf

Browse files
keithrowelljonasjabari
authored andcommitted
first draft of nested forms doc
1 parent a45588d commit a1999bf

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Nested Forms
2+
3+
Matestack provides functionality for reactive nested forms.
4+
5+
This works in conjunction with rails' `accepts_nested_attributes_for`. From the rails documentation on [nested attributes](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html):
6+
7+
> Nested attributes allow you to save attributes on associated records through the parent. By default nested attribute updating is turned off and you can enable it using the accepts_nested_attributes_for class method. When you enable nested attributes an attribute writer is defined on the model.
8+
9+
There is a little bit of setup required to enable this. There's a need for `accepts_nested_attributes_for`, `index_errors` on a models' `has_many` associations and an ActiveRecord patch.
10+
11+
12+
Consider the following model setup, which is the same model found in the dummy app in the spec directory (active in this dummy app):
13+
14+
```ruby
15+
class DummyModel < ApplicationRecord
16+
validates :title, presence: true, uniqueness: true
17+
has_many :dummy_child_models, index_errors: true
18+
accepts_nested_attributes_for :dummy_child_models, allow_destroy: true
19+
end
20+
21+
class DummyChildModel < ApplicationRecord
22+
validates :title, presence: true, uniqueness: true
23+
end
24+
```
25+
26+
## Index Errors
27+
28+
Note the `has_many :dummy_child_models, index_errors: true` declaration in the `Dummy Model` declaration above.
29+
30+
Normally with rails, when rendering forms using Active Record models, errors are available on individual model instances. When using `accepts_nested_attributes_for`, error messages sent as JSON are not as useful because it is not possible to figure out which associated model object the error relates to.
31+
32+
From rails 5, we can add an index to errors on nested models. We can add the option `index_errors: true` to has_many association to enable this behaviour on individual association.
33+
34+
35+
## ActiveRecord Patch
36+
37+
Matestack nested forms support requires an ActiveRecord patch. This is because `index_errors` does not consider indexes of the correct existing sub records.
38+
39+
See rails [issue #24390](https://github.com/rails/rails/issues/24390)
40+
41+
Add this monkey patch to your rails app
42+
43+
```ruby
44+
module ActiveRecord
45+
module AutosaveAssociation
46+
def validate_collection_association(reflection)
47+
if association = association_instance_get(reflection.name)
48+
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
49+
all_records = association.target.find_all
50+
records.each do |record|
51+
index = all_records.find_index(record)
52+
association_valid?(reflection, record, index)
53+
end
54+
end
55+
end
56+
end
57+
end
58+
end
59+
```
60+
61+
## Example
62+
63+
```ruby
64+
class ExamplePage < Matestack::Ui::Page
65+
66+
def prepare
67+
@dummy_model = DummyModel.new
68+
@dummy_model.dummy_child_models.build(title: "init-value")
69+
@dummy_model.dummy_child_models.build
70+
end
71+
72+
def response
73+
matestack_form form_config do
74+
form_input key: :title, type: :text, label: "dummy_model_title_input", id: "dummy_model_title_input"
75+
76+
@dummy_model.dummy_child_models.each do |dummy_child_model|
77+
dummy_child_model_form dummy_child_model
78+
end
79+
80+
form_fields_for_add_item key: :dummy_child_models_attributes, prototype: method(:dummy_child_model_form) do
81+
# type: :button is important! otherwise remove on first item is triggered on enter
82+
button "add", type: :button
83+
end
84+
85+
form_fields_for_remove_item do
86+
# id is just required in this spec, but type: :button is important! otherwise remove on first item is triggered on enter
87+
button "remove", ":id": "'remove'+nestedFormRuntimeId", type: :button
88+
end
89+
90+
button "Submit me!"
91+
92+
toggle show_on: "success", hide_after: 1000 do
93+
plain "success!"
94+
end
95+
toggle show_on: "failure", hide_after: 1000 do
96+
plain "failure!"
97+
end
98+
end
99+
end
100+
101+
def dummy_child_model_form dummy_child_model = DummyChildModel.new
102+
form_fields_for dummy_child_model, key: :dummy_child_models_attributes do
103+
form_input key: :title, type: :text, label: "dummy-child-model-title-input"
104+
form_fields_for_remove_item do
105+
# id is just required in this spec, but type: :button is important! otherwise remove on first item is triggered on enter
106+
button "remove", ":id": "'remove'+nestedFormRuntimeId", type: :button
107+
end
108+
end
109+
end
110+
end
111+
```
112+
113+
### Dynamically Adding Nested Items
114+
115+
As in the example above, you can dynamically add nested items. As the comment in the code suggests `type: :button` is important, otherwise remove on first item is triggered on enter.
116+
117+
```ruby
118+
form_fields_for_add_item key: :dummy_child_models_attributes, prototype: method(:dummy_child_model_form) do
119+
# type: :button is important! otherwise remove on first item is triggered on enter
120+
button "add", type: :button
121+
end
122+
```
123+
124+
### Dynamically Removing Nested Items
125+
126+
As in the example above, as well as dynamically adding items, you can dynamically remove nested items. Again, important: `type: :button` is important, otherwise remove on first item is triggered on enter.
127+
128+
```ruby
129+
form_fields_for_remove_item do
130+
# id is just required in this spec, but type: :button is important! otherwise remove on first item is triggered on enter
131+
button "remove", ":id": "'remove'+nestedFormRuntimeId", type: :button
132+
end
133+
```

0 commit comments

Comments
 (0)