reform 1.1.0 → 1.1.1

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