reform 0.2.7 → 1.0.0

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.
@@ -1,10 +1,9 @@
1
- module Reform
2
- class Form
1
+ class Reform::Form < Reform::Contract
3
2
  # TODO: this should be in Representer namespace.
4
3
  module EmptyAttributesOptions
5
4
  def options
6
5
  empty_fields = representable_attrs.
7
- find_all { |d| d.options[:empty] }.
6
+ find_all { |d| d[:empty] }.
8
7
  collect { |d| d.name.to_sym }
9
8
 
10
9
  super.exclude!(empty_fields)
@@ -14,11 +13,10 @@ module Reform
14
13
  module ReadonlyAttributesOptions
15
14
  def options
16
15
  readonly_fields = representable_attrs.
17
- find_all { |d| d.options[:virtual] }.
16
+ find_all { |d| d[:virtual] }.
18
17
  collect { |d| d.name.to_sym }
19
18
 
20
19
  super.exclude!(readonly_fields)
21
20
  end
22
21
  end
23
- end
24
22
  end
@@ -3,6 +3,12 @@ require 'representable/decorator'
3
3
 
4
4
  module Reform
5
5
  class Representer < Representable::Decorator
6
+ include Representable::Hash::AllowSymbols
7
+
8
+ extend Uber::InheritableAttr
9
+ inheritable_attr :options
10
+ # self.options = {}
11
+
6
12
  # Invokes #to_hash and/or #from_hash with #options. This provides a hook for other
7
13
  # modules to add options for the representational process.
8
14
  module WithOptions
@@ -42,11 +48,16 @@ module Reform
42
48
 
43
49
  def nested_forms(&block)
44
50
  clone_config!.
45
- find_all { |attr| attr.options[:form] }.
46
- collect { |attr| [attr, represented.send(attr.getter)] }. # DISCUSS: can't we do this with the Binding itself?
51
+ find_all { |attr| attr[:form] }.
47
52
  each(&block)
48
53
  end
49
54
 
55
+ def self.for(options)
56
+ clone.tap do |representer|
57
+ representer.options = options
58
+ end
59
+ end
60
+
50
61
  def self.clone # called in inheritable_attr :representer_class.
51
62
  Class.new(self) # By subclassing, representable_attrs.clone is called.
52
63
  end
@@ -62,7 +73,10 @@ module Reform
62
73
  def self.inline_representer(base_module, name, options, &block)
63
74
  name = name.to_s.singularize.camelize
64
75
 
65
- Class.new(Form) do
76
+ Class.new(self.options[:form_class]) do
77
+ # TODO: this will soon become a generic feature in representable.
78
+ include *options[:features].reverse if options[:features]
79
+
66
80
  instance_exec &block
67
81
 
68
82
  @form_name = name
@@ -1,3 +1,3 @@
1
1
  module Reform
2
- VERSION = "0.2.7"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -18,7 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "representable", "~> 1.7.5"
21
+ spec.add_dependency "representable", "~> 1.8.1"
22
+ spec.add_dependency "disposable", "~> 0.0.3"
22
23
  spec.add_dependency "uber", "~> 0.0.4"
23
24
  spec.add_dependency "activemodel"
24
25
  spec.add_development_dependency "bundler", "~> 1.3"
@@ -94,98 +94,6 @@ class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
94
94
  end
95
95
  end
96
96
 
97
- class FormBuilderCompatTest < MiniTest::Spec
98
- let (:form_class) {
99
- Class.new(Reform::Form) do
100
- include Reform::Form::ActiveModel::FormBuilderMethods
101
-
102
- property :artist do
103
- property :name
104
- validates :name, :presence => true
105
- end
106
-
107
- collection :songs do
108
- property :title
109
- property :release_date
110
- validates :title, :presence => true
111
- end
112
-
113
- class LabelForm < Reform::Form
114
- property :name
115
- end
116
-
117
- property :label, :form => LabelForm
118
- end
119
- }
120
-
121
- let (:song) { OpenStruct.new }
122
- let (:form) { form_class.new(OpenStruct.new(
123
- :artist => Artist.new(:name => "Propagandhi"),
124
- :songs => [song],
125
- :label => OpenStruct.new)) }
126
-
127
- it "respects _attributes params hash" do
128
- form.validate("artist_attributes" => {"name" => "Blink 182"},
129
- "songs_attributes" => {"0" => {"title" => "Damnit"}})
130
-
131
- form.artist.name.must_equal "Blink 182"
132
- form.songs.first.title.must_equal "Damnit"
133
- end
134
-
135
- it "allows nested collection and property to be missing" do
136
- form.validate({})
137
-
138
- form.artist.name.must_equal "Propagandhi"
139
-
140
- form.songs.size.must_equal 1
141
- form.songs[0].model.must_equal song # this is a weird test.
142
- end
143
-
144
- it "defines _attributes= setter so Rails' FB works properly" do
145
- form.must_respond_to("artist_attributes=")
146
- form.must_respond_to("songs_attributes=")
147
- form.must_respond_to("label_attributes=")
148
- end
149
-
150
- describe "deconstructed date parameters" do
151
- let(:form_attributes) do
152
- {
153
- "artist_attributes" => {"name" => "Blink 182"},
154
- "songs_attributes" => {"0" => {"title" => "Damnit", "release_date(1i)" => release_year,
155
- "release_date(2i)" => release_month, "release_date(3i)" => release_day}}
156
- }
157
- end
158
- let(:release_year) { "1997" }
159
- let(:release_month) { "9" }
160
- let(:release_day) { "27" }
161
-
162
- describe "with valid parameters" do
163
- it "creates a date" do
164
- form.validate(form_attributes)
165
-
166
- form.songs.first.release_date.must_equal Date.new(1997, 9, 27)
167
- end
168
- end
169
-
170
- %w(year month day).each do |date_attr|
171
- describe "when the #{date_attr} is missing" do
172
- let(:"release_#{date_attr}") { "" }
173
-
174
- it "rejects the date" do
175
- form.validate(form_attributes)
176
-
177
- form.songs.first.release_date.must_be_nil
178
- end
179
- end
180
- end
181
- end
182
-
183
- it "returns flat errors hash" do
184
- form.validate("artist_attributes" => {"name" => ""},
185
- "songs_attributes" => {"0" => {"title" => ""}})
186
- form.errors.messages.must_equal(:"artist.name" => ["can't be blank"], :"songs.title" => ["can't be blank"])
187
- end
188
- end
189
97
 
190
98
  class ActiveModelWithCompositionTest < MiniTest::Spec
191
99
  class HitForm < Reform::Form
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+
3
+ class AsTest < BaseTest
4
+ class AlbumForm < Reform::Form
5
+ property :name, :as => :title
6
+
7
+ property :single, :as => :hit do
8
+ property :title
9
+ end
10
+
11
+ collection :tracks, :as => :songs do
12
+ property :name, :as => :title
13
+ end
14
+
15
+ property :band do
16
+ property :company, :as => :label do
17
+ property :business, :as => :name
18
+ end
19
+ end
20
+ end
21
+
22
+ let (:song2) { Song.new("Roxanne") }
23
+
24
+ let (:params) {
25
+ {
26
+ "name" => "Best Of The Police",
27
+ "single" => {"title" => "So Lonely"},
28
+ "tracks" => [{"name" => "Message In A Bottle"}, {"name" => "Roxanne"}]
29
+ }
30
+ }
31
+
32
+ subject { AlbumForm.new(Album.new("Best Of", hit, [Song.new("Fallout"), song2])) }
33
+
34
+ it { subject.name.must_equal "Best Of" }
35
+ it { subject.single.title.must_equal "Roxanne" }
36
+ it { subject.tracks[0].name.must_equal "Fallout" }
37
+ it { subject.tracks[1].name.must_equal "Roxanne" }
38
+
39
+
40
+ describe "#validate" do
41
+
42
+
43
+ before { subject.validate(params) }
44
+
45
+ it { subject.name.must_equal "Best Of The Police" }
46
+ it { subject.single.title.must_equal "So Lonely" }
47
+ it { subject.tracks[0].name.must_equal "Message In A Bottle" }
48
+ it { subject.tracks[1].name.must_equal "Roxanne" }
49
+ end
50
+
51
+
52
+ describe "#sync" do
53
+ before {
54
+ subject.tracks[1].name = "Livin' Ain't No Crime"
55
+ subject.sync
56
+ }
57
+
58
+ it { song2.title.must_equal "Livin' Ain't No Crime" }
59
+ end
60
+
61
+
62
+ describe "#save (nested hash)" do
63
+ before { subject.validate(params) }
64
+
65
+ it do
66
+ hash = nil
67
+
68
+ subject.save do |f, nested_hash|
69
+ hash = nested_hash
70
+ end
71
+
72
+ hash.must_equal({"title"=>"Best Of The Police", "hit"=>{"title"=>"So Lonely"}, "songs"=>[{"title"=>"Message In A Bottle"}, {"title"=>"Roxanne"}]})
73
+ end
74
+ end
75
+ end
@@ -1,18 +1,36 @@
1
1
  require "test_helper"
2
2
  require "reform/form/coercion"
3
3
 
4
- class CoercionTest < MiniTest::Spec
5
- it "allows coercion" do
6
- form = Class.new(Reform::Form) do
4
+ class CoercionTest < BaseTest
5
+ subject do
6
+ Class.new(Reform::Form) do
7
7
  include Reform::Form::Coercion
8
8
 
9
- property :written_at, :type => DateTime
10
- end.new(OpenStruct.new(:written_at => "31/03/1981"))
9
+ property :released_at, :type => DateTime
10
+
11
+ property :hit do
12
+ property :length, :type => Integer
13
+ property :good, :type => Virtus::Attribute::Boolean
14
+ end
11
15
 
12
- form.written_at.must_be_kind_of DateTime
13
- form.written_at.must_equal DateTime.parse("Tue, 31 Mar 1981 00:00:00 +0000")
16
+ property :band do
17
+ property :label do
18
+ property :value, :type => Float
19
+ end
20
+ end
21
+ end.new(OpenStruct.new(
22
+ :released_at => "31/03/1981",
23
+ :hit => OpenStruct.new(:length => "312"),
24
+ :band => Band.new(OpenStruct.new(:value => "9999.99"))
25
+ ))
14
26
  end
15
27
 
28
+ it { subject.released_at.must_be_kind_of DateTime }
29
+ it { subject.released_at.must_equal DateTime.parse("Tue, 31 Mar 1981 00:00:00 +0000") }
30
+ it { subject.hit.length.must_equal 312 }
31
+ it { subject.band.label.value.must_equal 9999.99 }
32
+
33
+
16
34
  it "allows coercion in validate" do
17
35
  form = Class.new(Reform::Form) do
18
36
  include Reform::Form::Coercion
@@ -21,6 +39,6 @@ class CoercionTest < MiniTest::Spec
21
39
  end.new(OpenStruct.new())
22
40
 
23
41
  form.validate("id" => "1")
24
- form.to_hash.must_equal("id" => 1)
42
+ form.id.must_equal 1
25
43
  end
26
44
  end
@@ -1,6 +1,6 @@
1
1
  class CompositionTest < ReformSpec
2
2
  class SongAndArtist < Reform::Composition
3
- map({:artist => [:name], :song => [:title]}) #SongAndArtistMap.representable_attrs
3
+ map({:artist => [[:name]], :song => [[:title]]}) #SongAndArtistMap.representable_attrs
4
4
  end
5
5
 
6
6
  let (:comp) { SongAndArtist.new(:artist => @artist=OpenStruct.new, :song => rio) }
@@ -21,17 +21,16 @@ class CompositionTest < ReformSpec
21
21
  comp.artist.object_id.must_equal @artist.object_id
22
22
  end
23
23
 
24
- describe "::map_from" do
24
+
25
+ describe "::from" do
25
26
  it "creates the same mapping" do
26
27
  comp =
27
- Class.new(Reform::Composition) do
28
- map_from(
28
+ Reform::Composition.from(
29
29
  Class.new(Reform::Representer) do
30
30
  property :name, :on => :artist
31
31
  property :title, :on => :song
32
32
  end
33
- )
34
- end.
33
+ ).
35
34
  new(:artist => duran, :song => rio)
36
35
 
37
36
  comp.name.must_equal "Duran Duran"
@@ -39,6 +38,7 @@ class CompositionTest < ReformSpec
39
38
  end
40
39
  end
41
40
 
41
+
42
42
  describe "#nested_hash_for" do
43
43
  it "returns nested hash" do
44
44
  comp.nested_hash_for(:name => "Jimi Hendrix", :title => "Fire").must_equal({:artist=>{:name=>"Jimi Hendrix"}, :song=>{:title=>"Fire"}})
@@ -50,8 +50,8 @@ class CompositionTest < ReformSpec
50
50
 
51
51
  it "works with strings in map" do
52
52
  Class.new(Reform::Composition) do
53
- map(:artist => ["name"])
54
- end.new([nil]).nested_hash_for(:name => "Jimi Hendrix").must_equal({:artist=>{:name=>"Jimi Hendrix"}})
53
+ map(:artist => [["name"]])
54
+ end.new({}).nested_hash_for(:name => "Jimi Hendrix").must_equal({:artist=>{:name=>"Jimi Hendrix"}})
55
55
  end
56
56
  end
57
57
  end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+
3
+ class ContractTest < BaseTest
4
+ class AlbumContract < Reform::Contract
5
+ property :title
6
+ validates :title, :presence => true, :length => {:minimum => 3}
7
+
8
+ property :hit do
9
+ property :title
10
+ validates :title, :presence => true
11
+ end
12
+
13
+ collection :songs do
14
+ property :title
15
+ validates :title, :presence => true
16
+ end
17
+
18
+ validates :songs, :length => {:minimum => 4}
19
+
20
+ property :band do # yepp, people do crazy stuff like that.
21
+ validates :label, :presence => true
22
+
23
+ property :label do
24
+ property :name
25
+ validates :name, :presence => true
26
+ end
27
+ # TODO: make band a required object.
28
+ end
29
+ end
30
+
31
+ let (:album) { Album.new(nil, Song.new, [Song.new, Song.new], Band.new() ) }
32
+ subject { AlbumContract.new(album) }
33
+
34
+
35
+ describe "invalid" do
36
+ before {
37
+ res = subject.validate
38
+ res.must_equal false
39
+ }
40
+
41
+ it { subject.errors.messages.must_equal({:"hit.title"=>["can't be blank"], :"songs.title"=>["can't be blank"], :"band.label"=>["can't be blank"], :songs=>["is too short (minimum is 4 characters)"], :title=>["can't be blank", "is too short (minimum is 3 characters)"]}) }
42
+ end
43
+
44
+
45
+ describe "valid" do
46
+ let (:album) { Album.new(
47
+ "Keeper Of The Seven Keys",
48
+ nil,
49
+ [Song.new("Initiation"), Song.new("I'm Alive"), Song.new("A Little Time"), Song.new("Future World"),],
50
+ Band.new(Label.new("Noise"))
51
+ ) }
52
+
53
+ before { subject.validate.must_equal true }
54
+
55
+ it { subject.errors.messages.must_equal({}) }
56
+ end
57
+ end
@@ -14,6 +14,14 @@ class ErrorsTest < MiniTest::Spec
14
14
  validates :title, :presence => true
15
15
  end
16
16
 
17
+ property :band do # yepp, people do crazy stuff like that.
18
+ property :label do
19
+ property :name
20
+ validates :name, :presence => true
21
+ end
22
+ # TODO: make band a required object.
23
+ end
24
+
17
25
  validates :title, :presence => true
18
26
  end
19
27
 
@@ -21,7 +29,9 @@ class ErrorsTest < MiniTest::Spec
21
29
  OpenStruct.new(
22
30
  :title => "Blackhawks Over Los Angeles",
23
31
  :hit => song,
24
- :songs => songs # TODO: document this requirement
32
+ :songs => songs, # TODO: document this requirement,
33
+
34
+ :band => Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
25
35
  )
26
36
  end
27
37
  let (:song) { OpenStruct.new(:title => "Downtown") }
@@ -39,7 +49,9 @@ class ErrorsTest < MiniTest::Spec
39
49
  form.errors.messages.must_equal({
40
50
  :title => ["can't be blank"],
41
51
  :"hit.title"=>["can't be blank"],
42
- :"songs.title"=>["can't be blank"]})
52
+ :"songs.title"=>["can't be blank"],
53
+ :"band.label.name"=>["can't be blank"]
54
+ })
43
55
  end
44
56
 
45
57
  it do
@@ -55,36 +67,51 @@ class ErrorsTest < MiniTest::Spec
55
67
  form.errors.messages.must_equal({
56
68
  :title => ["can't be blank"],
57
69
  :"hit.title" => ["can't be blank"],
58
- :"songs.title"=> ["can't be blank"]})
59
- end # TODO: add another invalid item.
70
+ :"songs.title"=> ["can't be blank"],
71
+ :"band.label.name"=>["can't be blank"]
72
+ })
73
+ end
60
74
  end
61
75
 
76
+
62
77
  describe "#validate with main form invalid" do
63
- before { @result = form.validate("title"=>"") }
78
+ before { @result = form.validate("title"=>"", "band"=>{"label"=>{:name => "Fat Wreck"}}) }
64
79
 
65
80
  it { @result.must_equal false }
66
81
  it { form.errors.messages.must_equal({:title=>["can't be blank"]}) }
67
82
  end
68
83
 
84
+
69
85
  describe "#validate with middle nested form invalid" do
70
- before { @result = form.validate("hit"=>{"title" => ""}) }
86
+ before { @result = form.validate("hit"=>{"title" => ""}, "band"=>{"label"=>{:name => "Fat Wreck"}}) }
71
87
 
72
88
  it { @result.must_equal false }
73
89
  it { form.errors.messages.must_equal({:"hit.title"=>["can't be blank"]}) }
74
90
  end
75
91
 
76
- describe "#validate with last nested form invalid" do
77
- before { @result = form.validate("songs"=>[{"title" => ""}]) }
92
+
93
+ describe "#validate with collection form invalid" do
94
+ before { @result = form.validate("songs"=>[{"title" => ""}], "band"=>{"label"=>{:name => "Fat Wreck"}}) }
78
95
 
79
96
  it { @result.must_equal false }
80
- it { form.errors.messages.must_equal({:"songs.title"=>["can't be blank"]}) }
97
+ it( "xxxx") { form.errors.messages.must_equal({:"songs.title"=>["can't be blank"]}) }
81
98
  end
82
99
 
100
+
101
+ describe "#validate with collection and 2-level-nested invalid" do
102
+ before { @result = form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
103
+
104
+ it { @result.must_equal false }
105
+ it { form.errors.messages.must_equal({:"songs.title"=>["can't be blank"], :"band.label.name"=>["can't be blank"]}) }
106
+ end
107
+
108
+
83
109
  describe "correct #validate" do
84
110
  before { @result = form.validate(
85
111
  "hit" => {"title" => "Sacrifice"},
86
112
  "title" => "Second Heat",
87
- "songs" => [{"title"=>"Heart Of A Lion"}]
113
+ "songs" => [{"title"=>"Heart Of A Lion"}],
114
+ "band" => {"label"=>{:name => "Fat Wreck"}}
88
115
  ) }
89
116
 
90
117
  it { @result.must_equal true }