reform 1.2.6 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-

|
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
|
+

|
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.
|