reform 1.0.1 → 1.0.2

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: dbf6193ca27f1a4d6694b198771ab0bc3fec6459
4
- data.tar.gz: 797ddc8942d2fe5c7947833b4eb52d6d5dd7fc24
3
+ metadata.gz: 0d94ac5f49b9f57ec0fb4c215de22120c7dda916
4
+ data.tar.gz: c06cc0d203942de3501fc7a26ef653b26eb7ec36
5
5
  SHA512:
6
- metadata.gz: ba03b87c0652c7daff1b3c7a5cbb3d2807f2a70f0333bda323e6da7319be22907a9d11614d3c2d6c5064f02d0c93e2957bdf194e342cb5937f1667b85093ca26
7
- data.tar.gz: 259e07b615f263a630891ae34400cf9d2aacc966fa50c0f666b49b3047cd4cd82d11563f2ea8376e560ea70534d58973d21bcc61d90487bda3570e83d47e1c9b
6
+ metadata.gz: d601af3a2a19bc070cbb84f0dc8bfd160ad2f839cf00054f8ad98e655dbce6e254bccd9541f9612ae1bf4a6acf6e1e3482f4319610c327db0701a4cec508a0f5
7
+ data.tar.gz: f37b1d69a3240b8a3e153e286b6e1130afbf6557ff3d9a5faf7adf786573bdc52b7a8cb7ac0d2649c1a3710623e96d1b6bb70335b3688a6189fc56f7f80a5777
data/CHANGES.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 1.0.2
2
+
3
+ * The following property names are reserved and will raise an exception: `[:model, :aliased_model, :fields, :mapper]`
4
+ * You get warned now when overriding accessors for your properties:
5
+
6
+ ```ruby
7
+ property :title
8
+
9
+ def title
10
+ super.upcase
11
+ end
12
+ ```
13
+
14
+ This is because in Reform 1.1, those accessors will only be used when rendering the form, e.g. when doing `= @form.title`. If you override the accessors for presentation, only, you're fine. Add `presentation_accessors: true` to any property, the warnings will be suppressed and everything's gonna work. You may remove `presentation_accessors: true` in 1.1, but it won't affect the form.
15
+
16
+ However, if you used to override `#title` or `#title=` to manipulate incoming data, this is no longer working in 1.1. The reason for this is to make Reform cleaner. You will get two options `:validate_processor` and `:sync_processor` in order to filter data when calling `#validate` and when syncing data back to the model with `#sync` or `#save`.
17
+
1
18
  ## 1.0.1
2
19
 
3
20
  * Deprecated model readers for `Composition` and `ActiveModel`. Consider the following setup.
@@ -26,6 +43,7 @@
26
43
 
27
44
  This is gonna be **removed in 1.1**.
28
45
 
46
+
29
47
  ## 1.0.0
30
48
 
31
49
  * Removed `Form::DSL` in favour of `Form::Composition`.
data/database.sqlite3 CHANGED
Binary file
@@ -15,6 +15,8 @@ module Reform
15
15
  inheritable_attr :features
16
16
  self.features = []
17
17
 
18
+ RESERVED_METHODS = [:model, :aliased_model, :fields, :mapper] # TODO: refactor that so we don't need that.
19
+
18
20
 
19
21
  module PropertyMethods
20
22
  extend Forwardable
@@ -54,6 +56,8 @@ module Reform
54
56
 
55
57
  private
56
58
  def create_accessor(name)
59
+ handle_reserved_names(name)
60
+
57
61
  # Make a module that contains these very accessors, then include it
58
62
  # so they can be overridden but still are callable with super.
59
63
  accessors = Module.new do
@@ -62,6 +66,10 @@ module Reform
62
66
  end
63
67
  include accessors
64
68
  end
69
+
70
+ def handle_reserved_names(name)
71
+ raise "[Reform] the property name '#{name}' is reserved, please consider something else using :as." if RESERVED_METHODS.include?(name)
72
+ end
65
73
  end
66
74
  extend PropertyMethods
67
75
 
data/lib/reform/form.rb CHANGED
@@ -7,11 +7,6 @@ module Reform
7
7
  class Form < Contract
8
8
  self.representer_class = Reform::Representer.for(:form_class => self)
9
9
 
10
- def aliased_model
11
- # TODO: cache the Expose.from class!
12
- Reform::Expose.from(mapper).new(:model => model)
13
- end
14
-
15
10
  require "reform/form/virtual_attributes"
16
11
 
17
12
  require 'reform/form/validate'
@@ -23,5 +18,11 @@ module Reform
23
18
 
24
19
  require 'reform/form/multi_parameter_attributes'
25
20
  include MultiParameterAttributes # TODO: make features dynamic.
21
+
22
+ private
23
+ def aliased_model
24
+ # TODO: cache the Expose.from class!
25
+ Reform::Expose.from(mapper).new(:model => model)
26
+ end
26
27
  end
27
28
  end
@@ -59,10 +59,6 @@ module Reform::Form::Composition
59
59
  super(composition)
60
60
  end
61
61
 
62
- def aliased_model # we don't need an Expose as we save the Composition instance in the constructor.
63
- model
64
- end
65
-
66
62
  def to_nested_hash
67
63
  model.nested_hash_for(to_hash) # use composition to compute nested hash.
68
64
  end
@@ -70,4 +66,9 @@ module Reform::Form::Composition
70
66
  def to_hash(*args)
71
67
  mapper.new(self).to_hash(*args)
72
68
  end
69
+
70
+ private
71
+ def aliased_model # we don't need an Expose as we save the Composition instance in the constructor.
72
+ model
73
+ end
73
74
  end
@@ -1,61 +1,61 @@
1
- Reform::Form.class_eval do
2
- module Save
3
- module RecursiveSave
4
- def to_hash(*)
5
- # process output from InputRepresenter {title: "Mint Car", hit: <Form>}
6
- # and just call sync! on nested forms.
7
- nested_forms do |attr|
8
- attr.merge!(
9
- :instance => lambda { |fragment, *| fragment },
10
- :serialize => lambda { |object, args| object.save! unless args.binding[:save] === false },
11
- )
12
- end
13
-
14
- super
1
+ module Reform::Form::Save
2
+ module RecursiveSave
3
+ def to_hash(*)
4
+ # process output from InputRepresenter {title: "Mint Car", hit: <Form>}
5
+ # and just call sync! on nested forms.
6
+ nested_forms do |attr|
7
+ attr.merge!(
8
+ :instance => lambda { |fragment, *| fragment },
9
+ :serialize => lambda { |object, args| object.save! unless args.binding[:save] === false },
10
+ )
15
11
  end
16
- end
17
-
18
- def save
19
- # DISCUSS: we should never hit @mapper here (which writes to the models) when a block is passed.
20
- return yield self, to_nested_hash if block_given?
21
12
 
22
- sync_models # recursion
23
- save!
13
+ super
24
14
  end
15
+ end
25
16
 
26
- def save!
27
- save_model
28
- mapper.new(self).extend(RecursiveSave).to_hash # save! on all nested forms.
29
- end
17
+ def save
18
+ # DISCUSS: we should never hit @mapper here (which writes to the models) when a block is passed.
19
+ return yield self, to_nested_hash if block_given?
30
20
 
31
- def save_model
32
- model.save # TODO: implement nested (that should really be done by Twin/AR).
33
- end
21
+ sync_models # recursion
22
+ save!
23
+ end
24
+
25
+ def save!
26
+ save_model
27
+ mapper.new(self).extend(RecursiveSave).to_hash # save! on all nested forms.
28
+ end
34
29
 
30
+ def save_model
31
+ model.save # TODO: implement nested (that should really be done by Twin/AR).
32
+ end
35
33
 
36
- module NestedHash
37
- def to_hash(*)
38
- # Transform form data into a nested hash for #save.
39
- nested_forms do |attr|
40
- attr.merge!(
41
- :instance => lambda { |fragment, *| fragment },
42
- :serialize => lambda { |object, args| object.to_nested_hash },
43
- )
44
- end
45
34
 
46
- representable_attrs.each do |attr|
47
- attr.merge!(:as => attr[:private_name] || attr.name)
48
- end
35
+ module NestedHash
36
+ def to_hash(*)
37
+ # Transform form data into a nested hash for #save.
38
+ nested_forms do |attr|
39
+ attr.merge!(
40
+ :instance => lambda { |fragment, *| fragment },
41
+ :serialize => lambda { |object, args| object.to_nested_hash },
42
+ )
43
+ end
49
44
 
50
- super
45
+ representable_attrs.each do |attr|
46
+ attr.merge!(:as => attr[:private_name] || attr.name)
51
47
  end
48
+
49
+ super
52
50
  end
51
+ end
53
52
 
54
- require "active_support/hash_with_indifferent_access" # DISCUSS: replace?
55
- def to_nested_hash
56
- map = mapper.new(self).extend(Save::NestedHash)
53
+ require "active_support/hash_with_indifferent_access" # DISCUSS: replace?
54
+ def to_nested_hash
55
+ source = deprecate_potential_readers_used_in_sync_or_save(fields) # TODO: remove in 1.1.
57
56
 
58
- ActiveSupport::HashWithIndifferentAccess.new(map.to_hash)
59
- end
57
+ map = mapper.new(source).extend(NestedHash)
58
+
59
+ ActiveSupport::HashWithIndifferentAccess.new(map.to_hash)
60
60
  end
61
- end
61
+ end
@@ -1,60 +1,98 @@
1
- Reform::Form.class_eval do
2
- # #sync!
3
- # 1. assign scalars to model (respecting virtual, excluded attributes)
4
- # 2. call sync! on nested
5
- module Sync
6
- # Mechanics for writing input to model.
7
- # Writes input to model.
8
- module Writer
9
- def from_hash(*)
10
- # process output from InputRepresenter {title: "Mint Car", hit: <Form>}
11
- # and just call sync! on nested forms.
12
- nested_forms do |attr|
13
- attr.merge!(
14
- :instance => lambda { |fragment, *| fragment },
15
- :deserialize => lambda { |object, *| object.sync! },
16
- :setter => lambda { |*| } # don't write hit=<Form>.
17
- )
18
- end
19
-
20
- super
1
+ # #sync!
2
+ # 1. assign scalars to model (respecting virtual, excluded attributes)
3
+ # 2. call sync! on nested
4
+ module Reform::Form::Sync
5
+ # Mechanics for writing input to model.
6
+ # Writes input to model.
7
+ module Writer
8
+ def from_hash(*)
9
+ # process output from InputRepresenter {title: "Mint Car", hit: <Form>}
10
+ # and just call sync! on nested forms.
11
+ nested_forms do |attr|
12
+ attr.merge!(
13
+ :instance => lambda { |fragment, *| fragment },
14
+ :deserialize => lambda { |object, *| object.sync! },
15
+ :setter => lambda { |*| } # don't write hit=<Form>.
16
+ )
21
17
  end
22
- end
23
18
 
24
- # Transforms form input into what actually gets written to model.
25
- # output: {title: "Mint Car", hit: <Form>}
26
- module InputRepresenter
27
- include Reform::Representer::WithOptions
28
- # TODO: make dynamic.
29
- include Reform::Form::EmptyAttributesOptions
30
- include Reform::Form::ReadonlyAttributesOptions
19
+ super
20
+ end
21
+ end
31
22
 
32
- def to_hash(*)
33
- nested_forms do |attr|
34
- attr.merge!(
35
- :representable => false,
36
- :prepare => lambda { |obj, *| obj }
37
- )
23
+ # Transforms form input into what actually gets written to model.
24
+ # output: {title: "Mint Car", hit: <Form>}
25
+ module InputRepresenter
26
+ include Reform::Representer::WithOptions
27
+ # TODO: make dynamic.
28
+ include Reform::Form::EmptyAttributesOptions
29
+ include Reform::Form::ReadonlyAttributesOptions
38
30
 
39
- end
31
+ def to_hash(*)
32
+ nested_forms do |attr|
33
+ attr.merge!(
34
+ :representable => false,
35
+ :prepare => lambda { |obj, *| obj }
36
+ )
40
37
 
41
- super
42
38
  end
39
+
40
+ super
43
41
  end
44
42
  end
45
43
 
46
- ### TODO: add ToHash with :prepare => lambda { |form, args| form },
44
+
45
+ ### TODO: add ToHash with :prepare => lambda { |form, args| form },
47
46
 
48
47
  def sync_models
49
48
  sync!
50
49
  end
51
50
  alias_method :sync, :sync_models
52
51
 
52
+ # reading from fields allows using readers in form for presentation
53
+ # and writers still pass to fields in #validate????
53
54
  def sync! # semi-public.
54
- input_representer = mapper.new(self).extend(Sync::InputRepresenter)
55
+ source = deprecate_potential_readers_used_in_sync_or_save(fields) # TODO: remove in 1.1.
56
+
57
+ input_representer = mapper.new(source).extend(InputRepresenter) # FIXME: take values from self.fields!
55
58
 
56
59
  input = input_representer.to_hash
57
60
 
58
- mapper.new(aliased_model).extend(Sync::Writer).from_hash(input)
61
+ mapper.new(aliased_model).extend(Writer).from_hash(input)
62
+ end
63
+
64
+ def deprecate_potential_readers_used_in_sync_or_save(fields) # TODO: remove in 1.1.
65
+ readers = []
66
+
67
+ mapper.representable_attrs.each do |definition|
68
+ return fields if definition[:presentation_accessors]
69
+
70
+ name = definition.name
71
+ next if method(name).source_location.inspect =~ /forwardable/ # defined by Reform, not overridden by user.
72
+
73
+ readers << name
74
+ end
75
+ return fields if readers.size == 0
76
+
77
+ warn "[Reform] Deprecation: You're overriding the following readers: #{readers.join(', ')}. In Reform 1.1, those readers will be used for presentation in the view, only. In case you are using the readers deliberately to modify incoming data for #save or #sync: this won't work anymore. If you just use the custom readers in the form view, add `presentation_accessors: true` to a property to suppress this message and use the new behaviour."
78
+
79
+ self # old mode
80
+ end
81
+ def deprecate_potential_writers_used_in_validate(fields) # TODO: remove in 1.1.
82
+ readers = []
83
+
84
+ mapper.representable_attrs.each do |definition|
85
+ return fields if definition[:presentation_accessors]
86
+
87
+ name = definition.setter
88
+ next if method(name).source_location.inspect =~ /forwardable/ # defined by Reform, not overridden by user.
89
+
90
+ readers << name
91
+ end
92
+ return fields if readers.size == 0
93
+
94
+ warn "[Reform] Deprecation: You're overriding the following writers: #{readers.join(', ')}. In Reform 1.1, those writers will be used for presentation in the view, only. In case you are using the writers deliberately to modify incoming data for #setup or #validate: this won't work anymore. Add `presentation_accessors: true` to a property to suppress this message and use the new behaviour."
95
+
96
+ self # old mode
59
97
  end
60
- end
98
+ end
@@ -21,7 +21,7 @@ module Reform::Form::Validate
21
21
  module Populator
22
22
  class PopulateIfEmpty
23
23
  def initialize(*args)
24
- @form, @fragment, args = args
24
+ @fields, @fragment, args = args
25
25
  @index = args.first
26
26
  @args = args.last
27
27
  end
@@ -30,12 +30,15 @@ module Reform::Form::Validate
30
30
  binding = @args.binding
31
31
  form = binding.get
32
32
 
33
+ parent_form = @args.user_options[:parent_form]
34
+ form_model = parent_form.model # FIXME: sort out who's responsible for sync.
35
+
33
36
  return if binding.array? and form and form[@index] # TODO: this should be handled by the Binding.
34
37
  return if !binding.array? and form
35
38
  # only get here when above form is nil.
36
39
 
37
40
  if binding[:populate_if_empty].is_a?(Proc)
38
- model = @form.instance_exec(@fragment, @args, &binding[:populate_if_empty]) # call user block.
41
+ model = parent_form.instance_exec(@fragment, @args, &binding[:populate_if_empty]) # call user block.
39
42
  else
40
43
  model = binding[:populate_if_empty].new
41
44
  end
@@ -43,17 +46,17 @@ module Reform::Form::Validate
43
46
  form = binding[:form].new(model) # free service: wrap model with Form. this usually happens in #setup.
44
47
 
45
48
  if binding.array?
46
- @form.model.send("#{binding.getter}") << model # FIXME: i don't like this, but we have to add the model to the parent object to make associating work. i have to use #<< to stay compatible with AR's has_many API. DISCUSS: what happens when we get out-of-sync here?
47
- @form.send("#{binding.getter}")[@index] = form
49
+ form_model.send("#{binding.getter}") << model # FIXME: i don't like this, but we have to add the model to the parent object to make associating work. i have to use #<< to stay compatible with AR's has_many API. DISCUSS: what happens when we get out-of-sync here?
50
+ @fields.send("#{binding.getter}")[@index] = form
48
51
  else
49
- @form.model.send("#{binding.setter}", model) # FIXME: i don't like this, but we have to add the model to the parent object to make associating work.
50
- @form.send("#{binding.setter}", form) # :setter is currently overwritten by :parse_strategy.
52
+ form_model.send("#{binding.setter}", model) # FIXME: i don't like this, but we have to add the model to the parent object to make associating work.
53
+ @fields.send("#{binding.setter}", form) # :setter is currently overwritten by :parse_strategy.
51
54
  end
52
55
  end
53
56
  end # PopulateIfEmpty
54
57
 
55
58
 
56
- def from_hash(params, *args)
59
+ def from_hash(params, args)
57
60
  populated_attrs = []
58
61
 
59
62
  nested_forms do |attr|
@@ -78,7 +81,7 @@ module Reform::Form::Validate
78
81
  populated_attrs << attr.name.to_sym
79
82
  end
80
83
 
81
- super(params, {:include => populated_attrs})
84
+ super(params, {:include => populated_attrs}.merge(args))
82
85
  end
83
86
  end
84
87
 
@@ -90,15 +93,20 @@ module Reform::Form::Validate
90
93
  end
91
94
 
92
95
  def update!(params)
93
- # puts "updating in #{self.class.name}"
94
96
  populate!(params)
95
-
96
- mapper.new(self).extend(Update).from_hash(params)
97
+ deserialize!(params)
97
98
  end
98
99
 
99
100
  private
100
101
  def populate!(params)
101
- mapper.new(self).extend(Populator).from_hash(params)
102
+ target = deprecate_potential_writers_used_in_validate(fields) # TODO: remove in 1.1.
103
+
104
+ mapper.new(target).extend(Populator).from_hash(params, :parent_form => self) # TODO: remove model(form) once we found out how to synchronize the model correctly. see https://github.com/apotonick/reform/issues/86#issuecomment-43402047
102
105
  end
103
106
 
107
+ def deserialize!(params)
108
+ target = deprecate_potential_writers_used_in_validate(fields) # TODO: remove in 1.1.
109
+
110
+ mapper.new(target).extend(Update).from_hash(params)
111
+ end
104
112
  end
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.2"
3
3
  end
@@ -14,6 +14,7 @@ class FormCompositionTest < MiniTest::Spec
14
14
  # property :channel # FIXME: what about the "main model"?
15
15
  property :channel, :empty => true, :on => :song
16
16
  property :requester, :on => :requester, :skip_accessors => true
17
+ property :captcha, :on => :song, :empty => true
17
18
 
18
19
  validates :name, :title, :channel, :presence => true
19
20
  end
@@ -30,6 +31,7 @@ class FormCompositionTest < MiniTest::Spec
30
31
  it { form.requester_id.must_equal 2 }
31
32
  it { form.channel.must_equal nil }
32
33
  it { form.requester.must_equal "MCP" } # same name as composed model.
34
+ it { form.captcha.must_equal nil }
33
35
 
34
36
  # [DEPRECATED] # TODO: remove in 1.2.
35
37
  # delegation form -> composed models (e.g. when saving this can be handy)
@@ -61,7 +63,7 @@ class FormCompositionTest < MiniTest::Spec
61
63
  end
62
64
 
63
65
  it "provides nested symbolized hash as second block argument" do
64
- form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "channel" => "JJJ")
66
+ form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "channel" => "JJJ", "captcha" => "wonderful")
65
67
 
66
68
  hash = {}
67
69
 
@@ -69,14 +71,19 @@ class FormCompositionTest < MiniTest::Spec
69
71
  hash = map
70
72
  end
71
73
 
72
- hash.must_equal({:song=>{:title=>"Greyhound", :id=>1, :channel => "JJJ"}, :requester=>{:name=>"Frenzal Rhomb", :id=>2, :requester => "MCP"}})
74
+ hash.must_equal({
75
+ :song=>{:title=>"Greyhound", :id=>1, :channel => "JJJ", :captcha=>"wonderful"},
76
+ :requester=>{:name=>"Frenzal Rhomb", :id=>2, :requester => "MCP"}}
77
+ )
73
78
  end
74
79
 
75
80
  it "pushes data to models and calls #save when no block passed" do
76
81
  song.extend(Saveable)
77
82
  requester.extend(Saveable)
78
83
 
79
- form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb")
84
+ form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "captcha" => "1337")
85
+ form.captcha.must_equal "1337" # TODO: move to separate test.
86
+
80
87
  form.save
81
88
 
82
89
  requester.name.must_equal "Frenzal Rhomb"
data/test/reform_test.rb CHANGED
@@ -57,6 +57,17 @@ class ReformTest < ReformSpec
57
57
  let (:form) { SongForm.new(comp) }
58
58
 
59
59
 
60
+ describe "::property" do
61
+ it "doesn't allow reserved names" do
62
+ assert_raises RuntimeError do
63
+ Class.new(Reform::Form) do
64
+ property :model
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+
60
71
  describe "::properties" do
61
72
  subject do
62
73
  Class.new(Reform::Form) do
@@ -295,19 +306,96 @@ class ReadonlyAttributesTest < MiniTest::Spec
295
306
  end
296
307
  end
297
308
 
298
- class OverridingAccessorsTest < MiniTest::Spec
309
+
310
+ # TODO: formatter: lambda { |args| 1 }
311
+ # to define reader for presentation layer (e.g. default value for #weight).
312
+ class OverridingAccessorsTest < BaseTest
299
313
  class SongForm < Reform::Form
300
- property :title
314
+ property :title, :presentation_accessors => true
301
315
 
302
316
  def title=(v)
303
- super v.upcase
317
+ super v*2
318
+ end
319
+
320
+ def title
321
+ super.downcase
322
+ end
323
+ end
324
+
325
+ let (:song) { Song.new("Pray") }
326
+ subject { SongForm.new(song) }
327
+
328
+ # override reader for presentation.
329
+ it { subject.title.must_equal "pray" }
330
+
331
+ # overridden writer only works when called explicitely.
332
+ it do
333
+ subject.title = "Swing Life Away"
334
+ subject.title.must_equal "swing life awayswing life away"
335
+ end
336
+
337
+
338
+ describe "#save" do
339
+ before { subject.validate("title" => "Hey Little World") }
340
+
341
+ # for presentation, always use overridden accessor
342
+ it { subject.title.must_equal "hey little world" }
343
+
344
+ # the reader is not used when saving/syncing.
345
+ it do
346
+ subject.save do |f, hash|
347
+ hash["title"].must_equal "Hey Little World"
348
+ end
349
+ end
350
+
351
+ # the reader is not used when saving/syncing.
352
+ it do
353
+ song.extend(Saveable)
354
+ subject.save
355
+ song.title.must_equal "Hey Little World"
356
+ end
357
+ end
358
+ end
359
+
360
+
361
+ class OLDOverridingAccessorsTest < BaseTest # TODO: remove in 1.1
362
+ class SongForm < Reform::Form
363
+ property :title
364
+
365
+ def title=(v) # used in #validate.
366
+ super v*2
367
+ end
368
+
369
+ def title # used in #sync.
370
+ super.downcase
304
371
  end
305
372
  end
306
373
 
374
+ let (:song) { Song.new("Pray") }
375
+ subject { SongForm.new(song) }
376
+
377
+ # override reader for presentation.
378
+ it { subject.title.must_equal "pray" }
379
+
380
+
381
+ describe "#save" do
382
+ before { subject.validate("title" => "Hey Little World") }
383
+
384
+ # reader always used
385
+ it { subject.title.must_equal "hey little worldhey little world" }
307
386
 
308
- it "allows overriding accessors while keeping super" do
309
- form = SongForm.new(OpenStruct.new)
310
- form.validate("title" => "Hey Little World")
311
- form.title.must_equal "HEY LITTLE WORLD"
387
+ # the reader is not used when saving/syncing.
388
+ it do
389
+ subject.save do |f, hash|
390
+ hash["title"].must_equal "hey little worldhey little world"
391
+ end
392
+ end
393
+
394
+ # reader and writer used when saving/syncing.
395
+ it do
396
+ song.extend(Saveable)
397
+ subject.save
398
+ song.title.must_equal "hey little worldhey little world"
399
+ end
312
400
  end
313
401
  end
@@ -57,6 +57,60 @@ class ValidateTest < BaseTest
57
57
  it { subject.hit.title.must_equal "Roxanne" }
58
58
  end
59
59
 
60
+
61
+ describe ":populator, half-populated collection" do
62
+ let (:form) {
63
+ Class.new(Reform::Form) do
64
+ collection :songs, :populator => lambda { |fragment, index, args|
65
+ songs[index] or songs[index] = args.binding[:form].new(Song.new)
66
+ } do
67
+ property :title
68
+ end
69
+ end
70
+ }
71
+
72
+ let (:params) {
73
+ {
74
+ "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}]
75
+ }
76
+ }
77
+ let (:song) { Song.new("Englishman") }
78
+
79
+ subject { form.new(Album.new("Hits", nil, [song])) }
80
+
81
+ before { subject.validate(params) }
82
+
83
+ it { subject.songs[0].model.object_id.must_equal song.object_id } # this song was existing before.
84
+ it { subject.songs[0].title.must_equal "Fallout" }
85
+ it { subject.songs[1].title.must_equal "Roxanne" }
86
+ end
87
+
88
+ describe ":populate_if_empty, half-populated collection" do
89
+ let (:form) {
90
+ Class.new(Reform::Form) do
91
+ collection :songs, :populate_if_empty => Song do
92
+ property :title
93
+ end
94
+ end
95
+ }
96
+
97
+ let (:params) {
98
+ {
99
+ "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}]
100
+ }
101
+ }
102
+ let (:song) { Song.new("Englishman") }
103
+
104
+ subject { form.new(Album.new("Hits", nil, [song])) }
105
+
106
+ before { subject.validate(params) }
107
+
108
+ it { subject.songs[0].model.object_id.must_equal song.object_id } # this song was existing before.
109
+ it { subject.songs[0].title.must_equal "Fallout" }
110
+ it { subject.songs[1].title.must_equal "Roxanne" }
111
+ end
112
+
113
+
60
114
  describe ":populate_if_empty" do
61
115
  let (:form) {
62
116
  Class.new(Reform::Form) do
@@ -125,6 +179,7 @@ class ValidateTest < BaseTest
125
179
  end
126
180
 
127
181
 
182
+
128
183
  # test cardinalities.
129
184
  describe "with empty collection and cardinality" do
130
185
  let (:album) { Album.new }
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.0.1
4
+ version: 1.0.2
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-05-21 00:00:00.000000000 Z
12
+ date: 2014-06-07 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: 1.8.1
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: 1.8.1
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.4
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.4
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.4
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.4
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: 10.1.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: 10.1.0
98
98
  - !ruby/object:Gem::Dependency
@@ -113,56 +113,56 @@ 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
  description: Freeing your AR models from form logic.
@@ -173,8 +173,8 @@ executables: []
173
173
  extensions: []
174
174
  extra_rdoc_files: []
175
175
  files:
176
- - ".gitignore"
177
- - ".travis.yml"
176
+ - .gitignore
177
+ - .travis.yml
178
178
  - CHANGES.md
179
179
  - Gemfile
180
180
  - LICENSE.txt
@@ -258,17 +258,17 @@ require_paths:
258
258
  - lib
259
259
  required_ruby_version: !ruby/object:Gem::Requirement
260
260
  requirements:
261
- - - ">="
261
+ - - '>='
262
262
  - !ruby/object:Gem::Version
263
263
  version: '0'
264
264
  required_rubygems_version: !ruby/object:Gem::Requirement
265
265
  requirements:
266
- - - ">="
266
+ - - '>='
267
267
  - !ruby/object:Gem::Version
268
268
  version: '0'
269
269
  requirements: []
270
270
  rubyforge_project:
271
- rubygems_version: 2.2.1
271
+ rubygems_version: 2.2.2
272
272
  signing_key:
273
273
  specification_version: 4
274
274
  summary: Decouples your models from form by giving you form objects with validation,
@@ -315,4 +315,3 @@ test_files:
315
315
  - test/sync_test.rb
316
316
  - test/test_helper.rb
317
317
  - test/validate_test.rb
318
- has_rdoc: