reform 1.2.6 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -1
  3. data/CHANGES.md +14 -0
  4. data/Gemfile +3 -2
  5. data/README.md +225 -283
  6. data/Rakefile +27 -0
  7. data/TODO.md +12 -0
  8. data/database.sqlite3 +0 -0
  9. data/gemfiles/Gemfile.rails-3.0 +1 -0
  10. data/gemfiles/Gemfile.rails-3.1 +1 -0
  11. data/gemfiles/Gemfile.rails-3.2 +1 -0
  12. data/gemfiles/Gemfile.rails-4.0 +1 -0
  13. data/lib/reform.rb +0 -1
  14. data/lib/reform/contract.rb +64 -170
  15. data/lib/reform/contract/validate.rb +10 -13
  16. data/lib/reform/form.rb +74 -19
  17. data/lib/reform/form/active_model.rb +19 -14
  18. data/lib/reform/form/coercion.rb +1 -13
  19. data/lib/reform/form/composition.rb +2 -24
  20. data/lib/reform/form/multi_parameter_attributes.rb +43 -62
  21. data/lib/reform/form/populator.rb +85 -0
  22. data/lib/reform/form/prepopulate.rb +13 -43
  23. data/lib/reform/form/validate.rb +29 -90
  24. data/lib/reform/form/validation/unique_validator.rb +13 -0
  25. data/lib/reform/version.rb +1 -1
  26. data/reform.gemspec +7 -7
  27. data/test/active_model_test.rb +43 -0
  28. data/test/changed_test.rb +23 -51
  29. data/test/coercion_test.rb +1 -7
  30. data/test/composition_test.rb +128 -34
  31. data/test/contract_test.rb +27 -86
  32. data/test/feature_test.rb +43 -6
  33. data/test/fields_test.rb +2 -12
  34. data/test/form_builder_test.rb +28 -25
  35. data/test/form_option_test.rb +19 -0
  36. data/test/from_test.rb +0 -75
  37. data/test/inherit_test.rb +178 -117
  38. data/test/model_reflections_test.rb +1 -1
  39. data/test/populate_test.rb +226 -0
  40. data/test/prepopulator_test.rb +112 -0
  41. data/test/readable_test.rb +2 -4
  42. data/test/save_test.rb +56 -112
  43. data/test/setup_test.rb +48 -0
  44. data/test/skip_if_test.rb +5 -2
  45. data/test/skip_setter_and_getter_test.rb +54 -0
  46. data/test/test_helper.rb +3 -1
  47. data/test/uniqueness_test.rb +41 -0
  48. data/test/validate_test.rb +325 -289
  49. data/test/virtual_test.rb +1 -3
  50. data/test/writeable_test.rb +3 -4
  51. metadata +35 -39
  52. data/lib/reform/composition.rb +0 -63
  53. data/lib/reform/contract/setup.rb +0 -50
  54. data/lib/reform/form/changed.rb +0 -9
  55. data/lib/reform/form/sync.rb +0 -116
  56. data/lib/reform/representer.rb +0 -84
  57. data/test/empty_test.rb +0 -58
  58. data/test/form_composition_test.rb +0 -145
  59. data/test/nested_form_test.rb +0 -197
  60. data/test/prepopulate_test.rb +0 -85
  61. data/test/sync_option_test.rb +0 -83
  62. data/test/sync_test.rb +0 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6ad4504340052787a18f70b1249517dfd7ccc443
4
- data.tar.gz: 7bae41c59ff9cc4b242a0eb0855f7fa382c2e4ac
3
+ metadata.gz: 719c0d3aec98a161756c38012b620944cf19fa22
4
+ data.tar.gz: f7adb9cb663ea890809b3606a3ab01376fe4453d
5
5
  SHA512:
6
- metadata.gz: b11cfe1184db4d697a87397acaaa2791ff8c8bdffd89682116aa17e6b91d51c0c5b3de44c4e923266fcf8db9cc994c72c49312210c092d8c54e0bf0d62301e2d
7
- data.tar.gz: 42823db698bc87293a992987df02305ce8069459e707847cafb686c5477798a79a9f1b61c27825d8e5bb254bb6e58b005830b481ab64855312b5999d60e5d7c9
6
+ metadata.gz: 78d3c8711d9a337ca128e6e0cf14ebfe4df94230c4e1180110bd419ef7d4d0951be2a5f3823be807e2daf68f483a02f149e8bea212bfcac7d59613a115fbabc3
7
+ data.tar.gz: 9434d975625b3719bcd47a1a31da9be8feda733c3af43a78237fdbe1fa5e90805cefb88e9e707ec4c9f050a2615a1626ae345477076d1b235c9107f9c388f8cd
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.4
3
+ - 2.2.0
4
+ - 2.1.5
4
5
  - 2.0.0
5
6
  - 1.9.3
6
7
  gemfile:
@@ -8,3 +9,7 @@ gemfile:
8
9
  - gemfiles/Gemfile.rails-3.2
9
10
  - gemfiles/Gemfile.rails-3.1
10
11
  - gemfiles/Gemfile.rails-3.0
12
+ matrix:
13
+ fast_finish: true
14
+ allow_failures:
15
+ - rvm: ruby-head
data/CHANGES.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 2.0.0
2
+
3
+ * The `::reform_2_0!` is no longer there. Guess why.
4
+ * :populator => lambda { |fragment, index, args|
5
+ # songs[index] or songs[index] = args.binding[:form].new(Song.new)
6
+ # }
7
+ is now :populator => lambda { |fragment, index, args|
8
+ # songs[index] or songs.insert(index) = Song.new
9
+ # }
10
+ you don't need to know about forms anymore, the twin handles that using #insert.
11
+
12
+ * `:as` option removed. Use `:from`.
13
+ * With `Composition` included, `Form#model` would give you a composition object. You can grab that using `Form#mapper` now.
14
+
1
15
  ## 1.2.6
2
16
 
3
17
  * Added `:prepopulate` to fill out form properties for presentation. Note that you need to call `Form#prepopulate!` to trigger the prepopulation.
data/Gemfile CHANGED
@@ -2,5 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- # gem 'representable', path: "../representable"
6
- # gem "disposable", path: "../disposable"
5
+ #gem 'representable', path: "../representable"
6
+ gem "disposable", path: "../disposable"
7
+ # gem "disposable", github: "apotonick/disposable"
data/README.md CHANGED
@@ -1,73 +1,34 @@
1
1
  # Reform
2
2
 
3
- Decouple your models from forms. 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.
3
+ _Form objects decoupled from your models._
4
4
 
5
- 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).
6
-
7
- <a href="https://leanpub.com/trailblazer">
8
- ![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
9
- </a>
10
-
11
- Reform is part of the [Trailblazer project](https://github.com/apotonick/trailblazer). Please [buy my book](https://leanpub.com/trailblazer) to support the development and learn everything about Reform. Currently the book discusses:
12
-
13
- * Form objects, the DSL and basic API (chapter 2 and 3)
14
- * Basic validations and rendering forms (chapter 3)
15
- * Nested forms, prepopulating and validation populating and pre-selecting values (chapter 5)
5
+ 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.
16
6
 
17
- More chapters are coming!
7
+ 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).
18
8
 
9
+ ## This is not Reform 1.x!
19
10
 
20
- ## Installation
11
+ Temporary note: This is the README and API for Reform 2. On the public API, only a few tiny things have changed. When in trouble, join us on the IRC (Freenode) #trailblazer channel.
21
12
 
22
- Add this line to your Gemfile:
23
13
 
24
- ```ruby
25
- gem 'reform'
26
- ```
14
+ ## Disposable
27
15
 
28
- ## Nomenclature
29
-
30
- Reform comes with two base classes.
31
-
32
- * `Form` is what made you come here - it gives you a form class to handle all validations, wrap models, allow rendering with Rails form helpers, simplifies saving of models, and more.
33
- * `Contract` gives you a sub-set of `Form`: [this class](#contracts) is meant for API validation where already populated models get validated without having to maintain validations in the model classes.
16
+ Every form in Reform is a _twin_. Twins are non-persistent domain objects from the [Disposable gem](https://github.com/apotonick/disposable). All features of Disposable, like renaming fields, change tracking, etc. are available in Reform, too.
34
17
 
35
18
 
36
19
  ## Defining Forms
37
20
 
38
- You're working at a famous record label and your job is archiving all the songs, albums and artists. You start with a form to populate your `songs` table.
21
+ Forms are defined in separate classes. Often, these classes partially map to a model.
39
22
 
40
23
  ```ruby
41
- class SongForm < Reform::Form
24
+ class AlbumForm < Reform::Form
42
25
  property :title
43
- property :length
44
-
45
26
  validates :title, presence: true
46
- validates :length, numericality: true
47
27
  end
48
28
  ```
49
29
 
50
- Define your form's fields using `::property`. Validations no longer go into the model, but into the form.
30
+ Fields are declared using `::property`. Validations work exactly as you know it from Rails or other frameworks. Note that validations no longer got into the model.
51
31
 
52
- Luckily, this can be shortened as follows.
53
-
54
- ```ruby
55
- class SongForm < Reform::Form
56
- property :title, validates: {presence: true}
57
- property :length, validates: {numericality: true}
58
- end
59
- ```
60
-
61
- Use `properties` to bulk-specify fields.
62
-
63
- ```ruby
64
- class SongForm < Reform::Form
65
- properties :title, :length, validates: {presence: true} # both required!
66
- validates :length, numericality: true
67
- end
68
- ```
69
-
70
- After explicitely defining your fields, you're ready to use the form.
71
32
 
72
33
  ## The API
73
34
 
@@ -75,50 +36,34 @@ Forms have a ridiculously simple API with only a handful of public methods.
75
36
 
76
37
  1. `#initialize` always requires a model that the form represents.
77
38
  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.
78
- 3. `#errors` returns validation messages in a classy ActiveModel style.
39
+ 3. `#errors` returns validation messages in a classic ActiveModel style.
79
40
  4. `#sync` writes form data back to the model. This will only use setter methods on the model(s).
80
41
  5. `#save` (optional) will call `#save` on the model and nested models. Note that this implies a `#sync` call.
42
+ 6. `#present!` (optional) will run pre-population hooks to "fill out" your form before rendering.
81
43
 
82
44
  In addition to the main API, forms expose accessors to the defined properties. This is used for rendering or manual operations.
83
45
 
84
46
 
85
47
  ## Setup
86
48
 
87
- In your controller you'd create a form instance and pass in the models you want to work on.
49
+ In your controller or operation you create a form instance and pass in the models you want to work on.
88
50
 
89
51
  ```ruby
90
- class SongsController
52
+ class AlbumsController
91
53
  def new
92
- @form = SongForm.new(Song.new)
93
- end
94
- ```
95
-
96
- You can also setup the form for editing existing items.
97
-
98
- ```ruby
99
- class SongsController
100
- def edit
101
- @form = SongForm.new(Song.find(1))
54
+ @form = AlbumForm.new(Album.new)
102
55
  end
103
56
  ```
104
57
 
105
- Reform will read property values from the model in setup. Given the following form class.
58
+ This will also work as an editing form with an existing album.
106
59
 
107
60
  ```ruby
108
- class SongForm < Reform::Form
109
- property :title
110
- ```
111
-
112
- Internally, this form will call `song.title` to populate the title field.
113
-
114
- If you, for whatever reasons, want to use a different public name, use `:from`.
115
-
116
- ```ruby
117
- class SongForm < Reform::Form
118
- property :name, from: :title
61
+ def edit
62
+ @form = AlbumForm.new(Album.find(1))
63
+ end
119
64
  ```
120
65
 
121
- This will still call `song.title` but expose the attribute as `name`.
66
+ Reform will read property values from the model in setup. In our example, the `AlbumForm` will call `album.title` to populate the `title` field.
122
67
 
123
68
  ## Rendering Forms
124
69
 
@@ -126,18 +71,17 @@ Your `@form` is now ready to be rendered, either do it yourself or use something
126
71
 
127
72
  ```haml
128
73
  = form_for @form do |f|
129
-
130
- = f.input :name
131
74
  = f.input :title
132
75
  ```
133
76
 
134
- Nested forms and collections can be easily rendered with `fields_for`, etc. Just use Reform as if it would be an ActiveModel instance in the view layer.
77
+ 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.
78
+
79
+ Optionally, you might want to use the [#present!](#present) method to pre-populate fields and prepare the form for rendering.
135
80
 
136
- Note that you have a mechanism to [prepopulate forms](#prepopulating-forms) for rendering.
137
81
 
138
82
  ## Validation
139
83
 
140
- After a form submission, you want to validate the input.
84
+ After form submission, you need to validate the input.
141
85
 
142
86
  ```ruby
143
87
  class SongsController
@@ -168,145 +112,89 @@ It's then up to you what to do with the updated models - they're still unsaved.
168
112
  The easiest way to save the data is to call `#save` on the form.
169
113
 
170
114
  ```ruby
171
- @form.save #=> populates song with incoming data
172
- # by calling @form.song.title= and @form.song.length=.
115
+ @form.save #=> populates album with incoming data
116
+ # by calling @form.album.title=.
173
117
  ```
174
118
 
175
- This will sync the data to the model and then call `song.save`.
176
-
177
- Sometimes, you need to do stuff manually.
119
+ This will sync the data to the model and then call `album.save`.
178
120
 
121
+ Sometimes, you need to do saving manually.
179
122
 
180
123
  ## Saving Forms Manually
181
124
 
182
- Calling `#save` with a block doesn't do anything but providing you a nested hash with all the validated input. This allows you to implement the saving yourself.
125
+ Calling `#save` with a block doesn't do anything but provide you with a nested hash with all the validated input. This allows you to implement the saving yourself.
183
126
 
184
127
  The block parameter is a nested hash of the form input.
185
128
 
186
129
  ```ruby
187
130
  @form.save do |hash|
188
- hash #=> {title: "Rio", length: "366"}
189
-
190
- Song.create(hash)
131
+ hash #=> {title: "Greatest Hits"}
132
+ Album.create(hash)
191
133
  end
192
134
  ```
193
135
 
194
136
  You can always access the form's model. This is helpful when you were using populators to set up objects when validating.
195
137
 
196
138
  ```ruby
197
- @form.save do |nested|
139
+ @form.save do |hash|
198
140
  album = @form.model
199
141
 
200
- album.update_attributes(nested[:album])
201
- end
202
- ```
203
-
204
- If the form wraps multiple models, via [composition](#compositions), you can access them like this:
205
-
206
- ```ruby
207
- @form.save do |nested|
208
- song = @form.model[:song]
209
- label = @form.model[:label]
210
- end
211
- ```
212
-
213
- Note that you can call `#sync` and _then_ call `#save { |hsh| }` to save models yourself.
214
-
215
-
216
- ## Contracts
217
-
218
- Contracts give you a sub-set of the `Form` API.
219
-
220
- 1. `#initialize` accepts an already populated model.
221
- 2. `#validate` will run defined validations (without accepting a params hash as in `Form`).
222
-
223
- Contracts can be used to completely remove validation logic from your model classes. Validation should happen in a separate layer - a `Contract`.
224
-
225
- ### Defining Contracts
226
-
227
- A contract looks like a form.
228
-
229
- ```ruby
230
- class AlbumContract < Reform::Contract
231
- property :title
232
- validates :title, length: {minimum: 9}
233
-
234
- collection :songs do
235
- property :title
236
- validates :title, presence: true
142
+ album.update_attributes(hash[:album])
237
143
  end
238
144
  ```
239
145
 
240
- It defines the validations and the object graph to be inspected.
241
146
 
242
- In future versions and with the upcoming [Trailblazer framework](https://github.com/apotonick/trailblazer), contracts can be inherited from forms, representers, and cells, and vice-versa. Actually this already works with representer inheritance - let me know if you need help.
147
+ ## Nesting
243
148
 
244
- ### Using Contracts
245
-
246
- Applying a contract is simple, all you need is a populated object (e.g. an album after `#assign_attributes`).
149
+ Reform provides support for nested objects. Let's say the `Album` model keeps some associations.
247
150
 
248
151
  ```ruby
249
- album.assign_attributes(..)
250
-
251
- contract = AlbumContract.new(album)
252
-
253
- if contract.validate
254
- album.save
255
- else
256
- raise contract.errors.messages.inspect
152
+ class Album < ActiveRecord::Base
153
+ has_one :artist
154
+ has_many :songs
257
155
  end
258
156
  ```
259
157
 
260
- Contracts help you to make your data layer a dumb persistance tier. My [upcoming book discusses that in detail](http://nicksda.apotomo.de).
261
-
262
-
263
- ## Nesting Forms: 1-1 Relations
264
-
265
- Songs have artists to compose them. Let's say your `Song` model would implement that as follows.
266
-
267
- ```ruby
268
- class Song < ActiveRecord::Base
269
- has_one :artist
270
- end
271
- ```
158
+ 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.
272
159
 
273
- The edit form should allow changing data for artist and song.
274
160
 
275
161
  ```ruby
276
- class SongForm < Reform::Form
162
+ class AlbumForm < Reform::Form
277
163
  property :title
278
- property :length
164
+ validates :title, presence: true
279
165
 
280
166
  property :artist do
281
- property :name
282
-
283
- validates :name, presence: true
167
+ property :full_name
168
+ validates :full_name, presence: true
284
169
  end
285
170
 
286
- #validates :title, ...
171
+ collection :songs do
172
+ property :name
173
+ end
287
174
  end
288
175
  ```
289
176
 
290
- See how simple nesting forms is? By passing a block to `::property` you can define another form nested into your main form.
177
+ You can also reuse an existing form from elsewhere using `:form`.
291
178
 
179
+ ```ruby
180
+ property :artist, form: ArtistForm
181
+ ```
292
182
 
293
- ### has_one: Setup
183
+ ## Nested Setup
294
184
 
295
- This setup's only requirement is having a working `Song#artist` reader.
185
+ Reform will wrap defined nested objects in their own forms. This happens automatically when instantiating the form.
296
186
 
297
187
  ```ruby
298
- class SongsController
299
- def edit
300
- song = Song.find(1)
301
- song.artist #=> <0x999#Artist title="Duran Duran">
188
+ album.songs #=> [<Song name:"Run To The Hills">]
302
189
 
303
- @form = SongForm.new(song)
304
- end
190
+ form = AlbumForm.new(album)
191
+ form.songs[0] #=> <SongForm model: <Song name:"Run To The Hills">>
192
+ form.songs[0].name #=> "Run To The Hills"
305
193
  ```
306
194
 
307
- ### has_one: Rendering
195
+ ### Nested Rendering
308
196
 
309
- When rendering this form you could use the form's accessors manually.
197
+ When rendering a nested form you can use the form's readers to access the nested forms.
310
198
 
311
199
  ```haml
312
200
  = text_field :title, @form.title
@@ -318,155 +206,238 @@ Or use something like `#fields_for` in a Rails environment.
318
206
  ```haml
319
207
  = form_for @form do |f|
320
208
  = f.text_field :title
321
- = f.text_field :length
322
209
 
323
210
  = f.fields_for :artist do |a|
324
211
  = a.text_field :name
325
212
  ```
326
213
 
327
- ### has_one: Processing
214
+ ## Nested Processing
215
+
216
+ `validate` will assign values to the nested forms. `sync` and `save` work analogue to the non-nested form, just in a recursive way.
328
217
 
329
218
  The block form of `#save` would give you the following data.
330
219
 
331
220
  ```ruby
332
221
  @form.save do |nested|
333
-
334
- nested #=> {title: "Hungry Like The Wolf",
335
- # artist: {name: "Duran Duran"}}
336
- end
222
+ nested #=> {title: "Greatest Hits",
223
+ # artist: {name: "Duran Duran"},
224
+ # songs: [{title: "Hungry Like The Wolf"},
225
+ # {title: "Last Chance On The Stairways"}]
226
+ # }
227
+ end
337
228
  ```
338
229
 
339
- Supposed you use reform's automatic save without a block, the following assignments would be made.
230
+ 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.
231
+
232
+
233
+ ## Populating Forms for Validation
234
+
235
+ This topic is thorougly covered in the [Trailblazer book](https://leanpub.com/trailblazer) in chapters _Nested Forms_ and _Mastering Forms_.
236
+
237
+ With a complex nested setup it can sometimes be painful to setup the model object graph.
238
+
239
+ Let's assume you rendered the following form.
340
240
 
341
241
  ```ruby
342
- form.song.title = "Hungry Like The Wolf"
343
- form.song.artist.name = "Duran Duran"
344
- form.song.save
242
+ @form = AlbumForm.new(Album.new(songs: [Song.new, Song.new]))
345
243
  ```
346
244
 
347
- ## Nesting Forms: 1-n Relations
245
+ This will render two nested forms to create new songs.
348
246
 
349
- Reform also gives you nested collections.
247
+ In `validate`, you're supposed to setup the very same object graph, again. Reform has no way of remembering what the object setup was like a request ago.
350
248
 
351
- Let's have Albums with songs!
249
+ So, the following code will fail.
352
250
 
353
251
  ```ruby
354
- class Album < ActiveRecord::Base
355
- has_many :songs
356
- end
252
+ @form = AlbumForm.new(Album.new).validate(params[:album])
357
253
  ```
358
254
 
359
- The form might look like this.
255
+ However, you can advise Reform to setup the correct objects for you.
360
256
 
361
257
  ```ruby
362
258
  class AlbumForm < Reform::Form
363
- property :title
259
+ collection :songs, populate_if_empty: Song do
260
+ # ..
261
+ end
262
+ ```
364
263
 
365
- collection :songs do
366
- property :title
264
+ This works for both `property` and `collection` and instantiates `Song` objects where they're missing when calling `#validate`.
367
265
 
368
- validates :title, presence: true
266
+ If you want to create the objects yourself, because you're smarter than Reform, do it with a lambda.
267
+
268
+ ```ruby
269
+ class AlbumForm < Reform::Form
270
+ collection :songs, populate_if_empty: lambda { |fragment, args| Song.new } do
271
+ # ..
369
272
  end
370
- end
371
273
  ```
372
274
 
373
- This basically works like a nested `property` that iterates over a collection of songs.
275
+ Reform also allows to completely override population using the `:populator` options. This is [documented here](http://trailblazerb.org/gems/reform/populators.html), and also in the Trailblazer book.
374
276
 
375
- ### has_many: Rendering
277
+ ## Installation
376
278
 
377
- Reform will expose the collection using the `#songs` method.
279
+ Add this line to your Gemfile:
378
280
 
379
- ```haml
380
- = text_field :title, @form.title
381
- = text_field "songs[0][title]", @form.songs[0].title
281
+ ```ruby
282
+ gem 'reform'
382
283
  ```
383
284
 
384
- However, `#fields_for` works just fine, again.
385
285
 
386
- ```haml
387
- = form_for @form do |f|
388
- = f.text_field :title
286
+ ## Compositions
389
287
 
390
- = f.fields_for :songs do |s|
391
- = s.text_field :title
392
- ```
288
+ 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.
393
289
 
394
- ### has_many: Processing
290
+ ```ruby
291
+ class AlbumTwin < Reform::Form
292
+ include Composition
395
293
 
396
- The block form of `#save` will expose the data structures already discussed.
294
+ property :id, on: :album
295
+ property :title, on: :album
296
+ property :songs, on: :cd
297
+ property :cd_id, on: :cd, from: :id
298
+ end
299
+ ```
300
+ When initializing a composition, you have to pass a hash that contains the composees.
397
301
 
398
302
  ```ruby
399
- @form.save do |nested|
303
+ AlbumForm.new(album: album, cd: CD.find(1))
304
+ ```
305
+
306
+ => rendering
307
+ => sync with block
308
+
309
+ ## Hash Fields
310
+
311
+ Reform can also handle deeply nested hash fields from serialized hash columns. This is [documented here](https://github.com/apotonick/disposable#struct).
312
+
313
+ => Example
314
+
315
+ <a href="https://leanpub.com/trailblazer">
316
+ ![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
317
+ </a>
318
+
319
+ Reform is part of the [Trailblazer project](https://github.com/apotonick/trailblazer). Please [buy my book](https://leanpub.com/trailblazer) to support the development and learn everything about Reform. Currently the book discusses:
320
+
321
+ * Form objects, the DSL and basic API (chapter 2 and 3)
322
+ * Basic validations and rendering forms (chapter 3)
323
+ * Nested forms, prepopulating and validation populating and pre-selecting values (chapter 5)
324
+
325
+ More chapters are coming!
326
+
327
+
328
+
400
329
 
401
- nested #=> {title: "Rio"
402
- # songs: [{title: "Hungry Like The Wolf"},
403
- # {title: "Last Chance On The Stairways"}]
330
+ ## Nomenclature
331
+
332
+ Reform comes with two base classes.
333
+
334
+ * `Form` is what made you come here - it gives you a form class to handle all validations, wrap models, allow rendering with Rails form helpers, simplifies saving of models, and more.
335
+ * `Contract` gives you a sub-set of `Form`: [this class](#contracts) is meant for API validation where already populated models get validated without having to maintain validations in the model classes.
336
+
337
+
338
+
339
+ Luckily, this can be shortened as follows.
340
+
341
+ ```ruby
342
+ class SongForm < Reform::Form
343
+ property :title, validates: {presence: true}
344
+ property :length, validates: {numericality: true}
404
345
  end
405
346
  ```
406
347
 
348
+ Use `properties` to bulk-specify fields.
349
+
350
+ ```ruby
351
+ class SongForm < Reform::Form
352
+ properties :title, :length, validates: {presence: true} # both required!
353
+ validates :length, numericality: true
354
+ end
355
+ ```
407
356
 
408
- ## Nesting Configuration
409
357
 
410
- ### Turning Off Autosave
411
358
 
412
- You can assign Reform to _not_ call `save` on a particular nested model (per default, it is called automatically on all nested models).
359
+ If the form wraps multiple models, via [composition](#compositions), you can access them like this:
413
360
 
414
361
  ```ruby
415
- class AlbumForm < Reform::Form
416
- # ...
417
-
418
- collection :songs, save: false do
419
- # ..
362
+ @form.save do |nested|
363
+ song = @form.model[:song]
364
+ label = @form.model[:label]
420
365
  end
421
366
  ```
422
367
 
423
- The `:save` options set to false won't save models.
368
+ Note that you can call `#sync` and _then_ call `#save { |hsh| }` to save models yourself.
424
369
 
425
370
 
426
- ### Populating Forms For Validation
371
+ ## Contracts
427
372
 
428
- With a complex nested setup it can sometimes be painful to setup the model object graph.
373
+ Contracts give you a sub-set of the `Form` API.
429
374
 
430
- Let's assume you rendered the following form.
375
+ 1. `#initialize` accepts an already populated model.
376
+ 2. `#validate` will run defined validations (without accepting a params hash as in `Form`).
377
+
378
+ Contracts can be used to completely remove validation logic from your model classes. Validation should happen in a separate layer - a `Contract`.
379
+
380
+ ### Defining Contracts
381
+
382
+ A contract looks like a form.
431
383
 
432
384
  ```ruby
433
- @form = AlbumForm.new(Album.new(songs: [Song.new, Song.new]))
385
+ class AlbumContract < Reform::Contract
386
+ property :title
387
+ validates :title, length: {minimum: 9}
388
+
389
+ collection :songs do
390
+ property :title
391
+ validates :title, presence: true
392
+ end
434
393
  ```
435
394
 
436
- This will render two nested forms to create new songs.
395
+ It defines the validations and the object graph to be inspected.
437
396
 
438
- When **validating**, you're supposed to setup the very same object graph, again. Reform has no way of remembering what the object setup was like a request ago.
397
+ In future versions and with the upcoming [Trailblazer framework](https://github.com/apotonick/trailblazer), contracts can be inherited from forms, representers, and cells, and vice-versa. Actually this already works with representer inheritance - let me know if you need help.
439
398
 
440
- So, the following code will fail.
399
+ ### Using Contracts
400
+
401
+ Applying a contract is simple, all you need is a populated object (e.g. an album after `#assign_attributes`).
441
402
 
442
403
  ```ruby
443
- @form = AlbumForm.new(Album.new).validate(params[:album])
444
- ```
404
+ album.assign_attributes(..)
445
405
 
446
- However, you can advise Reform to setup the correct objects for you.
406
+ contract = AlbumContract.new(album)
447
407
 
448
- ```ruby
449
- class AlbumForm < Reform::Form
450
- # ...
408
+ if contract.validate
409
+ album.save
410
+ else
411
+ raise contract.errors.messages.inspect
412
+ end
413
+ ```
414
+
415
+ Contracts help you to make your data layer a dumb persistance tier. My [upcoming book discusses that in detail](http://nicksda.apotomo.de).
451
416
 
452
- collection :songs, populate_if_empty: Song do
453
- # ..
454
- end
455
417
  ```
456
418
 
457
- This works for both `property` and `collection` and instantiates `Song` objects where they're missing when calling `#validate`.
419
+ This basically works like a nested `property` that iterates over a collection of songs.
420
+
458
421
 
459
- If you want to create the objects yourself, because you're smarter than Reform, do it with a lambda.
422
+
423
+ ### Turning Off Autosave
424
+
425
+ You can assign Reform to _not_ call `save` on a particular nested model (per default, it is called automatically on all nested models).
460
426
 
461
427
  ```ruby
462
428
  class AlbumForm < Reform::Form
463
429
  # ...
464
430
 
465
- collection :songs, populate_if_empty: lambda { |fragment, args| Song.new } do
431
+ collection :songs, save: false do
466
432
  # ..
467
433
  end
468
434
  ```
469
435
 
436
+ The `:save` options set to false won't save models.
437
+
438
+
439
+
440
+
470
441
 
471
442
  ## Compositions
472
443
 
@@ -516,7 +487,7 @@ After you configured your composition in the form, reform hides the fact that yo
516
487
 
517
488
  When using `#save' without a block reform will use writer methods on the different objects to push validated data to the properties.
518
489
 
519
- Here's how the block parameters look like.
490
+ Here's what the block parameters look like.
520
491
 
521
492
  ```ruby
522
493
  @form.save do |nested|
@@ -854,22 +825,6 @@ property :song, form: SongForm
854
825
  The nested `SongForm` is a stand-alone form class you have to provide.
855
826
 
856
827
 
857
- ## Overriding Setters For Coercion
858
-
859
- When "real" coercion is too much and you simply want to convert incoming data yourself, override the setter.
860
-
861
- ```ruby
862
- class SongForm < Reform::Form
863
- property :title
864
-
865
- def title=(v)
866
- super(v.upcase)
867
- end
868
- ```
869
-
870
- This will capitalize the title _after_ calling `form.validate` but _before_ validation happens. Note that you can use `super` to call the original setter.
871
-
872
-
873
828
  ## Default Values For Presentation
874
829
 
875
830
  In case you want to change a value for presentation or provide a default value, override the reader. This is only considered when the form is rendered (e.g. in `form_for`).
@@ -904,49 +859,34 @@ form.changed?(:title) #=> true
904
859
  When including `Sync::SkipUnchanged`, the form won't assign unchanged values anymore in `#sync`.
905
860
 
906
861
 
907
- ## Dynamically Syncing And Saving Properties
908
-
909
- Both `#sync` and `#save` can be configured to run a dynamical lambda per property.
910
-
911
- The `sync:` option allows to statically add a lambda to a property.
912
-
913
- ```ruby
914
- property :title, sync: lambda { |value, options| model.set_title(value) }
915
- ```
916
-
917
- Instead of running Reform's built-in sync for this property the block is run.
918
-
919
- You can also provide the sync lambda at run-time.
862
+ ## Undocumented Features
920
863
 
921
- ```ruby
922
- form.sync(title: lambda { |value, options| form.model.title = "HOT: #{value}" })
923
- ```
864
+ _(Please don't read this section!)_
924
865
 
925
- This block is run in the caller's context allowing you to access environment variables. Note that the dynamic sync happens _before_ save, so the model id may be unavailable.
866
+ ### Skipping Properties when Validating
926
867
 
927
- You can do the same for saving.
868
+ In `#validate`, you can ignore properties now using `:skip_if` for deserialization.
928
869
 
929
870
  ```ruby
930
- form.save(title: lambda { |value, options| form.model.title = "#{form.model.id} --> #{value}" })
871
+ property :hit, skip_if: lambda { |fragment, *| fragment["title"].blank? }
931
872
  ```
932
- Again, this block is run in the caller's context.
933
-
934
- The two features are an excellent way to handle file uploads without ActiveRecord's horrible callbacks.
935
873
 
874
+ This works for both properties and nested forms. The property will simply be ignored when deserializing, as if it had never been in the incoming hash/document.
936
875
 
937
- ## Undocumented Features
938
-
939
- _(Please don't read this section!)_
876
+ For nested properties you can use `:skip_if: :all_blank` as a macro to ignore a nested form if all values are blank.
940
877
 
878
+ Note that this still runs validations for the property, though.
941
879
 
942
880
  ### Prepopulating Forms
943
881
 
882
+ Docs: http://trailblazerb.org/gems/reform/prepopulator.html
883
+
944
884
  When rendering a new form for an empty object, nested forms won't show up. The [Trailblazer book, chapter 5](https://leanpub.com/trailblazer), discusses this in detail.
945
885
 
946
- You can use the `:prepopulate` option to configure how to populate a nested form (this also works for scalar properties).
886
+ You can use the `:prepopulator` option to configure how to populate a nested form (this also works for scalar properties).
947
887
 
948
888
  ```ruby
949
- property :song, prepopulate: ->(*) { Song.new } do
889
+ property :song, prepopulator: ->(options) { self.song = Song.new } do
950
890
  # ..
951
891
  end
952
892
  ```
@@ -957,6 +897,8 @@ This option is only executed when being instructed to do so, using the `#prepopu
957
897
  form.prepopulate!
958
898
  ```
959
899
 
900
+ You can also pass options to `#prepopulate`.
901
+
960
902
  Only do this for forms that are about to get rendered, though.
961
903
 
962
904
  Collections and partial collection population is covered in chapter 5.