reform 1.1.1 → 1.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +35 -1
  3. data/Gemfile +1 -1
  4. data/README.md +83 -21
  5. data/TODO.md +8 -0
  6. data/database.sqlite3 +0 -0
  7. data/gemfiles/Gemfile.rails-4.0 +1 -0
  8. data/lib/reform.rb +4 -2
  9. data/lib/reform/active_record.rb +2 -1
  10. data/lib/reform/composition.rb +2 -2
  11. data/lib/reform/contract.rb +24 -7
  12. data/lib/reform/contract/setup.rb +21 -9
  13. data/lib/reform/contract/validate.rb +0 -6
  14. data/lib/reform/form.rb +6 -8
  15. data/lib/reform/form/active_model.rb +3 -2
  16. data/lib/reform/form/active_model/model_validations.rb +13 -1
  17. data/lib/reform/form/active_record.rb +1 -7
  18. data/lib/reform/form/changed.rb +9 -0
  19. data/lib/reform/form/json.rb +13 -0
  20. data/lib/reform/form/model_reflections.rb +18 -0
  21. data/lib/reform/form/save.rb +25 -3
  22. data/lib/reform/form/scalar.rb +4 -2
  23. data/lib/reform/form/sync.rb +82 -12
  24. data/lib/reform/form/validate.rb +38 -0
  25. data/lib/reform/rails.rb +1 -1
  26. data/lib/reform/representer.rb +14 -23
  27. data/lib/reform/schema.rb +23 -0
  28. data/lib/reform/twin.rb +20 -0
  29. data/lib/reform/version.rb +1 -1
  30. data/reform.gemspec +2 -2
  31. data/test/active_model_test.rb +2 -2
  32. data/test/active_record_test.rb +7 -4
  33. data/test/changed_test.rb +69 -0
  34. data/test/custom_validation_test.rb +47 -0
  35. data/test/deserialize_test.rb +2 -7
  36. data/test/empty_test.rb +30 -0
  37. data/test/fields_test.rb +24 -0
  38. data/test/form_composition_test.rb +24 -2
  39. data/test/form_test.rb +84 -0
  40. data/test/inherit_test.rb +12 -0
  41. data/test/model_reflections_test.rb +65 -0
  42. data/test/read_only_test.rb +28 -0
  43. data/test/reform_test.rb +2 -175
  44. data/test/representer_test.rb +47 -0
  45. data/test/save_test.rb +51 -1
  46. data/test/scalar_test.rb +0 -18
  47. data/test/skip_if_test.rb +62 -0
  48. data/test/skip_unchanged_test.rb +86 -0
  49. data/test/sync_option_test.rb +83 -0
  50. data/test/twin_test.rb +23 -0
  51. data/test/validate_test.rb +9 -1
  52. metadata +37 -9
  53. data/lib/reform/form/virtual_attributes.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4eac1a339ca122c10c68332cbb713fee79ef7670
4
- data.tar.gz: fbea1c3d34b9af52108f4d3e508b562b32f79c27
3
+ metadata.gz: 463e240b0183da1d46a70a9252959ead4e45a152
4
+ data.tar.gz: 07842a7a220f1c72bdc412d49a187bf7ed1b76bf
5
5
  SHA512:
6
- metadata.gz: 9242a0e45d2392e58af57958d695d37fb0f316f90701ebc383e256a72fa01066097e441752cd1deb112dabe628d91fca7ccfee835ee3deb2e2997fb0ca69a836
7
- data.tar.gz: a95af8299b9fc16284ec5c5d583208c1efdc12ed3a6a0bfc71a6cb885409731102d20faba0009c7827523baee402165d9cf331589e41e841d0d449b0b7d2ada5
6
+ metadata.gz: bb02ec3ec33906652a1b72aba03481751c0e5b6a5516a1f6b11dd2cf001a681b9b21d16b764b69f4e482875213926ad4d13b4d7a7c5002417419bed3f9653008
7
+ data.tar.gz: 6a2c816422517921b3b3b93a400deb35b5c989ffa6ea9b21f8d3789aa3d07d52717e36a1b3f313e622344ab3c17bc469d26efe860dc6de120f15f4dad220c779
data/CHANGES.md CHANGED
@@ -1,6 +1,40 @@
1
- ## 1.1.2
1
+ ## 1.2.0
2
2
 
3
+ ### Breakage
4
+
5
+ * Due to countless bugs we no longer include support for simple_form's type interrogation automatically. This allows using forms with non-AM objects. If you want full support for simple_form do as follows.
6
+
7
+ ```ruby
8
+ class SongForm < Reform::Form
9
+ include ModelReflections
10
+ ```
11
+
12
+ Including this module will add `#column_for_attribute` and other methods need by form builders to automatically guess the type of a property.
13
+
14
+ ### Changes
15
+
16
+ * `Form#save` with `Composition` now returns true only if all composite models saved.
17
+ * `Form::copy_validations_from` allows copying custom validators now.
3
18
  * `::validates_uniqueness_of` now accepts options like `:scope`. Thanks to @cveneziani for a failing test and insight.
19
+ * New call style for `::properties`. Instead of an array, it's now `properties :title, :genre`.
20
+ * All options are evaluated with `pass_options: true`.
21
+
22
+ ### New Stuff!!!
23
+
24
+ * `:skip_if`, `:skip_if: :all_blank`.
25
+ * You can now specify validations right in the `::property` call.
26
+
27
+ ```ruby
28
+ property :title, validates: {presence: true}
29
+ ```
30
+
31
+ Thanks to @zubin for this brillant idea!
32
+ * Wanna parse JSON in `#validate`? Include `Reform::Form::JSON`.
33
+ * Dirty
34
+ * :sync
35
+ * :save
36
+ * `Sync::SkipUnchanged`.
37
+
4
38
 
5
39
  ## 1.1.1
6
40
 
data/Gemfile CHANGED
@@ -2,5 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'representable', path: "../representable"
5
+ # gem 'representable', path: "../representable"
6
6
  # gem "disposable", path: "../disposable"
data/README.md CHANGED
@@ -29,13 +29,32 @@ class SongForm < Reform::Form
29
29
  property :title
30
30
  property :length
31
31
 
32
- validates :title, presence: true
32
+ validates :title, presence: true
33
33
  validates :length, numericality: true
34
34
  end
35
35
  ```
36
36
 
37
- To add fields to the form use the `::property` method. Also, validations no longer go into the model but sit in the form.
37
+ Define your form's fields using `::property`. Validations no longer go into the model, but into the form.
38
38
 
39
+ Luckily, this can be shortened as follows.
40
+
41
+ ```ruby
42
+ class SongForm < Reform::Form
43
+ property :title, validates: {presence: true}
44
+ property :length, validates {numericality: true}
45
+ end
46
+ ```
47
+
48
+ Use `properties` to bulk-specify fields.
49
+
50
+ ```ruby
51
+ class SongForm < Reform::Form
52
+ properties :title, :length, validates: {presence: true} # both required!
53
+ validates :length, numericality: true
54
+ end
55
+ ```
56
+
57
+ After explicitely defining your fields, you're ready to use the form.
39
58
 
40
59
  ## The API
41
60
 
@@ -163,12 +182,21 @@ You can always access the form's model. This is helpful when you were using popu
163
182
 
164
183
  ```ruby
165
184
  @form.save do |nested|
166
- album = @form.album.model
185
+ album = @form.model
167
186
 
168
187
  album.update_attributes(nested[:album])
169
188
  end
170
189
  ```
171
190
 
191
+ If the form wraps multiple models, via [composition](#compositions), you can access them like this:
192
+
193
+ ```ruby
194
+ @form.save do |nested|
195
+ song = @form.model[:song]
196
+ label = @form.model[:label]
197
+ end
198
+ ```
199
+
172
200
  Note that you can call `#sync` and _then_ call `#save { |hsh| }` to save models yourself.
173
201
 
174
202
 
@@ -202,7 +230,7 @@ In future versions and with the upcoming [Trailblazer framework](https://github.
202
230
 
203
231
  ### Using Contracts
204
232
 
205
- Applying a contract is simple, all you need is a populated object (e.g. an album after `#update_attributes`).
233
+ Applying a contract is simple, all you need is a populated object (e.g. an album after `#assign_attributes`).
206
234
 
207
235
  ```ruby
208
236
  album.assign_attributes(..)
@@ -288,9 +316,7 @@ Or use something like `#fields_for` in a Rails environment.
288
316
  The block form of `#save` would give you the following data.
289
317
 
290
318
  ```ruby
291
- @form.save do |data, nested|
292
- data.title #=> "Hungry Like The Wolf"
293
- data.artist.name #=> "Duran Duran"
319
+ @form.save do |nested|
294
320
 
295
321
  nested #=> {title: "Hungry Like The Wolf",
296
322
  # artist: {name: "Duran Duran"}}
@@ -357,9 +383,7 @@ However, `#fields_for` works just fine, again.
357
383
  The block form of `#save` will expose the data structures already discussed.
358
384
 
359
385
  ```ruby
360
- @form.save do |data, nested|
361
- data.title #=> "Rio"
362
- data.songs.first.title #=> "Hungry Like The Wolf"
386
+ @form.save do |nested|
363
387
 
364
388
  nested #=> {title: "Rio"
365
389
  # songs: [{title: "Hungry Like The Wolf"},
@@ -482,9 +506,7 @@ When using `#save' without a block reform will use writer methods on the differe
482
506
  Here's how the block parameters look like.
483
507
 
484
508
  ```ruby
485
- @form.save do |data, nested|
486
- data.title #=> "Rio"
487
- data.city #=> "London"
509
+ @form.save do |nested|
488
510
 
489
511
  nested #=> {
490
512
  # song: {title: "Rio"}
@@ -632,7 +654,7 @@ form.password_confirmation #=> "321"
632
654
  The nested hash in the block-`#save` provides the same value.
633
655
 
634
656
  ```ruby
635
- form.save do |f, nested|
657
+ form.save do |nested|
636
658
  nested[:password_confirmation] #=> "321"
637
659
  ```
638
660
 
@@ -652,10 +674,8 @@ You want to use this to display an initial value or to further process this fiel
652
674
  It is still readable in the nested hash and through the form itself.
653
675
 
654
676
  ```ruby
655
- form.save do |f, nested|
677
+ form.save do |nested|
656
678
  nested[:country] #=> "Australia"
657
-
658
- f.country #=> "Australia"
659
679
  ```
660
680
 
661
681
  ## Validations From Models
@@ -765,6 +785,18 @@ class SongForm < Reform::Form
765
785
  end
766
786
  ```
767
787
 
788
+ ## Validations For File Uploads
789
+
790
+ In case you're processing uploaded files with your form using CarrierWave, Paperclip, Dragonfly or Paperdragon we recommend using the awesome [file_validators](https://github.com/musaffa/file_validators) gem for file type and size validations.
791
+
792
+ ```ruby
793
+ class SongForm < Reform::Form
794
+ property :image
795
+
796
+ validates :image, file_size: {less_than: 2.megabytes},
797
+ file_content_type: {allow: ['image/jpeg', 'image/png', 'image/gif']}
798
+ ```
799
+
768
800
  ## Multiparameter Dates
769
801
 
770
802
  Composed multi-parameter dates as created by the Rails date helper are processed automatically. As soon as Reform detects an incoming `release_date(i1)` or the like it is gonna be converted into a date.
@@ -777,9 +809,7 @@ Note that the date will be `nil` when one of the components (year/month/day) is
777
809
  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.
778
810
 
779
811
 
780
- ## Additional Features
781
-
782
- ### Nesting Without Inline Representers
812
+ ## Nesting Without Inline Representers
783
813
 
784
814
  When nesting form, you usually use a so-called inline form doing `property :song do .. end`.
785
815
 
@@ -792,7 +822,7 @@ property :song, form: SongForm
792
822
  The nested `SongForm` is a stand-alone form class you have to provide.
793
823
 
794
824
 
795
- ### Overriding Accessors
825
+ ## Overriding Setters For Coercion
796
826
 
797
827
  When "real" coercion is too much and you simply want to convert incoming data yourself, override the setter.
798
828
 
@@ -808,6 +838,38 @@ class SongForm < Reform::Form
808
838
  This will capitalize the title _after_ calling `form.validate` but _before_ validation happens. Note that you can use `super` to call the original setter.
809
839
 
810
840
 
841
+ ## Default Values For Presentation
842
+
843
+ 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`).
844
+
845
+ ```ruby
846
+ class SongForm < Reform::Form
847
+ property :genre
848
+
849
+ def genre
850
+ super || 'Punkrock'
851
+ end
852
+ end
853
+ ```
854
+
855
+ This will now be used when rendering the view.
856
+
857
+ ```haml
858
+ = f.input :genre # calls form.genre which provides default.
859
+ ```
860
+
861
+ ## Dirty Tracker
862
+
863
+ Every form tracks changes in `#validate` and allows to check if a particular property value has changed using `#changed?`.
864
+
865
+ ```ruby
866
+ form.title => "Button Up"
867
+
868
+ form.validate("title" => "Just Kiddin'")
869
+ form.changed?(:title) #=> true
870
+ ```
871
+
872
+
811
873
  ## Undocumented Features
812
874
 
813
875
  _(Please don't read this section!)_
data/TODO.md CHANGED
@@ -1,3 +1,11 @@
1
+ * fields is a Twin and sorts out all the changed? stuff.
2
+ * virtual: don't read dont write
3
+ * empty dont read, but write
4
+ * read_only: read, don't write
5
+
6
+
7
+
8
+
1
9
  * `validates :title, :presence => true`
2
10
  with @model.title == "Little Green Car" and validate({}) the form is still valid (as we "have" a valid title). is that what we want?
3
11
 
data/database.sqlite3 CHANGED
Binary file
@@ -5,3 +5,4 @@ gemspec :path => '../'
5
5
 
6
6
  gem 'railties', '~> 4.0.0'
7
7
  gem 'activerecord', '~> 4.0.0'
8
+ gem 'minitest', '~> 4.2'
data/lib/reform.rb CHANGED
@@ -1,14 +1,16 @@
1
1
  module Reform
2
- autoload :Contract, 'reform/contract'
3
-
4
2
  def self.rails3_0?
5
3
  ::ActiveModel::VERSION::MAJOR == 3 and ::ActiveModel::VERSION::MINOR == 0
6
4
  end
7
5
  end
8
6
 
7
+ require 'reform/contract'
9
8
  require 'reform/form'
10
9
  require 'reform/form/composition'
11
10
  require 'reform/form/active_model'
11
+ require 'reform/form/module'
12
+ require 'reform/composition'
13
+
12
14
 
13
15
  if defined?(Rails) # DISCUSS: is everyone ok with this?
14
16
  require 'reform/rails'
@@ -1,2 +1,3 @@
1
1
  require 'reform/form/active_model'
2
- require 'reform/form/active_record'
2
+ require 'reform/form/active_record'
3
+ require 'reform/form/model_reflections' # only load this in AR context as simple_form currently is bound to AR.
@@ -42,7 +42,7 @@ module Reform
42
42
  end
43
43
 
44
44
  def save
45
- each { |model| model.save }
45
+ each.collect { |model| model.save }.all?
46
46
  end
47
47
 
48
48
  def nested_hash_for(attrs)
@@ -60,4 +60,4 @@ module Reform
60
60
  end
61
61
  end
62
62
  end
63
- end
63
+ end
@@ -1,6 +1,7 @@
1
1
  require 'forwardable'
2
2
  require 'uber/inheritable_attr'
3
3
  require 'uber/delegates'
4
+ require 'ostruct'
4
5
 
5
6
  require 'reform/representer'
6
7
 
@@ -29,11 +30,13 @@ module Reform
29
30
  module PropertyMethods
30
31
  def property(name, options={}, &block)
31
32
  options[:private_name] = options.delete(:as)
32
-
33
33
  options[:coercion_type] = options.delete(:type)
34
-
35
34
  options[:features] ||= []
36
35
  options[:features] += features.keys if block_given?
36
+ options[:pass_options] = true
37
+ options[:virtual] = true if options[:empty] # TODO: check TODO and fix naming!
38
+
39
+ validates(name, options.delete(:validates).dup) if options[:validates]
37
40
 
38
41
  definition = representer_class.property(name, options, &block)
39
42
  setup_form_definition(definition) if block_given? or options[:form]
@@ -42,16 +45,22 @@ module Reform
42
45
  definition
43
46
  end
44
47
 
48
+ def properties(*args)
49
+ options = args.extract_options!
50
+
51
+ if args.first.is_a? Array # TODO: REMOVE in 2.0.
52
+ warn "[Reform] Deprecation: Please pass a list of names instead of array to ::properties, like `properties :title, :id`."
53
+ args = args.first
54
+ end
55
+ args.each { |name| property(name, options.dup) }
56
+ end
57
+
45
58
  def collection(name, options={}, &block)
46
59
  options[:collection] = true
47
60
 
48
61
  property(name, options, &block)
49
62
  end
50
63
 
51
- def properties(names, options={})
52
- names.each { |name| property(name, options.dup) }
53
- end
54
-
55
64
  def setup_form_definition(definition)
56
65
  options = {
57
66
  # TODO: make this a bit nicer. why do we need :form at all?
@@ -65,6 +74,7 @@ module Reform
65
74
  end
66
75
 
67
76
  private
77
+
68
78
  def create_accessor(name)
69
79
  handle_reserved_names(name)
70
80
 
@@ -123,6 +133,13 @@ module Reform
123
133
  end
124
134
  end
125
135
 
136
+ def self.clone
137
+ Class.new(self)
138
+ end
139
+
140
+ require 'reform/schema'
141
+ extend Schema
142
+
126
143
  alias_method :aliased_model, :model
127
144
 
128
145
 
@@ -137,4 +154,4 @@ module Reform
137
154
  end
138
155
  end
139
156
 
140
- require 'reform/contract/errors'
157
+ require 'reform/contract/errors'
@@ -1,4 +1,3 @@
1
-
2
1
  module Reform
3
2
  class Contract
4
3
  module Setup
@@ -9,8 +8,9 @@ module Reform
9
8
 
10
9
  def setup_fields
11
10
  representer = mapper.new(aliased_model).extend(Setup::Representer)
11
+ options = setup_options(Reform::Representer::Options[]) # handles :empty.
12
12
 
13
- create_fields(representer.fields, representer.to_hash)
13
+ create_fields(mapper.fields, representer.to_hash(options))
14
14
  end
15
15
 
16
16
  # DISCUSS: setting up the Validation (populating with values) will soon be handled with Disposable::Twin logic.
@@ -18,14 +18,16 @@ module Reform
18
18
  Fields.new(field_names, fields)
19
19
  end
20
20
 
21
+ module SetupOptions
22
+ def setup_options(options)
23
+ options
24
+ end
25
+ end
26
+ include SetupOptions
27
+
21
28
 
22
29
  # Mechanics for setting up initial Field values.
23
30
  module Representer
24
- require 'reform/form/virtual_attributes' # FIXME: that shouldn't be here.
25
-
26
- include Reform::Representer::WithOptions
27
- include Reform::Form::EmptyAttributesOptions # FIXME: that shouldn't be here.
28
-
29
31
  def to_hash(*)
30
32
  nested_forms do |attr|
31
33
  attr.merge!(
@@ -36,9 +38,19 @@ module Reform
36
38
  )
37
39
  end
38
40
 
39
- super # TODO: allow something like super(:exclude => empty_fields)
41
+ super
40
42
  end
41
43
  end # Representer
44
+
45
+
46
+ module Empty
47
+ def setup_options(options)
48
+ empty_fields = mapper.representable_attrs.find_all { |d| d[:empty] }.collect { |d| d.name.to_sym }
49
+
50
+ options.exclude!(empty_fields)
51
+ end
52
+ end
53
+ include Empty
42
54
  end
43
- end # Validation
55
+ end # Setup
44
56
  end