reform 1.2.6 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -1
  3. data/CHANGES.md +14 -0
  4. data/Gemfile +3 -2
  5. data/README.md +225 -283
  6. data/Rakefile +27 -0
  7. data/TODO.md +12 -0
  8. data/database.sqlite3 +0 -0
  9. data/gemfiles/Gemfile.rails-3.0 +1 -0
  10. data/gemfiles/Gemfile.rails-3.1 +1 -0
  11. data/gemfiles/Gemfile.rails-3.2 +1 -0
  12. data/gemfiles/Gemfile.rails-4.0 +1 -0
  13. data/lib/reform.rb +0 -1
  14. data/lib/reform/contract.rb +64 -170
  15. data/lib/reform/contract/validate.rb +10 -13
  16. data/lib/reform/form.rb +74 -19
  17. data/lib/reform/form/active_model.rb +19 -14
  18. data/lib/reform/form/coercion.rb +1 -13
  19. data/lib/reform/form/composition.rb +2 -24
  20. data/lib/reform/form/multi_parameter_attributes.rb +43 -62
  21. data/lib/reform/form/populator.rb +85 -0
  22. data/lib/reform/form/prepopulate.rb +13 -43
  23. data/lib/reform/form/validate.rb +29 -90
  24. data/lib/reform/form/validation/unique_validator.rb +13 -0
  25. data/lib/reform/version.rb +1 -1
  26. data/reform.gemspec +7 -7
  27. data/test/active_model_test.rb +43 -0
  28. data/test/changed_test.rb +23 -51
  29. data/test/coercion_test.rb +1 -7
  30. data/test/composition_test.rb +128 -34
  31. data/test/contract_test.rb +27 -86
  32. data/test/feature_test.rb +43 -6
  33. data/test/fields_test.rb +2 -12
  34. data/test/form_builder_test.rb +28 -25
  35. data/test/form_option_test.rb +19 -0
  36. data/test/from_test.rb +0 -75
  37. data/test/inherit_test.rb +178 -117
  38. data/test/model_reflections_test.rb +1 -1
  39. data/test/populate_test.rb +226 -0
  40. data/test/prepopulator_test.rb +112 -0
  41. data/test/readable_test.rb +2 -4
  42. data/test/save_test.rb +56 -112
  43. data/test/setup_test.rb +48 -0
  44. data/test/skip_if_test.rb +5 -2
  45. data/test/skip_setter_and_getter_test.rb +54 -0
  46. data/test/test_helper.rb +3 -1
  47. data/test/uniqueness_test.rb +41 -0
  48. data/test/validate_test.rb +325 -289
  49. data/test/virtual_test.rb +1 -3
  50. data/test/writeable_test.rb +3 -4
  51. metadata +35 -39
  52. data/lib/reform/composition.rb +0 -63
  53. data/lib/reform/contract/setup.rb +0 -50
  54. data/lib/reform/form/changed.rb +0 -9
  55. data/lib/reform/form/sync.rb +0 -116
  56. data/lib/reform/representer.rb +0 -84
  57. data/test/empty_test.rb +0 -58
  58. data/test/form_composition_test.rb +0 -145
  59. data/test/nested_form_test.rb +0 -197
  60. data/test/prepopulate_test.rb +0 -85
  61. data/test/sync_option_test.rb +0 -83
  62. data/test/sync_test.rb +0 -56
@@ -1,84 +0,0 @@
1
- require 'representable/hash'
2
- require 'representable/decorator'
3
-
4
- module Reform
5
- class Representer < Representable::Decorator
6
- include Representable::Hash::AllowSymbols
7
-
8
- extend Uber::InheritableAttr
9
- inheritable_attr :options # FIXME: this doesn't need to be inheritable.
10
- # self.options = {}
11
-
12
-
13
- class Options < ::Hash
14
- def include!(names)
15
- includes.push(*names) #if names.size > 0
16
- self
17
- end
18
-
19
- def exclude!(names)
20
- excludes.push(*names) #if names.size > 0
21
- self
22
- end
23
-
24
- def excludes
25
- self[:exclude] ||= []
26
- end
27
-
28
- def includes
29
- self[:include] ||= []
30
- end
31
- end
32
-
33
-
34
- include Representable::Hash
35
-
36
- # Returns hash of all property names.
37
- def self.fields(&block)
38
- representable_attrs.find_all(&block).map(&:name)
39
- end
40
-
41
- def self.each(only_form=true, &block)
42
- definitions = representable_attrs
43
- definitions = representable_attrs.find_all { |attr| attr[:form] } if only_form
44
-
45
- definitions.each(&block)
46
- self
47
- end
48
-
49
- def self.for(options)
50
- clone.tap do |representer|
51
- representer.options = options
52
- end
53
- end
54
-
55
- def self.default_inline_class
56
- options[:form_class]
57
- end
58
-
59
- def self.clone # called in inheritable_attr :representer_class.
60
- Class.new(self) # By subclassing, representable_attrs.clone is called.
61
- end
62
-
63
- private
64
-
65
- # Inline forms always get saved in :extend.
66
- def self.build_inline(base, features, name, options, &block)
67
- name = name.to_s.singularize.camelize
68
-
69
- features = options[:features]
70
-
71
- Class.new(base || default_inline_class) do
72
- include *features
73
-
74
- class_eval &block
75
-
76
- @form_name = name
77
-
78
- def self.name # needed by ActiveModel::Validation and I18N.
79
- @form_name
80
- end
81
- end
82
- end
83
- end
84
- end
data/test/empty_test.rb DELETED
@@ -1,58 +0,0 @@
1
- require 'test_helper'
2
-
3
-
4
- class DeprecatedVirtualTest < MiniTest::Spec # TODO: remove me in 2.0.
5
- Location = Struct.new(:country)
6
-
7
- class LocationForm < Reform::Form
8
- property :country, virtual: true # this becomes readonly: true
9
- end
10
-
11
- let (:loc) { Location.new("Australia") }
12
- let (:form) { LocationForm.new(loc) }
13
-
14
- it { form.country.must_equal "Australia" }
15
- it do
16
- form.validate("country" => "Germany") # this usually won't change when submitting.
17
- form.country.must_equal "Germany"
18
-
19
- form.sync
20
- loc.country.must_equal "Australia" # the writer wasn't called.
21
-
22
- hash = {}
23
- form.save do |nested|
24
- hash = nested
25
- end
26
-
27
- hash.must_equal("country"=> "Germany")
28
- end
29
- end
30
-
31
- class DeprecatedEmptyTest < MiniTest::Spec # don't read, don't write
32
- Credentials = Struct.new(:password)
33
-
34
- class PasswordForm < Reform::Form
35
- property :password
36
- property :password_confirmation, empty: true
37
- end
38
-
39
- let (:cred) { Credentials.new }
40
- let (:form) { PasswordForm.new(cred) }
41
-
42
- it {
43
- form.validate("password" => "123", "password_confirmation" => "321")
44
-
45
- form.password.must_equal "123"
46
- form.password_confirmation.must_equal "321" # this is still readable in the UI.
47
-
48
- form.sync
49
- cred.password.must_equal "123"
50
-
51
- hash = {}
52
- form.save do |nested|
53
- hash = nested
54
- end
55
-
56
- hash.must_equal("password"=> "123", "password_confirmation" => "321")
57
- }
58
- end
@@ -1,145 +0,0 @@
1
- require 'test_helper'
2
-
3
- class FormCompositionTest < MiniTest::Spec
4
- Song = Struct.new(:id, :title, :band)
5
- Requester = Struct.new(:id, :name, :requester)
6
- Band = Struct.new(:title)
7
-
8
- class RequestForm < Reform::Form
9
- include Composition
10
-
11
- property :name, :on => :requester
12
- property :requester_id, :on => :requester, :from => :id
13
- properties :title, :id, :on => :song
14
- # property :channel # FIXME: what about the "main model"?
15
- property :channel, :empty => true, :on => :song
16
- property :requester, :on => :requester
17
- property :captcha, :on => :song, :empty => true
18
-
19
- validates :name, :title, :channel, :presence => true
20
-
21
- property :band, :on => :song do
22
- property :title
23
- end
24
- end
25
-
26
- let (:form) { RequestForm.new(:song => song, :requester => requester) }
27
- let (:song) { Song.new(1, "Rio", band) }
28
- let (:requester) { Requester.new(2, "Duran Duran", "MCP") }
29
- let (:band) { Band.new("Duran^2") }
30
-
31
- # delegation form -> composition works
32
- it { form.id.must_equal 1 }
33
- it { form.title.must_equal "Rio" }
34
- it { form.name.must_equal "Duran Duran" }
35
- it { form.requester_id.must_equal 2 }
36
- it { form.channel.must_equal nil }
37
- it { form.requester.must_equal "MCP" } # same name as composed model.
38
- it { form.captcha.must_equal nil }
39
-
40
- # #model just returns <Composition>.
41
- it { form.model.must_be_kind_of Reform::Composition }
42
-
43
- # #model[] -> composed models
44
- it { form.model[:requester].must_equal requester }
45
- it { form.model[:song].must_equal song }
46
-
47
-
48
- it "creates Composition for you" do
49
- form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal false
50
- end
51
-
52
- describe "#save" do
53
- # #save with {}
54
- it do
55
- hash = {}
56
-
57
- form.save do |map|
58
- hash[:name] = form.name
59
- hash[:title] = form.title
60
- end
61
-
62
- hash.must_equal({:name=>"Duran Duran", :title=>"Rio"})
63
- end
64
-
65
- it "provides nested symbolized hash as second block argument" do
66
- form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "channel" => "JJJ", "captcha" => "wonderful")
67
-
68
- hash = nil
69
-
70
- form.save do |map|
71
- hash = map
72
- end
73
-
74
- hash.must_equal({
75
- :song=>{:title=>"Greyhound", :id=>1, :channel => "JJJ", :captcha=>"wonderful", :band=>{"title"=>"Duran^2"}},
76
- :requester=>{:name=>"Frenzal Rhomb", :id=>2, :requester => "MCP"}
77
- }
78
- )
79
- end
80
-
81
- it "xxx pushes data to models and calls #save when no block passed" do
82
- song.extend(Saveable)
83
- requester.extend(Saveable)
84
- band.extend(Saveable)
85
-
86
- form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb", "captcha" => "1337")
87
- form.captcha.must_equal "1337" # TODO: move to separate test.
88
-
89
- form.save
90
-
91
- requester.name.must_equal "Frenzal Rhomb"
92
- requester.saved?.must_equal true
93
- song.title.must_equal "Greyhound"
94
- song.saved?.must_equal true
95
- song.band.title.must_equal "Duran^2"
96
- song.band.saved?.must_equal true
97
- end
98
-
99
- it "returns true when models all save successfully" do
100
- song.extend(Saveable)
101
- requester.extend(Saveable)
102
- band.extend(Saveable)
103
-
104
- form.save.must_equal true
105
- end
106
-
107
- it "returns false when one or more models don't save successfully" do
108
- module Unsaveable
109
- def save
110
- false
111
- end
112
- end
113
-
114
- song.extend(Unsaveable)
115
- requester.extend(Saveable)
116
- band.extend(Saveable)
117
-
118
- form.save.must_equal false
119
- end
120
- end
121
- end
122
-
123
-
124
- class FormCompositionCollectionTest < MiniTest::Spec
125
- Book = Struct.new(:id, :name)
126
- Library = Struct.new(:id) do
127
- def books
128
- [Book.new(1,"My book")]
129
- end
130
- end
131
-
132
- class LibraryForm < Reform::Form
133
- include Reform::Form::Composition
134
-
135
- collection :books, on: :library do
136
- property :id
137
- property :name
138
- end
139
- end
140
-
141
- let (:form) { LibraryForm.new(library: library) }
142
- let (:library) { Library.new(2) }
143
-
144
- it { form.save do |hash| hash.must_equal({:library=>{:books=>[{"id"=>1, "name"=>"My book"}]}}) end }
145
- end
@@ -1,197 +0,0 @@
1
- require 'test_helper'
2
-
3
- class NestedFormTest < MiniTest::Spec
4
- class AlbumForm < Reform::Form
5
- property :title
6
-
7
- # class SongForm < Reform::Form
8
- # property :title
9
- # validates :title, :presence => true
10
- # end
11
-
12
- #form :hit, :class => SongForm
13
- property :hit do
14
- property :title
15
- validates :title, :presence => true
16
- end
17
-
18
- collection :songs do
19
- property :title
20
- validates :title, :presence => true
21
- end
22
-
23
- validates :title, :presence => true
24
- end
25
-
26
- # AlbumForm::collection :songs, :form => SongForm
27
- # should be: AlbumForm.new(songs: [Song, Song])
28
-
29
- let (:album) do
30
- OpenStruct.new(
31
- :title => "Blackhawks Over Los Angeles",
32
- :hit => song,
33
- :songs => songs # TODO: document this requirement
34
- )
35
- end
36
- let (:song) { OpenStruct.new(:title => "Downtown") }
37
- let (:songs) { [OpenStruct.new(:title => "Calling")] }
38
- let (:form) { AlbumForm.new(album) }
39
-
40
- it "responds to #save" do
41
- hsh = nil
42
- form.save do |nested|
43
- hsh = nested
44
- end
45
- hsh.must_equal({"hit"=>{"title"=>"Downtown"}, "title" => "Blackhawks Over Los Angeles", "songs"=>[{"title"=>"Calling"}]})
46
- end
47
-
48
-
49
- it "creates nested forms" do
50
- form.hit.must_be_kind_of Reform::Form
51
- form.songs.must_be_kind_of Array
52
- end
53
-
54
- describe "#initialize" do
55
- describe "with empty object and no cardinality" do
56
- let(:form) { AlbumForm.new(OpenStruct.new) }
57
-
58
- it "allows initialization with empty properties" do
59
- form
60
- end
61
-
62
- it "allows #validate" do
63
- form.validate({})
64
- form.errors.messages.must_equal(:title=>["can't be blank"])
65
- end
66
- # it "must support #validate when initialized with empty properties" do
67
- # form.validate({})
68
- # form.errors.messages.must_equal(:title=>["can't be blank"], :"hit.title"=>["can't be blank"], :"songs.title"=>["can't be blank"])
69
- # end
70
- # it "must support #validate with attributes when initialized with empty properties" do
71
- # form.validate("hit"=>{"title"=>"Downtown"}, "title" => "Blackhawks Over Los Angeles", "songs"=>[{"title"=>"Calling"}])
72
- # form.title.must_eql "Blackhawks Over Los Angeles"
73
- # form.errors.messages.must_equal([])
74
- # end
75
- end
76
-
77
-
78
- end
79
-
80
-
81
- describe "rendering" do
82
- it { form.title.must_equal "Blackhawks Over Los Angeles" }
83
- it { form.hit.title.must_equal "Downtown" }
84
- it { form.songs[0].title.must_equal "Calling" }
85
- end
86
-
87
- describe "#save" do
88
- before { @result = form.validate(
89
- "hit" =>{"title" => "Sacrifice"},
90
- "title" =>"Second Heat",
91
- "songs" => [{"title" => "Scarified"}])
92
- }
93
-
94
- it "updates internal Fields" do
95
- data = {}
96
-
97
- form.save do |nested_hash|
98
- data[:title] = form.title
99
- data[:hit_title] = form.hit.title
100
- data[:first_title] = form.songs.first.title
101
- end
102
-
103
- data.must_equal(:title=>"Second Heat", :hit_title => "Sacrifice", :first_title => "Scarified")
104
- end
105
-
106
- it "returns nested hash with indifferent access" do
107
- nested = nil
108
-
109
- form.save do |nested_hash|
110
- nested = nested_hash
111
- end
112
-
113
- nested.must_equal("title"=>"Second Heat", "hit"=>{"title"=>"Sacrifice"}, "songs"=>[{"title"=>"Scarified"}])
114
-
115
- nested[:title].must_equal "Second Heat"
116
- nested["title"].must_equal "Second Heat"
117
- nested[:hit][:title].must_equal "Sacrifice"
118
- nested["hit"]["title"].must_equal "Sacrifice"
119
- end
120
-
121
- it "pushes data to models" do
122
- form.save
123
-
124
- album.title.must_equal "Second Heat"
125
- song.title.must_equal "Sacrifice"
126
- songs.first.title.must_equal "Scarified"
127
- end
128
-
129
- describe "with invalid args" do
130
- it "allows empty collection values" do
131
- form.validate({})
132
-
133
- form.songs.size.must_equal 1
134
- form.songs[0].title.must_equal "Scarified"
135
- end
136
- end
137
- end
138
-
139
- # describe "with aliased nested form name" do
140
- # let (:form) do
141
- # Class.new(Reform::Form) do
142
- # form :hit, :class => AlbumForm::SongForm, :as => :song
143
- # end.new(OpenStruct.new(:hit => OpenStruct.new(:title => "")))
144
- # end
145
-
146
- # it "uses alias in errors" do
147
- # form.validate({})
148
- # form.errors.messages.must_equal({})
149
- # end
150
- # end
151
-
152
- class ExplicitNestedFormTest < MiniTest::Spec
153
- let (:song) { OpenStruct.new(:title => "Downtown") }
154
- let (:album) do
155
- OpenStruct.new(
156
- :title => "Blackhawks Over Los Angeles",
157
- :hit => song,
158
- )
159
- end
160
- let (:form) { AlbumForm.new(album) }
161
-
162
- class SongForm < Reform::Form
163
- property :title
164
- validates_presence_of :title
165
- end
166
-
167
- class AlbumForm < Reform::Form
168
- property :title
169
-
170
- property :hit, :form => SongForm #, :parse_strategy => :sync, :instance => true
171
- end
172
-
173
-
174
- it "allows rendering" do
175
- form.hit.title.must_equal "Downtown"
176
- end
177
-
178
- it ("xxx") {
179
- form.validate({"hit" => {"title" => ""}})
180
- form.hit.title.must_equal ""
181
- form.errors[:"hit.title"].must_equal(["can't be blank"])
182
- }
183
- end
184
-
185
- class UnitTest < self
186
- it "keeps Forms for form collection" do
187
- form.send(:fields).songs.must_be_kind_of Array
188
- end
189
-
190
- describe "#validate" do
191
- it "keeps Form instances" do
192
- form.validate("songs"=>[{"title" => "Atwa"}])
193
- form.songs.first.must_be_kind_of Reform::Form
194
- end
195
- end
196
- end
197
- end