reform 2.2.4

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