reform 0.2.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e447e702b5effb25c8b881fb6c3056e57d39b5f9
4
- data.tar.gz: 83ab3bdb3a48858fa273ea8985eb04bdee24e5d5
3
+ metadata.gz: 331784eaf17a8dc17f08c7c39cdb60af3bf502ed
4
+ data.tar.gz: 48cc28e685c889c3fd7ca7290215c5c4d1801ec1
5
5
  SHA512:
6
- metadata.gz: 6891ad8debef392f1ac60f79c962bb42004325b15aa0d5ef460c1456ec03152c12dacf0e933193f2b0630042af1bc4a86ea9f564ab69e2f2d369cc3517a9dea0
7
- data.tar.gz: 4a4014071fb1f71da66d6124ba79f007d1e09bd2854ba6f989e299e931c158a5500408c171eb7f714704b930246d3ff5df46cd00a9d87d00ec49ef501d4201c9
6
+ metadata.gz: 983557534185ed0d80afaccd6b433abb2ffe97fb5e44898bb915bb8d44b92f68d11447e2e30d7839be23c8f91cd374ffefe98a704eabe8f54763f13a09ade2dd
7
+ data.tar.gz: fbf1dc09a1197491e17fd4e36d137a98309fa05310f18e40804093ba6e1af3b08959f524293eef751fe5cbc0a21e73bd915d1a76fdddf4eb63091e203332342e
data/CHANGES.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 1.0.0
2
+
3
+ * Removed `Form::DSL` in favour of `Form::Composition`.
4
+ * Simplified nested forms. You can now do
5
+ ```ruby
6
+ validates :songs, :length => {:minimum => 1}
7
+ validates :hit, :presence => true
8
+ ```
9
+ * Allow passing symbol hash keys into `#validate`.
10
+ * Unlimited nesting of forms, if you really want that.
11
+ * `save` gets called on all nested forms automatically, disable with `save: false`.
12
+ * Renaming with `as:` now works everywhere.
13
+ * Fixes to make `Composition` work everywhere.
14
+ * Extract setup and validate into `Contract`.
15
+ * Automatic population with `:populate_if_empty` in `#validate`.
16
+ * Remove `#from_hash` and `#to_hash`.
17
+ * Introduce `#sync` and make `#save` less smart.
18
+
1
19
  ## 0.2.7
2
20
 
3
21
  * Last release supporting Representable 1.7.
data/Gemfile CHANGED
@@ -2,4 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- #gem 'representable', path: "../representable"
5
+ # gem 'representable', path: "../representable"
6
+ # gem "disposable", path: "../disposable"
data/README.md CHANGED
@@ -4,11 +4,6 @@ Decouple your models from forms. Reform gives you a form object with validations
4
4
 
5
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
6
 
7
- # Development Status
8
-
9
- Dear Reform users - you know I love all of you. Reform is currently being improved by myself. I am definitely *not* resisting all of the feature requests (especially about nesting, model validations, automatic model setup when validating has_many, etc.) but working hard to make it better. Expect a new Reform version _and_ better README mid-late April and please refrain from telling me how lazy I am. Thanks and see you soon!!! :heart:
10
-
11
-
12
7
  ## Installation
13
8
 
14
9
  Add this line to your Gemfile:
@@ -17,6 +12,14 @@ Add this line to your Gemfile:
17
12
  gem 'reform'
18
13
  ```
19
14
 
15
+ ## Nomenclatura
16
+
17
+ Reform comes with two base classes.
18
+
19
+ * `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.
20
+ * `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.
21
+
22
+
20
23
  ## Defining Forms
21
24
 
22
25
  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.
@@ -34,7 +37,20 @@ end
34
37
  To add fields to the form use the `::property` method. Also, validations no longer go into the model but sit in the form.
35
38
 
36
39
 
37
- ## Using Forms
40
+ ## The API
41
+
42
+ Forms have a ridiculously simple API with only a handful of public methods.
43
+
44
+ 1. `#initialize` always requires a model that the form represents.
45
+ 2. `#validate(params)` will run all validations for the form with the input data. Its return value is the boolean result of the validations.
46
+ 3. `#errors` returns validation messages in a classy ActiveModel style.
47
+ 4. `#sync` writes form data back to the model. This will only use setter methods on the model(s).
48
+ 5. `#save` (optional) will call `#save` on the model and nested models. Note that this implies a `#sync` call.
49
+
50
+ In addition to the main API, forms expose accessors to the defined properties. This is used for rendering or manual operations.
51
+
52
+
53
+ ## Setup
38
54
 
39
55
  In your controller you'd create a form instance and pass in the models you wanna work on.
40
56
 
@@ -54,6 +70,25 @@ class SongsController
54
70
  end
55
71
  ```
56
72
 
73
+ Reform will read property values from the model in setup. Given the following form class.
74
+
75
+ ```ruby
76
+ class SongForm < Reform::Form
77
+ property :title
78
+ ```
79
+
80
+ Internally, this form will call `song.title` to populate the title field.
81
+
82
+ If you, for whatever reasons, want to use a different public name, use `:as`.
83
+
84
+ ```ruby
85
+ class SongForm < Reform::Form
86
+ property :name, as: :title
87
+ ```
88
+
89
+ This will still call `song.title` but expose the attribute as `name`.
90
+
91
+
57
92
  ## Rendering Forms
58
93
 
59
94
  Your `@form` is now ready to be rendered, either do it yourself or use something like Rails' `#form_for`, `simple_form` or `formtastic`.
@@ -65,7 +100,10 @@ Your `@form` is now ready to be rendered, either do it yourself or use something
65
100
  = f.input :title
66
101
  ```
67
102
 
68
- ## Validating Forms
103
+ Nested forms and collections can easily rendered with `fields_for`, etc. Just use Reform as if it would be an ActiveModel instance in the view layer.
104
+
105
+
106
+ ## Validation
69
107
 
70
108
  After a form submission, you wanna validate the input.
71
109
 
@@ -86,9 +124,32 @@ Note that Reform only updates values of the internal form attributes - the under
86
124
  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.
87
125
 
88
126
 
127
+ ## Syncing Back
128
+
129
+ 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.
130
+
131
+ It's then up to you what to do with the updated models - they're still unsaved.
132
+
133
+
89
134
  ## Saving Forms
90
135
 
91
- We provide a bullet-proof way to save your form data: by letting _you_ do it!
136
+ The easiest way to save the data is to call `#save` on the form.
137
+
138
+ ```ruby
139
+ @form.save #=> populates song with incoming data
140
+ # by calling @form.song.title= and @form.song.length=.
141
+ ```
142
+
143
+ This will sync the data to the model and then call `song.save`.
144
+
145
+ Sometimes, you need to do stuff manually.
146
+
147
+
148
+ ## Saving Forms Manually
149
+
150
+ This is where you call `#save` with a block. This won't touch the models at all but give you a nice hash, so you can do it yourself.
151
+
152
+ Note that you can call `#sync` and _then_ call `#save` with the block to save models yourself.
92
153
 
93
154
  ```ruby
94
155
  if @form.validate(params[:song])
@@ -97,7 +158,7 @@ We provide a bullet-proof way to save your form data: by letting _you_ do it!
97
158
  data.title #=> "Rio"
98
159
  data.length #=> "366"
99
160
 
100
- nested #=> {title: "Rio"}
161
+ nested #=> {title: "Rio", length: "366"}
101
162
 
102
163
  Song.create(nested)
103
164
  end
@@ -105,17 +166,50 @@ We provide a bullet-proof way to save your form data: by letting _you_ do it!
105
166
 
106
167
  While `data` gives you an object exposing the form property readers, `nested` is a hash reflecting the nesting structure of your form. Note how you can use arbitrary code to create/update models - in this example, we used `Song::create`.
107
168
 
108
- To push the incoming data to the models directly, call `#save` without the block.
169
+
170
+ ## Contracts
171
+
172
+ Contracts give you a sub-set of the `Form` API.
173
+
174
+ 1. `#initialize` accepts an already populated model.
175
+ 2. `#validate` will run defined validations (without accepting a params hash as in `Form`).
176
+
177
+ Contracts can be used to completely remove validation logic from your model classes. Validation should happen in a separate layer - a `Contract`.
178
+
179
+ ### Defining Contracts
180
+
181
+ A contract looks like a form.
109
182
 
110
183
  ```ruby
111
- @form.save #=> populates song with incoming data
112
- # by calling @form.song.title= and @form.song.length=.
184
+ class AlbumContract < Reform::Contract
185
+ property :title
186
+ validates :title, length: {minimum: 9}
187
+
188
+ collection :songs do
189
+ property :title
190
+ validates :title, presence: true
191
+ end
113
192
  ```
114
193
 
115
- Think of `@form.save` as a sync operation where the submitted data is written to your models using public setters.
194
+ It defines the validations and the object graph to be run.
195
+
196
+ 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.
116
197
 
117
- Note that this does _not_ call `save` on your models per default: this only happens in an ActiveRecord environment or when `Form::ActiveRecord` is mixed in (learn more [here](https://github.com/apotonick/reform#activerecord-compatibility)).
198
+ ### Using Contracts
118
199
 
200
+ Applying a contract is simple, all you need is a populated object (e.g. an album after `#update_attributes`).
201
+
202
+ ```ruby
203
+ album.update_attributes(..)
204
+
205
+ if AlbumContract.new(album).validate
206
+ album.save
207
+ else
208
+ raise album.errors.messages.inspect
209
+ end
210
+ ```
211
+
212
+ Contracts help you to make your data layer a dumb persistance tier. My [upcoming book discusses that in detail](http://nicksda.apotomo.de).
119
213
 
120
214
 
121
215
  ## Nesting Forms: 1-1 Relations
@@ -201,6 +295,7 @@ Supposed you use reform's automatic save without a block, the following assignme
201
295
  ```ruby
202
296
  form.song.title = "Hungry Like The Wolf"
203
297
  form.song.artist.name = "Duran Duran"
298
+ form.song.save
204
299
  ```
205
300
 
206
301
  ## Nesting Forms: 1-n Relations
@@ -266,6 +361,69 @@ end
266
361
  ```
267
362
 
268
363
 
364
+ ## Nesting Configuration
365
+
366
+ ### Turning Off Autosave
367
+
368
+ You can assign Reform to _not_ call `save` on a particular nested model (per default, it is called automatically on all nested models).
369
+
370
+ ```ruby
371
+ class AlbumForm < Reform::Form
372
+ # ...
373
+
374
+ collection :songs, save: false do
375
+ # ..
376
+ end
377
+ ```
378
+
379
+ The `:save` options set to false won't save models.
380
+
381
+
382
+ ### Populating Forms For Validation
383
+
384
+ With a complex nested setup it can sometimes be painful to setup the model object graph.
385
+
386
+ Let's assume you rendered the following form.
387
+
388
+ ```ruby
389
+ @form = AlbumForm.new(Album.new(songs: [Song.new, Song.new]))
390
+ ```
391
+
392
+ This will render two nested forms to create new songs.
393
+
394
+ 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.
395
+
396
+ So, the following code will fail.
397
+
398
+ ```ruby
399
+ @form = AlbumForm.new(Album.new).validate(params[:album])
400
+ ```
401
+
402
+ However, you can advise Reform to setup the correct objects for you.
403
+
404
+ ```ruby
405
+ class AlbumForm < Reform::Form
406
+ # ...
407
+
408
+ collection :songs, populate_if_empty: Song do
409
+ # ..
410
+ end
411
+ ```
412
+
413
+ This works for both `property` and `collection` and instantiates `Song` objects where they're missing when calling `#validate`.
414
+
415
+ If you wanna create the objects yourself, because you're smarter than Reform, do it with a lambda.
416
+
417
+ ```ruby
418
+ class AlbumForm < Reform::Form
419
+ # ...
420
+
421
+ collection :songs, populate_if_empty: lambda { |fragment, args| Song.new } do
422
+ # ..
423
+ end
424
+ ```
425
+
426
+
269
427
  ## Compositions
270
428
 
271
429
  Sometimes you might want to embrace two (or more) unrelated objects with a single form. While you could write a simple delegating composition yourself, reform comes with it built-in.
@@ -405,7 +563,9 @@ Reform doesn't really know whether it's working with a PORO, an `ActiveRecord` i
405
563
 
406
564
  When rendering the form, reform calls readers on the decorated model to retrieve the field data (`Song#title`, `Song#length`).
407
565
 
408
- When saving a submitted form, the same happens using writers. Reform simply calls `Song#title=(value)`. No knowledge is required about the underlying database layer.
566
+ When syncing a submitted form, the same happens using writers. Reform simply calls `Song#title=(value)`. No knowledge is required about the underlying database layer.
567
+
568
+ The same applies to saving: Reform will call `#save` on the main model and nested models.
409
569
 
410
570
  Nesting forms only requires readers for the nested properties as `Album#songs`.
411
571
 
@@ -426,7 +586,6 @@ However, you should know about two things.
426
586
  Reform provides the following `ActiveRecord` specific features. They're mixed in automatically in a Rails/AR setup.
427
587
 
428
588
  * Uniqueness validations. Use `validates_uniqueness_of` in your form.
429
- * Calling `Form#save` will explicitely call `save` on your model (added in 0.2.1) which will usually trigger a database insertion or update.
430
589
 
431
590
  As mentioned in the [Rails Integration](https://github.com/apotonick/reform#rails-integration) section some Rails 4 setups do not properly load.
432
591
 
@@ -518,6 +677,26 @@ class SongForm < Reform::Form
518
677
 
519
678
  This will capitalize the title _after_ calling `form.validate` but _before_ validation happens. Note that you can use `super` to call the original setter.
520
679
 
680
+
681
+ ## Undocumented Features
682
+
683
+ _(Please don't read this section!)_
684
+
685
+
686
+ ### Populator
687
+
688
+ You can run your very own populator logic if you're keen (and you know what you're doing).
689
+
690
+ ```ruby
691
+ class AlbumForm < Reform::Form
692
+ # ...
693
+
694
+ collection :songs, populator: lambda { |fragment, args| args.binding[:form].new(Song.find fragment[:id]) } do
695
+ # ..
696
+ end
697
+ ```
698
+
699
+
521
700
  ## Support
522
701
 
523
702
  If you run into any trouble chat with us on irc.freenode.org#trailblazer.
@@ -532,4 +711,4 @@ If you run into any trouble chat with us on irc.freenode.org#trailblazer.
532
711
 
533
712
  ### Attributions!!!
534
713
 
535
- Great thanks to [Blake Education](https://github.com/blake-education) for giving us the freedom and time to develop this project while working on their project.
714
+ Great thanks to [Blake Education](https://github.com/blake-education) for giving us the freedom and time to develop this project in 2013 while working on their project.
data/TODO.md CHANGED
@@ -4,11 +4,22 @@
4
4
  * document Form#to_hash and Form#to_nested_hash (e.g. with OpenStruct composition to make it a very simple form)
5
5
  * document getter: and representer_exec:
6
6
 
7
- * allow :as to rename nested forms
8
- * make #nested_forms easier
7
+ * Debug module that logs every step.
8
+ * no setters in Contract#setup
9
9
 
10
10
  vererben in inline representern (module zum einmixen, attrs löschen)
11
11
 
12
12
  # TODO: remove the concept of Errors#messages and just iterate over Errors.
13
13
  # each form contains its local field errors in Errors
14
- # form.messages should then go through them and compile a "summary" instead of adding them to the parents #errors in #validate.
14
+ # form.messages should then go through them and compile a "summary" instead of adding them to the parents #errors in #validate.
15
+
16
+
17
+
18
+ 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.
19
+
20
+
21
+ WHY DON'T PEOPLE USE THIS:
22
+ http://guides.rubyonrails.org/association_basics.html#the-has-many-association
23
+ 4.2.2.2 :autosave
24
+
25
+ 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.
Binary file
@@ -1,7 +1,15 @@
1
+ module Reform
2
+ autoload :Contract, 'reform/contract'
3
+
4
+ def self.rails3_0?
5
+ ::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
6
+ end
7
+ end
8
+
1
9
  require 'reform/form'
2
10
  require 'reform/form/composition'
3
11
  require 'reform/form/active_model'
4
12
 
5
13
  if defined?(Rails) # DISCUSS: is everyone ok with this?
6
14
  require 'reform/rails'
7
- end
15
+ end
@@ -1,54 +1,61 @@
1
+ require 'disposable/composition'
2
+
1
3
  module Reform
2
- # Keeps composition of models and knows how to transform a plain hash into a nested hash.
3
- class Composition
4
- class << self
5
- def map(options)
6
- @attr2obj = {} # {song: ["title", "track"], artist: ["name"]}
4
+ class Expose
5
+ include Disposable::Composition
7
6
 
8
- options.each do |mdl, meths|
9
- create_accessors(mdl, meths)
10
- attr_reader mdl
7
+ # DISCUSS: this might be moved to Disposable::Twin::Expose.
8
+ class << self
9
+ # Builder for a concrete Composition class with configurations from the form's representer.
10
+ def from(representer)
11
+ options = {}
11
12
 
12
- meths.each { |m| @attr2obj[m.to_s] = mdl }
13
+ representer.representable_attrs.each do |definition|
14
+ process_definition!(options, definition)
13
15
  end
14
- end
15
16
 
16
- # Specific to representable.
17
- def map_from(representer)
18
- options = {}
19
- representer.representable_attrs.each do |cfg|
20
- options[cfg.options[:on]] ||= []
21
- options[cfg.options[:on]] << cfg.name
17
+ Class.new(self).tap do |composition| # for 1.8 compat. you're welcome.
18
+ composition.map(options)
19
+ # puts composition@map.inspect
22
20
  end
23
-
24
- map options
25
21
  end
26
22
 
27
- def model_for_property(name)
28
- @attr2obj.fetch(name.to_s)
23
+ private
24
+ def process_definition!(options, definition)
25
+ options[:model] ||= []
26
+ options[:model] << [definition[:private_name], definition.name].compact
29
27
  end
28
+ end
29
+ end
30
30
 
31
- private
32
- def create_accessors(model, methods)
33
- accessors = methods.collect { |m| [m, "#{m}="] }.flatten
34
- delegate *accessors << {:to => :"#{model}"}
31
+ # Keeps composition of models and knows how to transform a plain hash into a nested hash.
32
+ class Composition < Expose
33
+
34
+ # DISCUSS: this might be moved to Disposable::Twin::Composition.
35
+ class << self
36
+ # Builder for a concrete Composition class with configurations from the form's representer.
37
+ def process_definition!(options, definition)
38
+ options[definition[:on]] ||= []
39
+ options[definition[:on]] << [definition[:private_name], definition.name].compact
35
40
  end
36
41
  end
37
42
 
38
- # TODO: make class method?
43
+ def save
44
+ each { |model| model.save }
45
+ end
46
+
39
47
  def nested_hash_for(attrs)
40
48
  {}.tap do |hsh|
41
49
  attrs.each do |name, val|
42
- obj = self.class.model_for_property(name)
43
- hsh[obj] ||= {}
44
- hsh[obj][name.to_sym] = val
45
- end
46
- end
47
- end
50
+ #obj = self.class.model_for_property(name)
51
+ config = self.class.instance_variable_get(:@map)[name.to_sym]
48
52
 
49
- def initialize(models)
50
- models.each do |name, obj|
51
- instance_variable_set(:"@#{name}", obj)
53
+ model = config[:model]
54
+ method = config[:method]
55
+
56
+ hsh[model] ||= {}
57
+ hsh[model][method] = val
58
+ end
52
59
  end
53
60
  end
54
61
  end