reform 1.1.0 → 1.1.1

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: b95fac93b5186e174e6e2aef263bff4bf5305210
4
- data.tar.gz: bafdc2c15f78fa1fe27e9b554c32d3aafcfb4fbd
3
+ metadata.gz: 4eac1a339ca122c10c68332cbb713fee79ef7670
4
+ data.tar.gz: fbea1c3d34b9af52108f4d3e508b562b32f79c27
5
5
  SHA512:
6
- metadata.gz: a55abac9d331f20d75fa66ed8950c35e5cd16460fc054a182c6d0cfb0c4d9e1524349553b9aeeeafa33b75453d35ce3c8476ad8f0adce7775c41d99d4176338a
7
- data.tar.gz: 61d436b3f4cb696e2cffa02f7e3ff1179741209da8a4f2073e36da45d12971983ff19be9ffdcb0b515ed601f933240685287fa208287a63f521d3d4dbcb2e99c
6
+ metadata.gz: 9242a0e45d2392e58af57958d695d37fb0f316f90701ebc383e256a72fa01066097e441752cd1deb112dabe628d91fca7ccfee835ee3deb2e2997fb0ca69a836
7
+ data.tar.gz: a95af8299b9fc16284ec5c5d583208c1efdc12ed3a6a0bfc71a6cb885409731102d20faba0009c7827523baee402165d9cf331589e41e841d0d449b0b7d2ada5
data/CHANGES.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 1.1.2
2
+
3
+ * `::validates_uniqueness_of` now accepts options like `:scope`. Thanks to @cveneziani for a failing test and insight.
4
+
5
+ ## 1.1.1
6
+
7
+ * Fix a bug where including a form module would mess up the options has of the validations (under older Rails).
8
+ * Fix `::properties` which modified the options hash while iterating properties.
9
+ * `Form#save` now returns the result of the `model.save` invocation.
10
+ * Fix: When overriding a reader method for a nested form for presentation (e.g. to provide an initial new record), this reader was used in `#update!`. The deserialize/update run now grabs the actual nested form instances directly from `fields`.
11
+ * `Errors#to_s` is now delegated to `messages.to_s`. This is used in `Trailblazer::Operation`.
12
+
1
13
  ## 1.1.0
2
14
 
3
15
  * Deprecate first block argument in save. It's new signature is `save { |hash| }`. You already got the form instance when calling `form.save` so there's no need to pass it into the block.
@@ -6,6 +18,7 @@
6
18
  * You can now define forms in modules including `Reform::Form::Module` to improve reusability.
7
19
  * Inheriting from forms and then overriding/extending properties with `:inherit` now works properly.
8
20
  * You can now define methods in inline forms.
21
+ * Added `Form::ActiveModel::ModelValidations` to copy validations from model classes. Thanks to @cameron-martin for this fine addition.
9
22
  * Forms can now also deserialize other formats, e.g. JSON. This allows them to be used as a contract for API endpoints and in Operations in Trailblazer.
10
23
  * Composition forms no longer expose readers to the composition members. The composition is available via `Form#model`, members via `Form#model[:member_name]`.
11
24
  * ActiveRecord support is now included correctly and passed on to nested forms.
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
@@ -493,6 +493,35 @@ Here's how the block parameters look like.
493
493
  end
494
494
  ```
495
495
 
496
+
497
+ ## Forms In Modules
498
+
499
+ To maximize reusability, you can also define forms in modules and include them in other modules or classes.
500
+
501
+ ```ruby
502
+ module SongsForm
503
+ include Reform::Form::Module
504
+
505
+ collection :songs do
506
+ property :title
507
+ validates :title, presence: true
508
+ end
509
+ end
510
+ ```
511
+
512
+ This can now be included into a real form.
513
+
514
+ ```ruby
515
+ class AlbumForm < Reform::Form
516
+ property :title
517
+
518
+ include SongsForm
519
+ end
520
+ ```
521
+
522
+ Note that you can also override properties [using inheritance](#inheritance) in Reform.
523
+
524
+
496
525
  ## Inheritance
497
526
 
498
527
  Forms can be derived from other forms and will inherit all properties and validations.
@@ -538,6 +567,8 @@ Using `inherit:` here will extend the existing `songs` form with the `band_id` f
538
567
 
539
568
  Often you want incoming form data to be converted to a type, like timestamps. Reform uses [virtus](https://github.com/solnic/virtus) for coercion, the DSL is seamlessly integrated into Reform with the `:type` option.
540
569
 
570
+ ### Virtus Coercion
571
+
541
572
  Be sure to add `virtus` to your Gemfile.
542
573
 
543
574
  ```ruby
@@ -549,10 +580,32 @@ class SongForm < Reform::Form
549
580
  property :written_at, type: DateTime
550
581
  end
551
582
 
552
- @form.save do |data, nested|
553
- data.written_at #=> <DateTime XXX>
583
+ form.validate("written_at" => "26 September")
584
+ ```
585
+
586
+ Coercion only happens in `#validate`.
587
+
588
+ ```
589
+ form.written_at #=> <DateTime "2014 September 26 00:00">
590
+ ```
591
+
592
+ ### Manual Coercing Values
593
+
594
+ If you need to filter values manually, you can override the setter in the form.
595
+
596
+ ```ruby
597
+ class SongForm < Reform::Form
598
+ property :title
599
+
600
+ def title=(value)
601
+ super sanitize(value) # value is raw form input.
602
+ end
603
+ end
554
604
  ```
555
605
 
606
+ As with the built-in coercion, this setter is only called in `#validate`.
607
+
608
+
556
609
  ## Virtual Attributes
557
610
 
558
611
  Virtual fields come in handy when there's no direct mapping to a model attribute or when you plan on displaying but not processing a value.
@@ -605,6 +658,35 @@ form.save do |f, nested|
605
658
  f.country #=> "Australia"
606
659
  ```
607
660
 
661
+ ## Validations From Models
662
+
663
+ Sometimes when you still keep validations in your models (which you shouldn't) copying them to a form might not feel right. In that case, you can let Reform automatically copy them.
664
+
665
+ ```ruby
666
+ class SongForm < Reform::Form
667
+ property :title
668
+
669
+ extend ActiveModel::ModelValidations
670
+ copy_validations_from Song
671
+ end
672
+ ```
673
+
674
+ Note how `copy_validations_from` copies over the validations allowing you to stay DRY.
675
+
676
+ This also works with Composition.
677
+
678
+ ```ruby
679
+ class SongForm < Reform::Form
680
+ include Composition
681
+ # ...
682
+
683
+ extend ActiveModel::ModelValidations
684
+ copy_validations_from song: Song, band: Band
685
+ end
686
+ ```
687
+
688
+ Be warned that we _do not_ encourage copying validations. You should rather move validation code into forms and not work on your model directly anymore.
689
+
608
690
  ## Agnosticism: Mapping Data
609
691
 
610
692
  Reform doesn't really know whether it's working with a PORO, an `ActiveRecord` instance or a `Sequel` row.
data/database.sqlite3 CHANGED
Binary file
@@ -48,14 +48,14 @@ module Reform
48
48
  property(name, options, &block)
49
49
  end
50
50
 
51
- def properties(names, *args)
52
- names.each { |name| property(name, *args) }
51
+ def properties(names, options={})
52
+ names.each { |name| property(name, options.dup) }
53
53
  end
54
54
 
55
55
  def setup_form_definition(definition)
56
56
  options = {
57
57
  # TODO: make this a bit nicer. why do we need :form at all?
58
- :form => (definition[:extend] and definition[:extend].evaluate(nil)) || definition[:form], # :form is always just a Form class name.
58
+ :form => (definition.representer_module) || definition[:form], # :form is always just a Form class name.
59
59
  :pass_options => true, # new style of passing args
60
60
  :prepare => lambda { |form, args| form }, # always just return the form without decorating.
61
61
  :representable => true, # form: Class must be treated as a typed property.
@@ -109,6 +109,20 @@ module Reform
109
109
  features[mod] = true
110
110
  end
111
111
 
112
+ # allows including representers from Representable, Roar or disposable.
113
+ def self.inherit_module!(representer) # called from Representable::included.
114
+ # representer_class.inherit_module!(representer)
115
+ representer.representable_attrs.each do |dfn|
116
+ next if dfn.name == "links" # wait a second # FIXME what is that?
117
+
118
+ # TODO: remove manifesting and do that in representable, too!
119
+ args = [dfn.name, dfn.instance_variable_get(:@options)] # TODO: dfn.to_args (inluding &block)
120
+
121
+ property(*args) and next unless dfn.representer_module
122
+ property(*args) { include dfn.representer_module } # nested.
123
+ end
124
+ end
125
+
112
126
  alias_method :aliased_model, :model
113
127
 
114
128
 
@@ -30,4 +30,8 @@ class Reform::Contract::Errors < ActiveModel::Errors
30
30
  def valid? # TODO: test me in unit test.
31
31
  blank?
32
32
  end
33
+
34
+ def to_s
35
+ messages.inspect
36
+ end
33
37
  end # Errors
@@ -25,8 +25,9 @@ module Reform::Form::ActiveModel
25
25
 
26
26
  # Modify the incoming Rails params hash to be representable compliant.
27
27
  def update!(params)
28
- # DISCUSS: #validate should actually expect the complete params hash and then pick the right key as it knows the form name.
29
- # however, this would cause confusion?
28
+ return super unless params.is_a?(Hash)
29
+ # TODO: run this only for hash deserialization, but generically (#deserialize_hash ?).
30
+
30
31
  mapper.new(self).nested_forms do |attr, model| # FIXME: make this simpler.
31
32
  rename_nested_param_for!(params, attr)
32
33
  end
@@ -8,8 +8,9 @@ module Reform::Form::ActiveRecord
8
8
  end
9
9
 
10
10
  module ClassMethods
11
- def validates_uniqueness_of(attribute)
12
- validates_with UniquenessValidator, :attributes => [attribute]
11
+ def validates_uniqueness_of(attribute, options={})
12
+ options = options.merge(:attributes => [attribute])
13
+ validates_with(UniquenessValidator, options)
13
14
  end
14
15
  def i18n_scope
15
16
  :activerecord
@@ -1,5 +1,5 @@
1
1
  # Include this in every module that gets further included.
2
- module Reform::Form::Module
2
+ module Reform::Form::Module # Schema
3
3
  def self.included(base)
4
4
  base.extend ClassMethods
5
5
  base.extend Included
@@ -8,16 +8,17 @@ module Reform::Form::Module
8
8
  module Included # TODO: use representable's inheritance mechanism.
9
9
  def included(base)
10
10
  super
11
- @instructions.each { |cfg| base.send(cfg[0], *cfg[1], &cfg[2]) } # property :name, {} do .. end
11
+ instructions.each { |cfg|
12
+ args = cfg[1].dup
13
+ options = args.extract_options!.dup # we need to duplicate options has as AM::Validations messes it up later.
14
+
15
+ base.send(cfg[0], *args, options, &cfg[2]) } # property :name, {} do .. end
12
16
  end
13
17
  end
14
18
 
15
19
  module ClassMethods
16
- def property(*args, &block)
17
- instructions << [:property, args, block]
18
- end
19
- def validates(*args, &block)
20
- instructions << [:validates, args, block]
20
+ def method_missing(method, *args, &block)
21
+ instructions << [method, args, block]
21
22
  end
22
23
 
23
24
  def instructions
@@ -14,6 +14,7 @@ module Reform::Form::Save
14
14
  end
15
15
  end
16
16
 
17
+ # Returns the result of that save invocation on the model.
17
18
  def save(&block)
18
19
  # DISCUSS: we should never hit @mapper here (which writes to the models) when a block is passed.
19
20
  return deprecate_first_save_block_arg(&block) if block_given?
@@ -23,8 +24,9 @@ module Reform::Form::Save
23
24
  end
24
25
 
25
26
  def save!
26
- save_model
27
+ result = save_model
27
28
  mapper.new(fields).extend(RecursiveSave).to_hash # save! on all nested forms. # TODO: only include nested forms here.
29
+ result
28
30
  end
29
31
 
30
32
  def save_model
@@ -12,6 +12,8 @@ module Reform::Form::Validate
12
12
  :collection => attr[:collection], # TODO: Def#merge! doesn't consider :collection if it's already set in attr YET.
13
13
  :parse_strategy => :sync, # just use nested objects as they are.
14
14
 
15
+ # :getter grabs nested forms directly from fields bypassing the reader method which could possibly be overridden for presentation.
16
+ :getter => lambda { |options| fields.send(options.binding.name) },
15
17
  :deserialize => lambda { |object, params, args| object.update!(params) },
16
18
  )
17
19
 
@@ -59,6 +61,10 @@ module Reform::Form::Validate
59
61
  form = binding[:form].new(model) # free service: wrap model with Form. this usually happens in #setup.
60
62
 
61
63
  if binding.array?
64
+ # TODO: please extract this into Disposable.
65
+ fields = fields.send(:fields)
66
+
67
+ fields.send("#{binding.setter}", []) unless fields.send("#{binding.getter}") # DISCUSS: why do I have to initialize this here?
62
68
  fields.send("#{binding.getter}")[index] = form
63
69
  else
64
70
  fields.send("#{binding.setter}", form) # :setter is currently overwritten by :parse_strategy.
@@ -76,6 +82,8 @@ module Reform::Form::Validate
76
82
  super() # run the actual validation on self.
77
83
  end
78
84
 
85
+ # Some users use this method to pre-populate a form. Not saying this is right, but we'll keep
86
+ # this method here.
79
87
  def update!(params)
80
88
  deserialize!(params)
81
89
  end
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.1"
3
3
  end
@@ -19,7 +19,6 @@ require 'reform/active_record'
19
19
  # end
20
20
  # Artist.new(:name => "Racer X").save
21
21
 
22
-
23
22
  class ActiveRecordTest < MiniTest::Spec
24
23
  class SongForm < Reform::Form
25
24
  include Reform::Form::ActiveRecord
@@ -28,7 +27,7 @@ class ActiveRecordTest < MiniTest::Spec
28
27
  property :title
29
28
  property :created_at
30
29
 
31
- validates_uniqueness_of :title
30
+ validates_uniqueness_of :title, scope: [:album_id, :artist_id]
32
31
  validates :created_at, :presence => true # have another property to test if we mix up.
33
32
 
34
33
  property :artist do
@@ -37,7 +36,9 @@ class ActiveRecordTest < MiniTest::Spec
37
36
  end
38
37
  end
39
38
 
40
- let (:form) { SongForm.new(Song.new(:artist => Artist.new)) }
39
+ let(:album) { Album.create(:title => "Damnation") }
40
+ let(:artist) { Artist.create(:name => "Opeth") }
41
+ let(:form) { SongForm.new(Song.new(:artist => Artist.new)) }
41
42
 
42
43
  it { form.class.i18n_scope.must_equal :activerecord }
43
44
 
@@ -45,11 +46,20 @@ class ActiveRecordTest < MiniTest::Spec
45
46
  end
46
47
 
47
48
  # uniqueness
48
- it "is valid when name is unique" do
49
- form.validate("artist" => {"name" => "Paul Gilbert"}, "title" => "The Gargoyle", "created_at" => "November 6, 1966").must_equal true
49
+ it "is valid when title is unique for the same artist and album" do
50
+ form.validate("title" => "The Gargoyle", "artist_id" => artist.id, "album" => album.id, "created_at" => "November 6, 1966").must_equal true
51
+ end
52
+
53
+ it "is invalid when title is taken for the same artist and album" do
54
+ Song.create(title: "Windowpane", artist_id: artist.id, album_id: album.id)
55
+ form.validate("title" => "Windowpane", "artist_id" => artist.id, "album" => album).must_equal false
50
56
  end
51
57
 
52
58
  # nested object taken.
59
+ it "is valid when artist name is unique" do
60
+ form.validate("artist" => {"name" => "Paul Gilbert"}, "title" => "The Gargoyle", "created_at" => "November 6, 1966").must_equal true
61
+ end
62
+
53
63
  it "is invalid and shows error when taken" do
54
64
  Song.delete_all
55
65
  Artist.create(:name => "Racer X")
@@ -190,8 +200,6 @@ class PopulateWithActiveRecordTest < MiniTest::Spec
190
200
  # a.save
191
201
  # puts a.songs.inspect
192
202
 
193
-
194
-
195
203
  # b = a.songs.first
196
204
 
197
205
  # a.songs = [Song.new(title:"Biomag")]
data/test/builder_test.rb CHANGED
@@ -1,32 +1,32 @@
1
- require 'test_helper'
1
+ # require 'test_helper'
2
2
 
3
- class BuilderTest < MiniTest::Spec
4
- it do
5
- Builder.new.checkboxes(:settings, :hash => {"play" => true, "released" => false}).must_equal %{<input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />
6
- <input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />}
7
- end
8
- end
3
+ # class BuilderTest < MiniTest::Spec
4
+ # it do
5
+ # Builder.new.checkboxes(:settings, :hash => {"play" => true, "released" => false}).must_equal %{<input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />
6
+ # <input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />}
7
+ # end
8
+ # end
9
9
 
10
10
 
11
- require 'action_view/helpers/capture_helper'
12
- require 'action_view/helpers/tag_helper'
13
- require 'action_view/helpers/url_helper'
14
- require 'action_view/helpers/sanitize_helper'
15
- require 'action_view/helpers/text_helper'
16
- require 'action_view/helpers/form_tag_helper'
17
- require 'action_view/helpers/form_helper'
11
+ # require 'action_view/helpers/capture_helper'
12
+ # require 'action_view/helpers/tag_helper'
13
+ # require 'action_view/helpers/url_helper'
14
+ # require 'action_view/helpers/sanitize_helper'
15
+ # require 'action_view/helpers/text_helper'
16
+ # require 'action_view/helpers/form_tag_helper'
17
+ # require 'action_view/helpers/form_helper'
18
18
 
19
- class Builder
20
- include ActionView::Helpers::CaptureHelper
21
- include ActionView::Helpers::FormHelper
19
+ # class Builder
20
+ # include ActionView::Helpers::CaptureHelper
21
+ # include ActionView::Helpers::FormHelper
22
22
 
23
- # {name: value}
24
- def checkboxes(name, options)
25
- # get property form.to_a ? to_builder_hash or something like that
26
- options[:hash].collect do |id, value|
27
- ActionView::Helpers::InstanceTag.new(:object_name, :method, self).to_check_box_tag({:name => "yo"}, :checked_value, :unchecked_value)
23
+ # # {name: value}
24
+ # def checkboxes(name, options)
25
+ # # get property form.to_a ? to_builder_hash or something like that
26
+ # options[:hash].collect do |id, value|
27
+ # ActionView::Helpers::InstanceTag.new(:object_name, :method, self).to_check_box_tag({:name => "yo"}, :checked_value, :unchecked_value)
28
28
 
29
- # check_box_tag(id, value, checked = false, {})
30
- end.join("\n")
31
- end
32
- end
29
+ # # check_box_tag(id, value, checked = false, {})
30
+ # end.join("\n")
31
+ # end
32
+ # end
@@ -3,6 +3,8 @@ require 'representable/json'
3
3
 
4
4
  class DeserializeTest < BaseTest
5
5
  class AlbumContract < Reform::Form
6
+ include Reform::Form::ActiveModel::FormBuilderMethods # overrides #update!, too.
7
+
6
8
  self.representer_class.class_eval do
7
9
  include Representable::JSON
8
10
  end
@@ -33,8 +35,8 @@ class DeserializeTest < BaseTest
33
35
 
34
36
  let (:json) { '{"hit":{"title":"Sacrifice"},"title":"Second Heat","songs":[{"title":"Heart Of A Lion"}],"band":{"label":{"name":"Fat Wreck"}}}' }
35
37
 
36
- it {
38
+ it do
37
39
  subject.validate(json)
38
40
  subject.band.label.name.must_equal "Fat Wreck"
39
- }
41
+ end
40
42
  end
data/test/errors_test.rb CHANGED
@@ -119,4 +119,12 @@ class ErrorsTest < MiniTest::Spec
119
119
  it { form.title.must_equal "Second Heat" }
120
120
  it { form.songs.first.title.must_equal "Heart Of A Lion" }
121
121
  end
122
+
123
+
124
+ describe "Errors#to_s" do
125
+ before { form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
126
+
127
+ # to_s is aliased to messages
128
+ it { form.errors.to_s.must_equal "{:\"songs.title\"=>[\"can't be blank\"], :\"band.label.name\"=>[\"can't be blank\"]}" }
129
+ end
122
130
  end
data/test/inherit_test.rb CHANGED
@@ -34,6 +34,7 @@ class InheritTest < BaseTest
34
34
  end
35
35
 
36
36
 
37
+ require 'reform/form/coercion'
37
38
  class ModuleInclusionTest < MiniTest::Spec
38
39
  module BandPropertyForm
39
40
  include Reform::Form::Module
@@ -53,15 +54,35 @@ class ModuleInclusionTest < MiniTest::Spec
53
54
  end
54
55
 
55
56
  validates :band, :presence => true
57
+
58
+ property :cool, type: Virtus::Attribute::Boolean # test coercion.
59
+ end
60
+
61
+ # TODO: test if works, move stuff into inherit_schema!
62
+ module AirplaysPropertyForm
63
+ include Reform::Form::Module
64
+
65
+ collection :airplays do
66
+ property :station
67
+ validates :station, :presence => true
68
+ end
69
+ validates :airplays, :presence => true
56
70
  end
57
71
 
58
72
 
73
+ # test:
74
+ # by including BandPropertyForm into multiple classes we assure that options hashes don't get messed up by AM:V.
75
+ class HitForm < Reform::Form
76
+ include BandPropertyForm
77
+ end
78
+
59
79
  class SongForm < Reform::Form
60
80
  property :title
61
81
 
62
82
  include BandPropertyForm
63
83
  end
64
84
 
85
+
65
86
  let (:song) { OpenStruct.new(:band => OpenStruct.new(:title => "Time Again")) }
66
87
 
67
88
  # nested form from module is present and creates accessor.
@@ -71,9 +92,72 @@ class ModuleInclusionTest < MiniTest::Spec
71
92
  it { SongForm.new(song).id.must_equal 1 }
72
93
  it { SongForm.new(song).band.id.must_equal 2 }
73
94
 
95
+ # validators get inherited.
96
+ it do
97
+ form = SongForm.new(OpenStruct.new)
98
+ form.validate({})
99
+ form.errors.messages.must_equal({:band=>["can't be blank"]})
100
+ end
101
+
102
+ # coercion works
103
+ it do
104
+ form = SongForm.new(OpenStruct.new)
105
+ form.validate({:cool => "1"})
106
+ form.cool.must_equal true
107
+ end
108
+
109
+
110
+ # include a module into a module into a class :)
111
+ module AlbumFormModule
112
+ include Reform::Form::Module
113
+ include BandPropertyForm
114
+
115
+ property :name
116
+ validates :name, :presence => true
117
+ end
118
+
119
+ class AlbumForm < Reform::Form
120
+ include AlbumFormModule
121
+
122
+ property :band, :inherit => true do
123
+ property :label
124
+ validates :label, :presence => true
125
+ end
126
+ end
127
+ # puts "......"+ AlbumForm.representer_class.representable_attrs.get(:band).inspect
128
+
129
+ it do
130
+ form = AlbumForm.new(OpenStruct.new(:band => OpenStruct.new))
131
+ form.validate({"band" => {}})
132
+ form.errors.messages.must_equal({:"band.title"=>["can't be blank"], :"band.label"=>["can't be blank"], :name=>["can't be blank"]})
133
+ end
134
+
135
+
136
+ # including representer into form
137
+ module GenericRepresenter
138
+ include Representable
139
+
140
+ property :title
141
+ property :manager do
142
+ property :title
143
+ end
144
+ end
145
+
146
+ class LabelForm < Reform::Form
147
+ property :location
148
+
149
+ include GenericRepresenter
150
+ validates :title, :presence => true
151
+ property :manager, :inherit => true do
152
+ validates :title, :presence => true
153
+ end
154
+ end
155
+ puts "......"+ LabelForm.representer_class.representable_attrs.get(:title).inspect
156
+
157
+
74
158
  it do
75
- form = SongForm.new(OpenStruct.new())
76
- form.validate({})
77
- form.errors.messages.must_equal({:band=>["can't be blank"]})
159
+ form = LabelForm.new(OpenStruct.new(:manager => OpenStruct.new))
160
+ form.validate({"manager" => {}, "title"=>""}) # it's important to pass both nested and scalar here!
161
+ form.errors.messages.must_equal(:title=>["can't be blank"], :"manager.title"=>["can't be blank"], )
78
162
  end
79
163
  end
data/test/reform_test.rb CHANGED
@@ -69,14 +69,21 @@ class ReformTest < ReformSpec
69
69
 
70
70
 
71
71
  describe "::properties" do
72
+ let (:options) { {:type => String} }
73
+
72
74
  subject do
75
+ opts = options
73
76
  Class.new(Reform::Form) do
74
- properties [:name, :title]
77
+ properties [:name, :title], opts
78
+ properties [:created_at]
75
79
  end.new(comp)
76
80
  end
77
81
 
78
- it { subject.name.must_equal "Duran Duran" }
79
- it { subject.title.must_equal "Rio" }
82
+ it { subject.name.must_equal "Duran Duran" }
83
+ it { subject.title.must_equal "Rio" }
84
+ it { subject.created_at.must_equal nil }
85
+ # don't overwrite options.
86
+ it { subject; options.must_equal({:type => String}) }
80
87
  end
81
88
 
82
89
  class SongForm < Reform::Form
@@ -157,23 +164,6 @@ class ReformTest < ReformSpec
157
164
  form.errors.messages.must_equal({:name=>["can't be blank"], :title=>["can't be blank"]})
158
165
  end
159
166
  end
160
-
161
- describe "method validations" do
162
- it "allows accessing models" do
163
- form = Class.new(Reform::Form) do
164
- property :name
165
- validate "name_correct?"
166
-
167
- def name_correct?
168
- errors.add :name, "Please give me a name" if model.name.nil?
169
- end
170
- end.new(comp)
171
-
172
- form.validate({}).must_equal false
173
- form.errors.messages.must_equal({:name=>["Please give me a name"]})
174
- end
175
- end
176
-
177
167
  end
178
168
 
179
169
  describe "#errors" do
data/test/save_test.rb CHANGED
@@ -78,4 +78,12 @@ class SaveTest < BaseTest
78
78
  it { band.saved?.must_equal true }
79
79
  it { label.saved?.must_equal nil }
80
80
  end
81
+
82
+
83
+ # #save returns result (this goes into disposable soon).
84
+ it { subject.save.must_equal true }
85
+ it do
86
+ album.instance_eval { def save; false; end }
87
+ subject.save.must_equal false
88
+ end
81
89
  end
data/test/scalar_test.rb CHANGED
@@ -163,5 +163,23 @@ class SelfNestedTest < BaseTest
163
163
  form.validate({}).must_equal true
164
164
  end
165
165
 
166
+
167
+ it do
168
+ form = StringForm.new(AlbumCover.new(nil))
169
+ form.validate({"image"=>""}).must_equal true
170
+ end
171
+
172
+
173
+
174
+ # TODO: move to validate_test.
175
+ # property :rejection_reason, scalar: true, virtual: true, empty: true do # optional parameter
176
+ # validates length: {minimum: 5}, if: lambda { model.present? and avatar_moderation == "2" } # IF present, at least 5 characters.
177
+ # end
178
+ class BlaForm < Reform::Form
179
+ property :image# creates "empty" form
180
+ validates :image, :length => {:minimum => 10}, if: lambda { image and image != "" }
181
+ end
182
+
183
+ it { BlaForm.new(AlbumCover.new(nil)).validate({"image"=>""}).must_equal true }
166
184
  # DISCUSS: when AlbumCover.new("Hello").validate({}), does that fail?
167
185
  end
@@ -275,6 +275,66 @@ class ValidateTest < BaseTest
275
275
  it { subject.band.label.name.must_equal "Stiff" }
276
276
  it { subject.title.must_equal "House Of Fun" }
277
277
  end
278
+
279
+
280
+ # providing manual validator method allows accessing form's API.
281
+ describe "with ::validate" do
282
+ let (:form) {
283
+ Class.new(Reform::Form) do
284
+ property :title
285
+
286
+ validate :title?
287
+
288
+ def title?
289
+ errors.add :title, "not lowercase" if title == "Fallout"
290
+ end
291
+ end
292
+ }
293
+
294
+ let (:params) { {"title" => "Fallout"} }
295
+ let (:song) { Song.new("Englishman") }
296
+
297
+ subject { form.new(song) }
298
+
299
+ before { @res = subject.validate(params) }
300
+
301
+ it { @res.must_equal false }
302
+ it { subject.errors.messages.must_equal({:title=>["not lowercase"]}) }
303
+ end
304
+
305
+
306
+ # overriding the reader for a nested form should only be considered when rendering.
307
+ describe "with overridden reader for nested form" do
308
+ let (:form) {
309
+ Class.new(Reform::Form) do
310
+ property :band, :populate_if_empty => lambda { |*| Band.new } do
311
+ property :label
312
+ end
313
+
314
+ collection :songs, :populate_if_empty => lambda { |*| Song.new } do
315
+ property :title
316
+ end
317
+
318
+ def band
319
+ raise "only call me when rendering the form!"
320
+ end
321
+
322
+ def songs
323
+ raise "only call me when rendering the form!"
324
+ end
325
+ end.new(album)
326
+ }
327
+
328
+ let (:album) { Album.new }
329
+
330
+ # don't use #artist when validating!
331
+ it do
332
+ form.validate("band" => {"label" => "Hellcat"}, "songs" => [{"title" => "Stand Your Ground"}, {"title" => "Otherside"}])
333
+ form.sync
334
+ album.band.label.must_equal "Hellcat"
335
+ album.songs.first.title.must_equal "Stand Your Ground"
336
+ end
337
+ end
278
338
  end
279
339
 
280
340
  # #validate(params)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reform
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
@@ -9,90 +9,90 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-08-20 00:00:00.000000000 Z
12
+ date: 2014-09-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: representable
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ~>
18
+ - - "~>"
19
19
  - !ruby/object:Gem::Version
20
20
  version: 2.0.3
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - ~>
25
+ - - "~>"
26
26
  - !ruby/object:Gem::Version
27
27
  version: 2.0.3
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: disposable
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ~>
32
+ - - "~>"
33
33
  - !ruby/object:Gem::Version
34
34
  version: 0.0.5
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ~>
39
+ - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: 0.0.5
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: uber
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - ~>
46
+ - - "~>"
47
47
  - !ruby/object:Gem::Version
48
48
  version: 0.0.8
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - ~>
53
+ - - "~>"
54
54
  - !ruby/object:Gem::Version
55
55
  version: 0.0.8
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: activemodel
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - '>='
60
+ - - ">="
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - '>='
67
+ - - ">="
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: bundler
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - ~>
74
+ - - "~>"
75
75
  - !ruby/object:Gem::Version
76
76
  version: '1.3'
77
77
  type: :development
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
- - - ~>
81
+ - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '1.3'
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: rake
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
- - - '>='
88
+ - - ">="
89
89
  - !ruby/object:Gem::Version
90
90
  version: '0'
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
- - - '>='
95
+ - - ">="
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
98
  - !ruby/object:Gem::Dependency
@@ -113,70 +113,70 @@ dependencies:
113
113
  name: activerecord
114
114
  requirement: !ruby/object:Gem::Requirement
115
115
  requirements:
116
- - - '>='
116
+ - - ">="
117
117
  - !ruby/object:Gem::Version
118
118
  version: '0'
119
119
  type: :development
120
120
  prerelease: false
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
122
  requirements:
123
- - - '>='
123
+ - - ">="
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: sqlite3
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
- - - '>='
130
+ - - ">="
131
131
  - !ruby/object:Gem::Version
132
132
  version: '0'
133
133
  type: :development
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
- - - '>='
137
+ - - ">="
138
138
  - !ruby/object:Gem::Version
139
139
  version: '0'
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: virtus
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
- - - '>='
144
+ - - ">="
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
147
  type: :development
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
- - - '>='
151
+ - - ">="
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: rails
156
156
  requirement: !ruby/object:Gem::Requirement
157
157
  requirements:
158
- - - '>='
158
+ - - ">="
159
159
  - !ruby/object:Gem::Version
160
160
  version: '0'
161
161
  type: :development
162
162
  prerelease: false
163
163
  version_requirements: !ruby/object:Gem::Requirement
164
164
  requirements:
165
- - - '>='
165
+ - - ">="
166
166
  - !ruby/object:Gem::Version
167
167
  version: '0'
168
168
  - !ruby/object:Gem::Dependency
169
169
  name: actionpack
170
170
  requirement: !ruby/object:Gem::Requirement
171
171
  requirements:
172
- - - '>='
172
+ - - ">="
173
173
  - !ruby/object:Gem::Version
174
174
  version: '0'
175
175
  type: :development
176
176
  prerelease: false
177
177
  version_requirements: !ruby/object:Gem::Requirement
178
178
  requirements:
179
- - - '>='
179
+ - - ">="
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  description: Freeing your AR models from form logic.
@@ -187,8 +187,8 @@ executables: []
187
187
  extensions: []
188
188
  extra_rdoc_files: []
189
189
  files:
190
- - .gitignore
191
- - .travis.yml
190
+ - ".gitignore"
191
+ - ".travis.yml"
192
192
  - CHANGES.md
193
193
  - Gemfile
194
194
  - LICENSE.txt
@@ -279,12 +279,12 @@ require_paths:
279
279
  - lib
280
280
  required_ruby_version: !ruby/object:Gem::Requirement
281
281
  requirements:
282
- - - '>='
282
+ - - ">="
283
283
  - !ruby/object:Gem::Version
284
284
  version: '0'
285
285
  required_rubygems_version: !ruby/object:Gem::Requirement
286
286
  requirements:
287
- - - '>='
287
+ - - ">="
288
288
  - !ruby/object:Gem::Version
289
289
  version: '0'
290
290
  requirements: []