reform 2.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +11 -0
- data/CHANGES.md +415 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +339 -0
- data/Rakefile +15 -0
- data/TODO.md +45 -0
- data/gemfiles/Gemfile.disposable-0.3 +6 -0
- data/lib/reform.rb +8 -0
- data/lib/reform/contract.rb +77 -0
- data/lib/reform/contract/errors.rb +43 -0
- data/lib/reform/contract/validate.rb +33 -0
- data/lib/reform/form.rb +94 -0
- data/lib/reform/form/call.rb +23 -0
- data/lib/reform/form/coercion.rb +3 -0
- data/lib/reform/form/composition.rb +34 -0
- data/lib/reform/form/dry.rb +67 -0
- data/lib/reform/form/module.rb +27 -0
- data/lib/reform/form/mongoid.rb +37 -0
- data/lib/reform/form/orm.rb +26 -0
- data/lib/reform/form/populator.rb +123 -0
- data/lib/reform/form/prepopulate.rb +24 -0
- data/lib/reform/form/validate.rb +60 -0
- data/lib/reform/mongoid.rb +4 -0
- data/lib/reform/validation.rb +40 -0
- data/lib/reform/validation/groups.rb +73 -0
- data/lib/reform/version.rb +3 -0
- data/reform.gemspec +29 -0
- data/test/benchmarking.rb +26 -0
- data/test/call_test.rb +23 -0
- data/test/changed_test.rb +41 -0
- data/test/coercion_test.rb +66 -0
- data/test/composition_test.rb +149 -0
- data/test/contract_test.rb +77 -0
- data/test/default_test.rb +22 -0
- data/test/deprecation_test.rb +27 -0
- data/test/deserialize_test.rb +104 -0
- data/test/errors_test.rb +165 -0
- data/test/feature_test.rb +65 -0
- data/test/fixtures/dry_error_messages.yml +44 -0
- data/test/form_option_test.rb +24 -0
- data/test/form_test.rb +57 -0
- data/test/from_test.rb +75 -0
- data/test/inherit_test.rb +119 -0
- data/test/module_test.rb +142 -0
- data/test/parse_pipeline_test.rb +15 -0
- data/test/populate_test.rb +270 -0
- data/test/populator_skip_test.rb +28 -0
- data/test/prepopulator_test.rb +112 -0
- data/test/read_only_test.rb +3 -0
- data/test/readable_test.rb +30 -0
- data/test/readonly_test.rb +14 -0
- data/test/reform_test.rb +223 -0
- data/test/save_test.rb +89 -0
- data/test/setup_test.rb +48 -0
- data/test/skip_if_test.rb +74 -0
- data/test/skip_setter_and_getter_test.rb +54 -0
- data/test/test_helper.rb +49 -0
- data/test/validate_test.rb +420 -0
- data/test/validation/dry_test.rb +60 -0
- data/test/validation/dry_validation_test.rb +352 -0
- data/test/validation/errors.yml +4 -0
- data/test/virtual_test.rb +24 -0
- data/test/writeable_test.rb +29 -0
- metadata +265 -0
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
|
6
|
+
# gem "representable", "2.4.0.rc5"
|
7
|
+
# gem 'representable', path: "../representable"
|
8
|
+
# # gem 'representable', github: "apotonick/representable"
|
9
|
+
# gem "disposable", path: "../disposable"
|
10
|
+
# gem "disposable", github: "apotonick/disposable"
|
11
|
+
|
12
|
+
|
13
|
+
# gem "declarative", path: "../declarative"
|
14
|
+
|
15
|
+
gem "minitest-line"
|
16
|
+
gem 'byebug'
|
17
|
+
|
18
|
+
# gem "uber", path: "../uber"
|
19
|
+
gem "representable", ">= 3.0.1"
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 - 2014 Nick Sutterer
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,339 @@
|
|
1
|
+
# Reform
|
2
|
+
|
3
|
+
[![Gitter Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://gitter.im/trailblazer/chat)
|
4
|
+
[![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/)
|
5
|
+
[![Build
|
6
|
+
Status](https://travis-ci.org/apotonick/reform.svg)](https://travis-ci.org/apotonick/reform)
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/reform.svg)](http://badge.fury.io/rb/reform)
|
8
|
+
|
9
|
+
_Form objects decoupled from your models._
|
10
|
+
|
11
|
+
Reform gives you a form object with validations and nested setup of models. It is completely framework-agnostic and doesn't care about your database.
|
12
|
+
|
13
|
+
Although reform can be used in any Ruby framework, it comes with [Rails support](#rails-integration), works with [simple_form and other form gems](#formbuilder-support), allows nesting forms to implement [has_one](#nesting-forms-1-1-relations) and [has_many](#nesting-forms-1-n-relations) relationships, can [compose a form](#compositions) from multiple objects and gives you [coercion](#coercion).
|
14
|
+
|
15
|
+
## Full Documentation
|
16
|
+
|
17
|
+
Reform is part of the [Trailblazer](http://trailblazer.to) framework. [Full documentation](http://trailblazer.to/gems/reform) is available on the project site.
|
18
|
+
|
19
|
+
## Reform 2.2
|
20
|
+
|
21
|
+
Temporary note: Reform 2.2 does **not automatically load Rails files** anymore (e.g. `ActiveModel::Validations`). You need the `reform-rails` gem, see [Installation](#installation).
|
22
|
+
|
23
|
+
## Defining Forms
|
24
|
+
|
25
|
+
Forms are defined in separate classes. Often, these classes partially map to a model.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class AlbumForm < Reform::Form
|
29
|
+
property :title
|
30
|
+
validates :title, presence: true
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Fields are declared using `::property`. Validations work exactly as you know it from Rails or other frameworks. Note that validations no longer go into the model.
|
35
|
+
|
36
|
+
|
37
|
+
## The API
|
38
|
+
|
39
|
+
Forms have a ridiculously simple API with only a handful of public methods.
|
40
|
+
|
41
|
+
1. `#initialize` always requires a model that the form represents.
|
42
|
+
2. `#validate(params)` updates the form's fields with the input data (only the form, _not_ the model) and then runs all validations. The return value is the boolean result of the validations.
|
43
|
+
3. `#errors` returns validation messages in a classic ActiveModel style.
|
44
|
+
4. `#sync` writes form data back to the model. This will only use setter methods on the model(s).
|
45
|
+
5. `#save` (optional) will call `#save` on the model and nested models. Note that this implies a `#sync` call.
|
46
|
+
6. `#prepopulate!` (optional) will run pre-population hooks to "fill out" your form before rendering.
|
47
|
+
|
48
|
+
In addition to the main API, forms expose accessors to the defined properties. This is used for rendering or manual operations.
|
49
|
+
|
50
|
+
|
51
|
+
## Setup
|
52
|
+
|
53
|
+
In your controller or operation you create a form instance and pass in the models you want to work on.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class AlbumsController
|
57
|
+
def new
|
58
|
+
@form = AlbumForm.new(Album.new)
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
This will also work as an editing form with an existing album.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
def edit
|
66
|
+
@form = AlbumForm.new(Album.find(1))
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Reform will read property values from the model in setup. In our example, the `AlbumForm` will call `album.title` to populate the `title` field.
|
71
|
+
|
72
|
+
## Rendering Forms
|
73
|
+
|
74
|
+
Your `@form` is now ready to be rendered, either do it yourself or use something like Rails' `#form_for`, `simple_form` or `formtastic`.
|
75
|
+
|
76
|
+
```haml
|
77
|
+
= form_for @form do |f|
|
78
|
+
= f.input :title
|
79
|
+
```
|
80
|
+
|
81
|
+
Nested forms and collections can be easily rendered with `fields_for`, etc. Note that you no longer pass the model to the form builder, but the Reform instance.
|
82
|
+
|
83
|
+
Optionally, you might want to use the `#prepopulate!` method to pre-populate fields and prepare the form for rendering.
|
84
|
+
|
85
|
+
|
86
|
+
## Validation
|
87
|
+
|
88
|
+
After form submission, you need to validate the input.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class SongsController
|
92
|
+
def create
|
93
|
+
@form = SongForm.new(Song.new)
|
94
|
+
|
95
|
+
#=> params: {song: {title: "Rio", length: "366"}}
|
96
|
+
|
97
|
+
if @form.validate(params[:song])
|
98
|
+
```
|
99
|
+
|
100
|
+
The `#validate` method first updates the values of the form - the underlying model is still treated as immutuable and *remains unchanged*. It then runs all validations you provided in the form.
|
101
|
+
|
102
|
+
It's the only entry point for updating the form. This is per design, as separating writing and validation doesn't make sense for a form.
|
103
|
+
|
104
|
+
This allows rendering the form after `validate` with the data that has been submitted. However, don't get confused, the model's values are still the old, original values and are only changed after a `#save` or `#sync` operation.
|
105
|
+
|
106
|
+
|
107
|
+
## Syncing Back
|
108
|
+
|
109
|
+
After validation, you have two choices: either call `#save` and let Reform sort out the rest. Or call `#sync`, which will write all the properties back to the model. In a nested form, this works recursively, of course.
|
110
|
+
|
111
|
+
It's then up to you what to do with the updated models - they're still unsaved.
|
112
|
+
|
113
|
+
|
114
|
+
## Saving Forms
|
115
|
+
|
116
|
+
The easiest way to save the data is to call `#save` on the form.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
if @form.validate(params[:song])
|
120
|
+
@form.save #=> populates album with incoming data
|
121
|
+
# by calling @form.album.title=.
|
122
|
+
else
|
123
|
+
# handle validation errors.
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
This will sync the data to the model and then call `album.save`.
|
128
|
+
|
129
|
+
Sometimes, you need to do saving manually.
|
130
|
+
|
131
|
+
## Default values
|
132
|
+
|
133
|
+
Reform allows default values to be provided for properties.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class AlbumForm < Reform::Form
|
137
|
+
property :price_in_cents, default: 9_95
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
## Saving Forms Manually
|
142
|
+
|
143
|
+
Calling `#save` with a block will provide a nested hash of the form's properties and values. This does **not call `#save` on the models** and allows you to implement the saving yourself.
|
144
|
+
|
145
|
+
The block parameter is a nested hash of the form input.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
@form.save do |hash|
|
149
|
+
hash #=> {title: "Greatest Hits"}
|
150
|
+
Album.create(hash)
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
You can always access the form's model. This is helpful when you were using populators to set up objects when validating.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
@form.save do |hash|
|
158
|
+
album = @form.model
|
159
|
+
|
160
|
+
album.update_attributes(hash[:album])
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
|
165
|
+
## Nesting
|
166
|
+
|
167
|
+
Reform provides support for nested objects. Let's say the `Album` model keeps some associations.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
class Album < ActiveRecord::Base
|
171
|
+
has_one :artist
|
172
|
+
has_many :songs
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
The implementation details do not really matter here, as long as your album exposes readers and writes like `Album#artist` and `Album#songs`, this allows you to define nested forms.
|
177
|
+
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class AlbumForm < Reform::Form
|
181
|
+
property :title
|
182
|
+
validates :title, presence: true
|
183
|
+
|
184
|
+
property :artist do
|
185
|
+
property :full_name
|
186
|
+
validates :full_name, presence: true
|
187
|
+
end
|
188
|
+
|
189
|
+
collection :songs do
|
190
|
+
property :name
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
You can also reuse an existing form from elsewhere using `:form`.
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
property :artist, form: ArtistForm
|
199
|
+
```
|
200
|
+
|
201
|
+
## Nested Setup
|
202
|
+
|
203
|
+
Reform will wrap defined nested objects in their own forms. This happens automatically when instantiating the form.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
album.songs #=> [<Song name:"Run To The Hills">]
|
207
|
+
|
208
|
+
form = AlbumForm.new(album)
|
209
|
+
form.songs[0] #=> <SongForm model: <Song name:"Run To The Hills">>
|
210
|
+
form.songs[0].name #=> "Run To The Hills"
|
211
|
+
```
|
212
|
+
|
213
|
+
### Nested Rendering
|
214
|
+
|
215
|
+
When rendering a nested form you can use the form's readers to access the nested forms.
|
216
|
+
|
217
|
+
```haml
|
218
|
+
= text_field :title, @form.title
|
219
|
+
= text_field "artist[name]", @form.artist.name
|
220
|
+
```
|
221
|
+
|
222
|
+
Or use something like `#fields_for` in a Rails environment.
|
223
|
+
|
224
|
+
```haml
|
225
|
+
= form_for @form do |f|
|
226
|
+
= f.text_field :title
|
227
|
+
|
228
|
+
= f.fields_for :artist do |a|
|
229
|
+
= a.text_field :name
|
230
|
+
```
|
231
|
+
|
232
|
+
## Nested Processing
|
233
|
+
|
234
|
+
`validate` will assign values to the nested forms. `sync` and `save` work analogue to the non-nested form, just in a recursive way.
|
235
|
+
|
236
|
+
The block form of `#save` would give you the following data.
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
@form.save do |nested|
|
240
|
+
nested #=> {title: "Greatest Hits",
|
241
|
+
# artist: {name: "Duran Duran"},
|
242
|
+
# songs: [{title: "Hungry Like The Wolf"},
|
243
|
+
# {title: "Last Chance On The Stairways"}]
|
244
|
+
# }
|
245
|
+
end
|
246
|
+
```
|
247
|
+
|
248
|
+
The manual saving with block is not encouraged. You should rather check the Disposable docs to find out how to implement your manual tweak with the official API.
|
249
|
+
|
250
|
+
|
251
|
+
## Populating Forms
|
252
|
+
|
253
|
+
Very often, you need to give Reform some information how to create or find nested objects when `validate`ing. This directive is called _populator_ and [documented here](http://trailblazer.to/gems/reform/populator.html).
|
254
|
+
|
255
|
+
## Installation
|
256
|
+
|
257
|
+
Add this line to your Gemfile:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
gem "reform"
|
261
|
+
```
|
262
|
+
|
263
|
+
Reform works fine with Rails 3.1-5.0. However, inheritance of validations with `ActiveModel::Validations` is broken in Rails 3.2 and 4.0.
|
264
|
+
|
265
|
+
Since Reform 2.2, you have to add the `reform-rails` gem to your `Gemfile` to automatically load ActiveModel/Rails files.
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
gem "reform-rails"
|
269
|
+
```
|
270
|
+
|
271
|
+
Since Reform 2.0 you need to specify which **validation backend** you want to use (unless you're in a Rails environment where ActiveModel will be used).
|
272
|
+
|
273
|
+
To use ActiveModel (not recommended because very out-dated).
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
require "reform/form/active_model/validations"
|
277
|
+
Reform::Form.class_eval do
|
278
|
+
include Reform::Form::ActiveModel::Validations
|
279
|
+
end
|
280
|
+
```
|
281
|
+
|
282
|
+
To use dry-validation (recommended).
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
require "reform/form/dry"
|
286
|
+
Reform::Form.class_eval do
|
287
|
+
feature Reform::Form::Dry
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
Put this in an initializer or on top of your script.
|
292
|
+
|
293
|
+
|
294
|
+
## Compositions
|
295
|
+
|
296
|
+
Reform allows to map multiple models to one form. The [complete documentation](https://github.com/apotonick/disposable#composition) is here, however, this is how it works.
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
class AlbumTwin < Reform::Form
|
300
|
+
include Composition
|
301
|
+
|
302
|
+
property :id, on: :album
|
303
|
+
property :title, on: :album
|
304
|
+
property :songs, on: :cd
|
305
|
+
property :cd_id, on: :cd, from: :id
|
306
|
+
end
|
307
|
+
```
|
308
|
+
When initializing a composition, you have to pass a hash that contains the composees.
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
AlbumForm.new(album: album, cd: CD.find(1))
|
312
|
+
```
|
313
|
+
|
314
|
+
## More
|
315
|
+
|
316
|
+
Reform comes many more optional features, like hash fields, coercion, virtual fields, and so on. Check the [full documentation here](http://trailblazer.to/gems/reform).
|
317
|
+
|
318
|
+
<a href="https://leanpub.com/trailblazer">
|
319
|
+
![](http://trailblazer.to/images/3dbuch-freigestellt.png)
|
320
|
+
</a>
|
321
|
+
|
322
|
+
Reform is part of the [Trailblazer project](http://trailblazer.to). Please [buy my book](https://leanpub.com/trailblazer) to support the development and learn everything about Reform - there's two chapters dedicated to Reform!
|
323
|
+
|
324
|
+
|
325
|
+
## Security And Strong_parameters
|
326
|
+
|
327
|
+
By explicitely defining the form layout using `::property` there is no more need for protecting from unwanted input. `strong_parameter` or `attr_accessible` become obsolete. Reform will simply ignore undefined incoming parameters.
|
328
|
+
|
329
|
+
## This is not Reform 1.x!
|
330
|
+
|
331
|
+
Temporary note: This is the README and API for Reform 2. On the public API, only a few tiny things have changed. Here are the [Reform 1.2 docs](https://github.com/apotonick/reform/blob/v1.2.6/README.md).
|
332
|
+
|
333
|
+
Anyway, please upgrade and _report problems_ and do not simply assume that we will magically find out what needs to get fixed. When in trouble, join us on [Gitter](https://gitter.im/trailblazer/chat).
|
334
|
+
|
335
|
+
[Full documentation for Reform](http://trailblazer.to/gems/reform) is available online, or support us and grab the [Trailblazer book](https://leanpub.com/trailblazer). There is an [Upgrading Guide](http://trailblazer.to/gems/reform/upgrading-guide.html) to help you migrate from Reform 1.x.
|
336
|
+
|
337
|
+
### Attributions!!!
|
338
|
+
|
339
|
+
Great thanks to [Blake Education](https://github.com/blake-education) for giving us the freedom and time to develop this project in 2013 while working on their project.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => [:test]
|
5
|
+
Rake::TestTask.new(:test) do |test|
|
6
|
+
test.libs << 'test'
|
7
|
+
test.test_files = FileList['test/*_test.rb'] + FileList["test/validation/*_test.rb"]
|
8
|
+
test.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
Rake::TestTask.new(:test_rails) do |test|
|
12
|
+
test.libs << 'test'
|
13
|
+
test.test_files = FileList['test/rails/*_test.rb']
|
14
|
+
test.verbose = true
|
15
|
+
end
|
data/TODO.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# 2.0
|
2
|
+
|
3
|
+
* make Coercible optional (include it to activate)
|
4
|
+
* all options Uber:::Value with :method support
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
# NOTES
|
9
|
+
* use the same test setup everywhere (album -> songs -> composer)
|
10
|
+
* copy things in tests
|
11
|
+
* one test file per "feature": sync_test, sync_option_test.
|
12
|
+
|
13
|
+
* fields is a Twin and sorts out all the changed? stuff.
|
14
|
+
* virtual: don't read dont write
|
15
|
+
* empty dont read, but write
|
16
|
+
* read_only: read, don't write
|
17
|
+
|
18
|
+
* make SkipUnchanged default?
|
19
|
+
|
20
|
+
|
21
|
+
* `validates :title, :presence => true`
|
22
|
+
with @model.title == "Little Green Car" and validate({}) the form is still valid (as we "have" a valid title). is that what we want?
|
23
|
+
|
24
|
+
* document Form#to_hash and Form#to_nested_hash (e.g. with OpenStruct composition to make it a very simple form)
|
25
|
+
* document getter: and representer_exec:
|
26
|
+
|
27
|
+
* Debug module that logs every step.
|
28
|
+
* no setters in Contract#setup
|
29
|
+
|
30
|
+
vererben in inline representern (module zum einmixen, attrs löschen)
|
31
|
+
|
32
|
+
# TODO: remove the concept of Errors#messages and just iterate over Errors.
|
33
|
+
# each form contains its local field errors in Errors
|
34
|
+
# form.messages should then go through them and compile a "summary" instead of adding them to the parents #errors in #validate.
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
in a perfect world, a UI form would send JSON as in the API. that's why the reform form creates the correct object graph first, then validates. creating the graph usually happens in the API representer code.
|
39
|
+
|
40
|
+
|
41
|
+
WHY DON'T PEOPLE USE THIS:
|
42
|
+
http://guides.rubyonrails.org/association_basics.html#the-has-many-association
|
43
|
+
4.2.2.2 :autosave
|
44
|
+
|
45
|
+
If you set the :autosave option to true, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.
|