reform 1.0.4 → 1.1.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.
- checksums.yaml +4 -4
- data/CHANGES.md +13 -0
- data/Gemfile +1 -1
- data/README.md +61 -13
- data/database.sqlite3 +0 -0
- data/lib/reform/composition.rb +1 -0
- data/lib/reform/contract.rb +24 -15
- data/lib/reform/form.rb +5 -0
- data/lib/reform/form/active_model.rb +5 -3
- data/lib/reform/form/active_model/model_validations.rb +98 -0
- data/lib/reform/form/active_record.rb +8 -1
- data/lib/reform/form/coercion.rb +4 -4
- data/lib/reform/form/composition.rb +2 -19
- data/lib/reform/form/module.rb +27 -0
- data/lib/reform/form/multi_parameter_attributes.rb +22 -2
- data/lib/reform/form/save.rb +18 -5
- data/lib/reform/form/scalar.rb +52 -0
- data/lib/reform/form/sync.rb +5 -6
- data/lib/reform/form/validate.rb +40 -57
- data/lib/reform/rails.rb +3 -3
- data/lib/reform/representer.rb +14 -7
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +6 -4
- data/test/active_model_test.rb +4 -23
- data/test/active_record_test.rb +188 -42
- data/test/as_test.rb +1 -1
- data/test/builder_test.rb +32 -0
- data/test/coercion_test.rb +56 -28
- data/test/deserialize_test.rb +40 -0
- data/test/form_builder_test.rb +1 -1
- data/test/form_composition_test.rb +50 -22
- data/test/inherit_test.rb +79 -0
- data/test/model_validations_test.rb +82 -0
- data/test/nested_form_test.rb +2 -13
- data/test/reform_test.rb +106 -7
- data/test/scalar_test.rb +167 -0
- data/test/test_helper.rb +10 -0
- data/test/validate_test.rb +42 -2
- metadata +37 -12
- data/test/setup_test.rb +0 -68
data/test/active_record_test.rb
CHANGED
@@ -1,65 +1,81 @@
|
|
1
1
|
require 'test_helper'
|
2
|
+
require 'reform/active_record'
|
3
|
+
|
4
|
+
# ActiveRecord::Schema.define do
|
5
|
+
# create_table :artists do |table|
|
6
|
+
# table.column :name, :string
|
7
|
+
# table.timestamps
|
8
|
+
# end
|
9
|
+
# create_table :songs do |table|
|
10
|
+
# table.column :title, :string
|
11
|
+
# table.column :artist_id, :integer
|
12
|
+
# table.column :album_id, :integer
|
13
|
+
# table.timestamps
|
14
|
+
# end
|
15
|
+
# create_table :albums do |table|
|
16
|
+
# table.column :title, :string
|
17
|
+
# table.timestamps
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# Artist.new(:name => "Racer X").save
|
21
|
+
|
2
22
|
|
3
23
|
class ActiveRecordTest < MiniTest::Spec
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
include Reform::Form::ActiveRecord
|
8
|
-
model :artist
|
24
|
+
class SongForm < Reform::Form
|
25
|
+
include Reform::Form::ActiveRecord
|
26
|
+
model :song
|
9
27
|
|
10
|
-
|
11
|
-
|
28
|
+
property :title
|
29
|
+
property :created_at
|
12
30
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
31
|
+
validates_uniqueness_of :title
|
32
|
+
validates :created_at, :presence => true # have another property to test if we mix up.
|
33
|
+
|
34
|
+
property :artist do
|
35
|
+
property :name
|
36
|
+
validates_uniqueness_of :name # this currently also tests if Form::AR is included as a feature.
|
37
|
+
end
|
17
38
|
end
|
18
39
|
|
19
|
-
|
40
|
+
let (:form) { SongForm.new(Song.new(:artist => Artist.new)) }
|
20
41
|
|
21
|
-
|
22
|
-
# ActiveRecord::Schema.define do
|
23
|
-
# create_table :artists do |table|
|
24
|
-
# table.column :name, :string
|
25
|
-
# table.timestamps
|
26
|
-
# end
|
27
|
-
# end
|
28
|
-
# Artist.new(:name => "Racer X").save
|
42
|
+
it { form.class.i18n_scope.must_equal :activerecord }
|
29
43
|
|
30
|
-
|
31
|
-
|
44
|
+
it "allows accessing the database" do
|
45
|
+
end
|
32
46
|
|
33
|
-
|
34
|
-
|
35
|
-
|
47
|
+
# uniqueness
|
48
|
+
it "is valid when name is unique" do
|
49
|
+
form.validate("artist" => {"name" => "Paul Gilbert"}, "title" => "The Gargoyle", "created_at" => "November 6, 1966").must_equal true
|
50
|
+
end
|
36
51
|
|
37
|
-
|
38
|
-
|
52
|
+
# nested object taken.
|
53
|
+
it "is invalid and shows error when taken" do
|
54
|
+
Song.delete_all
|
55
|
+
Artist.create(:name => "Racer X")
|
39
56
|
|
40
|
-
|
41
|
-
|
42
|
-
|
57
|
+
form.validate("artist" => {"name" => "Racer X"}, "title" => "Ghost Inside My Skin").must_equal false
|
58
|
+
form.errors.messages.must_equal({:"artist.name"=>["has already been taken"], :created_at => ["can't be blank"]})
|
59
|
+
end
|
43
60
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
61
|
+
it "works with Composition" do
|
62
|
+
form = Class.new(Reform::Form) do
|
63
|
+
include Reform::Form::ActiveRecord
|
64
|
+
include Reform::Form::Composition
|
48
65
|
|
49
|
-
|
50
|
-
|
51
|
-
|
66
|
+
property :name, :on => :artist
|
67
|
+
validates_uniqueness_of :name
|
68
|
+
end.new(:artist => Artist.new)
|
52
69
|
|
53
|
-
|
54
|
-
|
55
|
-
end
|
70
|
+
Artist.create(:name => "Bad Religion")
|
71
|
+
form.validate("name" => "Bad Religion").must_equal false
|
56
72
|
end
|
57
73
|
|
58
74
|
describe "#save" do
|
59
75
|
# TODO: test 1-n?
|
60
76
|
it "calls model.save" do
|
61
77
|
Artist.delete_all
|
62
|
-
form.validate("name" => "Bad Religion")
|
78
|
+
form.validate("artist" => {"name" => "Bad Religion"}, "title" => "Ghost Inside My Skin")
|
63
79
|
form.save
|
64
80
|
Artist.where(:name => "Bad Religion").size.must_equal 1
|
65
81
|
end
|
@@ -71,4 +87,134 @@ class ActiveRecordTest < MiniTest::Spec
|
|
71
87
|
Artist.where(:name => "Bad Religion").size.must_equal 0
|
72
88
|
end
|
73
89
|
end
|
74
|
-
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
class PopulateWithActiveRecordTest < MiniTest::Spec
|
94
|
+
class AlbumForm < Reform::Form
|
95
|
+
property :title
|
96
|
+
|
97
|
+
collection :songs, :populate_if_empty => Song do
|
98
|
+
property :title
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
let (:album) { Album.new(:songs => []) }
|
103
|
+
it do
|
104
|
+
form = AlbumForm.new(album)
|
105
|
+
|
106
|
+
form.validate("songs" => [{"title" => "Straight From The Jacket"}])
|
107
|
+
|
108
|
+
# form populated.
|
109
|
+
form.songs.size.must_equal 1
|
110
|
+
form.songs[0].model.must_be_kind_of Song
|
111
|
+
|
112
|
+
# model NOT populated.
|
113
|
+
album.songs.must_equal []
|
114
|
+
|
115
|
+
|
116
|
+
form.sync
|
117
|
+
|
118
|
+
# form populated.
|
119
|
+
form.songs.size.must_equal 1
|
120
|
+
form.songs[0].model.must_be_kind_of Song
|
121
|
+
|
122
|
+
# model also populated.
|
123
|
+
song = album.songs[0]
|
124
|
+
album.songs.must_equal [song]
|
125
|
+
song.title.must_equal "Straight From The Jacket"
|
126
|
+
|
127
|
+
|
128
|
+
if ActiveRecord::VERSION::STRING !~ /^3.0/
|
129
|
+
# saving saves association.
|
130
|
+
form.save
|
131
|
+
|
132
|
+
album.reload
|
133
|
+
song = album.songs[0]
|
134
|
+
album.songs.must_equal [song]
|
135
|
+
song.title.must_equal "Straight From The Jacket"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
describe "modifying 1., adding 2." do
|
141
|
+
let (:song) { Song.new(:title => "Part 2") }
|
142
|
+
let (:album) { Album.create.tap { |a| a.songs << song } }
|
143
|
+
|
144
|
+
it do
|
145
|
+
form = AlbumForm.new(album)
|
146
|
+
|
147
|
+
id = album.songs[0].id
|
148
|
+
assert id > 0
|
149
|
+
|
150
|
+
form.validate("songs" => [{"title" => "Part Two"}, {"title" => "Check For A Pulse"}])
|
151
|
+
|
152
|
+
# form populated.
|
153
|
+
form.songs.size.must_equal 2
|
154
|
+
form.songs[0].model.must_be_kind_of Song
|
155
|
+
form.songs[1].model.must_be_kind_of Song
|
156
|
+
|
157
|
+
# model NOT populated.
|
158
|
+
album.songs.must_equal [song]
|
159
|
+
|
160
|
+
|
161
|
+
form.sync
|
162
|
+
|
163
|
+
# form populated.
|
164
|
+
form.songs.size.must_equal 2
|
165
|
+
|
166
|
+
# model also populated.
|
167
|
+
album.songs.size.must_equal 2
|
168
|
+
|
169
|
+
# corrected title
|
170
|
+
album.songs[0].title.must_equal "Part Two"
|
171
|
+
# ..but same song.
|
172
|
+
album.songs[0].id.must_equal id
|
173
|
+
|
174
|
+
# and a new song.
|
175
|
+
album.songs[1].title.must_equal "Check For A Pulse"
|
176
|
+
album.songs[1].persisted?.must_equal true # TODO: with << strategy, this shouldn't be saved.
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# it do
|
181
|
+
# a=Album.new
|
182
|
+
# a.songs << Song.new(title: "Old What's His Name") # Song does not get persisted.
|
183
|
+
|
184
|
+
# a.songs[1] = Song.new(title: "Permanent Rust")
|
185
|
+
|
186
|
+
# puts "@@@"
|
187
|
+
# puts a.songs.inspect
|
188
|
+
|
189
|
+
# puts "---"
|
190
|
+
# a.save
|
191
|
+
# puts a.songs.inspect
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
# b = a.songs.first
|
196
|
+
|
197
|
+
# a.songs = [Song.new(title:"Biomag")]
|
198
|
+
# puts "\\\\"
|
199
|
+
# a.save
|
200
|
+
# a.reload
|
201
|
+
# puts a.songs.inspect
|
202
|
+
|
203
|
+
# b.reload
|
204
|
+
# puts "#{b.inspect}, #{b.persisted?}"
|
205
|
+
|
206
|
+
|
207
|
+
# a.songs = [a.songs.first, Song.new(title: "Count Down")]
|
208
|
+
# b = a.songs.first
|
209
|
+
# puts ":::::"
|
210
|
+
# a.save
|
211
|
+
# a.reload
|
212
|
+
# puts a.songs.inspect
|
213
|
+
|
214
|
+
# b.reload
|
215
|
+
# puts "#{b.inspect}, #{b.persisted?}"
|
216
|
+
# end
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
|
data/test/as_test.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BuilderTest < MiniTest::Spec
|
4
|
+
it do
|
5
|
+
Builder.new.checkboxes(:settings, :hash => {"play" => true, "released" => false}).must_equal %{<input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />
|
6
|
+
<input name="yo" type="hidden" value="unchecked_value" /><input id="object_name_method" name="yo" type="checkbox" value="checked_value" />}
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
require 'action_view/helpers/capture_helper'
|
12
|
+
require 'action_view/helpers/tag_helper'
|
13
|
+
require 'action_view/helpers/url_helper'
|
14
|
+
require 'action_view/helpers/sanitize_helper'
|
15
|
+
require 'action_view/helpers/text_helper'
|
16
|
+
require 'action_view/helpers/form_tag_helper'
|
17
|
+
require 'action_view/helpers/form_helper'
|
18
|
+
|
19
|
+
class Builder
|
20
|
+
include ActionView::Helpers::CaptureHelper
|
21
|
+
include ActionView::Helpers::FormHelper
|
22
|
+
|
23
|
+
# {name: value}
|
24
|
+
def checkboxes(name, options)
|
25
|
+
# get property form.to_a ? to_builder_hash or something like that
|
26
|
+
options[:hash].collect do |id, value|
|
27
|
+
ActionView::Helpers::InstanceTag.new(:object_name, :method, self).to_check_box_tag({:name => "yo"}, :checked_value, :unchecked_value)
|
28
|
+
|
29
|
+
# check_box_tag(id, value, checked = false, {})
|
30
|
+
end.join("\n")
|
31
|
+
end
|
32
|
+
end
|
data/test/coercion_test.rb
CHANGED
@@ -2,43 +2,71 @@ require "test_helper"
|
|
2
2
|
require "reform/form/coercion"
|
3
3
|
|
4
4
|
class CoercionTest < BaseTest
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class Irreversible < Virtus::Attribute
|
6
|
+
def coerce(value)
|
7
|
+
value*2
|
8
|
+
end
|
9
|
+
end
|
8
10
|
|
9
|
-
|
11
|
+
class Form < Reform::Form
|
12
|
+
include Coercion
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
property :released_at, :type => DateTime
|
15
|
+
|
16
|
+
property :hit do
|
17
|
+
# include Coercion
|
18
|
+
property :length, :type => Integer
|
19
|
+
property :good, :type => Virtus::Attribute::Boolean
|
20
|
+
end
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
property :band do
|
23
|
+
include Coercion
|
24
|
+
property :label do
|
25
|
+
include Coercion
|
26
|
+
property :value, :type => Irreversible
|
20
27
|
end
|
21
|
-
end
|
22
|
-
:released_at => "31/03/1981",
|
23
|
-
:hit => OpenStruct.new(:length => "312"),
|
24
|
-
:band => Band.new(OpenStruct.new(:value => "9999.99"))
|
25
|
-
))
|
28
|
+
end
|
26
29
|
end
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
it { subject.band.label.value.must_equal 9999.99 }
|
31
|
+
subject do
|
32
|
+
Form.new(album)
|
33
|
+
end
|
32
34
|
|
35
|
+
let (:album) {
|
36
|
+
OpenStruct.new(
|
37
|
+
:released_at => "31/03/1981",
|
38
|
+
:hit => OpenStruct.new(:length => "312"),
|
39
|
+
:band => Band.new(OpenStruct.new(:value => "9999.99"))
|
40
|
+
)
|
41
|
+
}
|
33
42
|
|
34
|
-
it
|
35
|
-
|
36
|
-
|
43
|
+
# it { subject.released_at.must_be_kind_of DateTime }
|
44
|
+
it { subject.released_at.must_equal "31/03/1981" } # NO coercion in setup.
|
45
|
+
it { subject.hit.length.must_equal "312" }
|
46
|
+
it { subject.band.label.value.must_equal "9999.99" }
|
37
47
|
|
38
|
-
property :id, :type => Integer
|
39
|
-
end.new(OpenStruct.new())
|
40
48
|
|
41
|
-
|
42
|
-
|
49
|
+
let (:params) {
|
50
|
+
{
|
51
|
+
:released_at => "30/03/1981",
|
52
|
+
:hit => {:length => "312"},
|
53
|
+
:band => {:label => {:value => "9999.99"}}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
|
58
|
+
# validate
|
59
|
+
describe "#validate" do
|
60
|
+
before { subject.validate(params) }
|
61
|
+
|
62
|
+
it { subject.released_at.must_equal DateTime.parse("30/03/1981") }
|
63
|
+
it { subject.hit.length.must_equal 312 }
|
64
|
+
it { subject.hit.good.must_equal nil }
|
65
|
+
it { subject.band.label.value.must_equal "9999.999999.99" } # coercion happened once.
|
43
66
|
end
|
67
|
+
|
68
|
+
# save
|
69
|
+
|
70
|
+
|
71
|
+
|
44
72
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'representable/json'
|
3
|
+
|
4
|
+
class DeserializeTest < BaseTest
|
5
|
+
class AlbumContract < Reform::Form
|
6
|
+
self.representer_class.class_eval do
|
7
|
+
include Representable::JSON
|
8
|
+
end
|
9
|
+
def deserialize_method
|
10
|
+
:from_json
|
11
|
+
end
|
12
|
+
|
13
|
+
property :title
|
14
|
+
validates :title, :presence => true, :length => {:minimum => 3}
|
15
|
+
|
16
|
+
property :hit do
|
17
|
+
property :title
|
18
|
+
validates :title, :presence => true
|
19
|
+
end
|
20
|
+
|
21
|
+
property :band do # yepp, people do crazy stuff like that.
|
22
|
+
validates :label, :presence => true
|
23
|
+
|
24
|
+
property :label do
|
25
|
+
property :name
|
26
|
+
validates :name, :presence => true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
let (:album) { Album.new(nil, Song.new, [Song.new, Song.new], Band.new(Label.new("Fat Wreck")) ) }
|
32
|
+
subject { AlbumContract.new(album) }
|
33
|
+
|
34
|
+
let (:json) { '{"hit":{"title":"Sacrifice"},"title":"Second Heat","songs":[{"title":"Heart Of A Lion"}],"band":{"label":{"name":"Fat Wreck"}}}' }
|
35
|
+
|
36
|
+
it {
|
37
|
+
subject.validate(json)
|
38
|
+
subject.band.label.name.must_equal "Fat Wreck"
|
39
|
+
}
|
40
|
+
end
|