reform 1.0.1 → 1.0.2

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: 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: