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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -1
- data/CHANGES.md +14 -0
- data/Gemfile +3 -2
- data/README.md +225 -283
- data/Rakefile +27 -0
- data/TODO.md +12 -0
- data/database.sqlite3 +0 -0
- data/gemfiles/Gemfile.rails-3.0 +1 -0
- data/gemfiles/Gemfile.rails-3.1 +1 -0
- data/gemfiles/Gemfile.rails-3.2 +1 -0
- data/gemfiles/Gemfile.rails-4.0 +1 -0
- data/lib/reform.rb +0 -1
- data/lib/reform/contract.rb +64 -170
- data/lib/reform/contract/validate.rb +10 -13
- data/lib/reform/form.rb +74 -19
- data/lib/reform/form/active_model.rb +19 -14
- data/lib/reform/form/coercion.rb +1 -13
- data/lib/reform/form/composition.rb +2 -24
- data/lib/reform/form/multi_parameter_attributes.rb +43 -62
- data/lib/reform/form/populator.rb +85 -0
- data/lib/reform/form/prepopulate.rb +13 -43
- data/lib/reform/form/validate.rb +29 -90
- data/lib/reform/form/validation/unique_validator.rb +13 -0
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +7 -7
- data/test/active_model_test.rb +43 -0
- data/test/changed_test.rb +23 -51
- data/test/coercion_test.rb +1 -7
- data/test/composition_test.rb +128 -34
- data/test/contract_test.rb +27 -86
- data/test/feature_test.rb +43 -6
- data/test/fields_test.rb +2 -12
- data/test/form_builder_test.rb +28 -25
- data/test/form_option_test.rb +19 -0
- data/test/from_test.rb +0 -75
- data/test/inherit_test.rb +178 -117
- data/test/model_reflections_test.rb +1 -1
- data/test/populate_test.rb +226 -0
- data/test/prepopulator_test.rb +112 -0
- data/test/readable_test.rb +2 -4
- data/test/save_test.rb +56 -112
- data/test/setup_test.rb +48 -0
- data/test/skip_if_test.rb +5 -2
- data/test/skip_setter_and_getter_test.rb +54 -0
- data/test/test_helper.rb +3 -1
- data/test/uniqueness_test.rb +41 -0
- data/test/validate_test.rb +325 -289
- data/test/virtual_test.rb +1 -3
- data/test/writeable_test.rb +3 -4
- metadata +35 -39
- data/lib/reform/composition.rb +0 -63
- data/lib/reform/contract/setup.rb +0 -50
- data/lib/reform/form/changed.rb +0 -9
- data/lib/reform/form/sync.rb +0 -116
- data/lib/reform/representer.rb +0 -84
- data/test/empty_test.rb +0 -58
- data/test/form_composition_test.rb +0 -145
- data/test/nested_form_test.rb +0 -197
- data/test/prepopulate_test.rb +0 -85
- data/test/sync_option_test.rb +0 -83
- data/test/sync_test.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 719c0d3aec98a161756c38012b620944cf19fa22
|
4
|
+
data.tar.gz: f7adb9cb663ea890809b3606a3ab01376fe4453d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
#
|
6
|
-
|
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
|
-
|
3
|
+
_Form objects decoupled from your models._
|
4
4
|
|
5
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
25
|
-
gem 'reform'
|
26
|
-
```
|
14
|
+
## Disposable
|
27
15
|
|
28
|
-
|
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
|
-
|
21
|
+
Forms are defined in separate classes. Often, these classes partially map to a model.
|
39
22
|
|
40
23
|
```ruby
|
41
|
-
class
|
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
|
-
|
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
|
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
|
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
|
52
|
+
class AlbumsController
|
91
53
|
def new
|
92
|
-
@form =
|
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
|
-
|
58
|
+
This will also work as an editing form with an existing album.
|
106
59
|
|
107
60
|
```ruby
|
108
|
-
|
109
|
-
|
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
|
-
|
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.
|
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
|
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
|
172
|
-
# by calling @form.
|
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 `
|
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
|
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: "
|
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 |
|
139
|
+
@form.save do |hash|
|
198
140
|
album = @form.model
|
199
141
|
|
200
|
-
album.update_attributes(
|
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
|
-
|
147
|
+
## Nesting
|
243
148
|
|
244
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
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
|
162
|
+
class AlbumForm < Reform::Form
|
277
163
|
property :title
|
278
|
-
|
164
|
+
validates :title, presence: true
|
279
165
|
|
280
166
|
property :artist do
|
281
|
-
property :
|
282
|
-
|
283
|
-
validates :name, presence: true
|
167
|
+
property :full_name
|
168
|
+
validates :full_name, presence: true
|
284
169
|
end
|
285
170
|
|
286
|
-
|
171
|
+
collection :songs do
|
172
|
+
property :name
|
173
|
+
end
|
287
174
|
end
|
288
175
|
```
|
289
176
|
|
290
|
-
|
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
|
-
|
183
|
+
## Nested Setup
|
294
184
|
|
295
|
-
|
185
|
+
Reform will wrap defined nested objects in their own forms. This happens automatically when instantiating the form.
|
296
186
|
|
297
187
|
```ruby
|
298
|
-
|
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
|
-
|
304
|
-
|
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
|
-
###
|
195
|
+
### Nested Rendering
|
308
196
|
|
309
|
-
When rendering
|
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
|
-
|
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
|
-
|
335
|
-
#
|
336
|
-
|
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
|
-
|
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.
|
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
|
-
|
245
|
+
This will render two nested forms to create new songs.
|
348
246
|
|
349
|
-
Reform
|
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
|
-
|
249
|
+
So, the following code will fail.
|
352
250
|
|
353
251
|
```ruby
|
354
|
-
|
355
|
-
has_many :songs
|
356
|
-
end
|
252
|
+
@form = AlbumForm.new(Album.new).validate(params[:album])
|
357
253
|
```
|
358
254
|
|
359
|
-
|
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
|
-
|
259
|
+
collection :songs, populate_if_empty: Song do
|
260
|
+
# ..
|
261
|
+
end
|
262
|
+
```
|
364
263
|
|
365
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
277
|
+
## Installation
|
376
278
|
|
377
|
-
|
279
|
+
Add this line to your Gemfile:
|
378
280
|
|
379
|
-
```
|
380
|
-
|
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
|
-
|
387
|
-
= form_for @form do |f|
|
388
|
-
= f.text_field :title
|
286
|
+
## Compositions
|
389
287
|
|
390
|
-
|
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
|
-
|
290
|
+
```ruby
|
291
|
+
class AlbumTwin < Reform::Form
|
292
|
+
include Composition
|
395
293
|
|
396
|
-
|
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
|
-
|
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
|
-
|
402
|
-
|
403
|
-
|
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
|
-
|
359
|
+
If the form wraps multiple models, via [composition](#compositions), you can access them like this:
|
413
360
|
|
414
361
|
```ruby
|
415
|
-
|
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
|
-
|
368
|
+
Note that you can call `#sync` and _then_ call `#save { |hsh| }` to save models yourself.
|
424
369
|
|
425
370
|
|
426
|
-
|
371
|
+
## Contracts
|
427
372
|
|
428
|
-
|
373
|
+
Contracts give you a sub-set of the `Form` API.
|
429
374
|
|
430
|
-
|
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
|
-
|
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
|
-
|
395
|
+
It defines the validations and the object graph to be inspected.
|
437
396
|
|
438
|
-
|
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
|
-
|
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
|
-
|
444
|
-
```
|
404
|
+
album.assign_attributes(..)
|
445
405
|
|
446
|
-
|
406
|
+
contract = AlbumContract.new(album)
|
447
407
|
|
448
|
-
|
449
|
-
|
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
|
419
|
+
This basically works like a nested `property` that iterates over a collection of songs.
|
420
|
+
|
458
421
|
|
459
|
-
|
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,
|
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
|
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
|
-
##
|
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
|
-
|
922
|
-
form.sync(title: lambda { |value, options| form.model.title = "HOT: #{value}" })
|
923
|
-
```
|
864
|
+
_(Please don't read this section!)_
|
924
865
|
|
925
|
-
|
866
|
+
### Skipping Properties when Validating
|
926
867
|
|
927
|
-
|
868
|
+
In `#validate`, you can ignore properties now using `:skip_if` for deserialization.
|
928
869
|
|
929
870
|
```ruby
|
930
|
-
|
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
|
-
|
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 `:
|
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,
|
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.
|