reform 0.2.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 }