reform 1.2.6 → 2.0.0.beta1
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/.travis.yml +6 -1
- data/CHANGES.md +14 -0
- data/Gemfile +3 -2
- data/README.md +225 -283
- data/Rakefile +27 -0
- data/TODO.md +12 -0
- data/database.sqlite3 +0 -0
- data/gemfiles/Gemfile.rails-3.0 +1 -0
- data/gemfiles/Gemfile.rails-3.1 +1 -0
- data/gemfiles/Gemfile.rails-3.2 +1 -0
- data/gemfiles/Gemfile.rails-4.0 +1 -0
- data/lib/reform.rb +0 -1
- data/lib/reform/contract.rb +64 -170
- data/lib/reform/contract/validate.rb +10 -13
- data/lib/reform/form.rb +74 -19
- data/lib/reform/form/active_model.rb +19 -14
- data/lib/reform/form/coercion.rb +1 -13
- data/lib/reform/form/composition.rb +2 -24
- data/lib/reform/form/multi_parameter_attributes.rb +43 -62
- data/lib/reform/form/populator.rb +85 -0
- data/lib/reform/form/prepopulate.rb +13 -43
- data/lib/reform/form/validate.rb +29 -90
- data/lib/reform/form/validation/unique_validator.rb +13 -0
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +7 -7
- data/test/active_model_test.rb +43 -0
- data/test/changed_test.rb +23 -51
- data/test/coercion_test.rb +1 -7
- data/test/composition_test.rb +128 -34
- data/test/contract_test.rb +27 -86
- data/test/feature_test.rb +43 -6
- data/test/fields_test.rb +2 -12
- data/test/form_builder_test.rb +28 -25
- data/test/form_option_test.rb +19 -0
- data/test/from_test.rb +0 -75
- data/test/inherit_test.rb +178 -117
- data/test/model_reflections_test.rb +1 -1
- data/test/populate_test.rb +226 -0
- data/test/prepopulator_test.rb +112 -0
- data/test/readable_test.rb +2 -4
- data/test/save_test.rb +56 -112
- data/test/setup_test.rb +48 -0
- data/test/skip_if_test.rb +5 -2
- data/test/skip_setter_and_getter_test.rb +54 -0
- data/test/test_helper.rb +3 -1
- data/test/uniqueness_test.rb +41 -0
- data/test/validate_test.rb +325 -289
- data/test/virtual_test.rb +1 -3
- data/test/writeable_test.rb +3 -4
- metadata +35 -39
- data/lib/reform/composition.rb +0 -63
- data/lib/reform/contract/setup.rb +0 -50
- data/lib/reform/form/changed.rb +0 -9
- data/lib/reform/form/sync.rb +0 -116
- data/lib/reform/representer.rb +0 -84
- data/test/empty_test.rb +0 -58
- data/test/form_composition_test.rb +0 -145
- data/test/nested_form_test.rb +0 -197
- data/test/prepopulate_test.rb +0 -85
- data/test/sync_option_test.rb +0 -83
- data/test/sync_test.rb +0 -56
@@ -0,0 +1,13 @@
|
|
1
|
+
class Reform::Form::UniqueValidator < ActiveModel::EachValidator
|
2
|
+
def validate_each(form, attribute, value)
|
3
|
+
# search for models with attribute equals to form field value
|
4
|
+
query = form.model.class.where(attribute => value)
|
5
|
+
|
6
|
+
# if model persisted, excluded own model from query
|
7
|
+
query = query.merge(form.model.class.where("id <> ?", form.model.id)) if form.model.persisted?
|
8
|
+
|
9
|
+
# if any models found, add error on attribute
|
10
|
+
form.errors.add(attribute, "#{attribute} must be unique.") if query.any?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
data/lib/reform/version.rb
CHANGED
data/reform.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'reform/version'
|
@@ -8,8 +7,8 @@ Gem::Specification.new do |spec|
|
|
8
7
|
spec.version = Reform::VERSION
|
9
8
|
spec.authors = ["Nick Sutterer", "Garrett Heinlen"]
|
10
9
|
spec.email = ["apotonick@gmail.com", "heinleng@gmail.com"]
|
11
|
-
spec.description = %q{
|
12
|
-
spec.summary = %q{
|
10
|
+
spec.description = %q{Form object decoupled from models.}
|
11
|
+
spec.summary = %q{Form object decoupled from models with validation, population and presentation.}
|
13
12
|
spec.homepage = "https://github.com/apotonick/reform"
|
14
13
|
spec.license = "MIT"
|
15
14
|
|
@@ -18,13 +17,14 @@ Gem::Specification.new do |spec|
|
|
18
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
18
|
spec.require_paths = ["lib"]
|
20
19
|
|
21
|
-
spec.add_dependency "representable", "
|
22
|
-
spec.add_dependency "disposable", "~> 0.
|
20
|
+
spec.add_dependency "representable", ">= 2.2.2"
|
21
|
+
spec.add_dependency "disposable", "~> 0.1.2"
|
23
22
|
spec.add_dependency "uber", "~> 0.0.11"
|
24
23
|
spec.add_dependency "activemodel"
|
25
|
-
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler"
|
26
26
|
spec.add_development_dependency "rake"
|
27
|
-
spec.add_development_dependency "minitest"
|
27
|
+
spec.add_development_dependency "minitest"
|
28
28
|
spec.add_development_dependency "activerecord"
|
29
29
|
spec.add_development_dependency "sqlite3"
|
30
30
|
spec.add_development_dependency "virtus"
|
data/test/active_model_test.rb
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
+
module IsolatedRailsEngine
|
4
|
+
def self.use_relative_model_naming?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
class Lyric < ActiveRecord::Base
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module NormalRailsEngine
|
13
|
+
class Lyric < ActiveRecord::Base
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
3
18
|
class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
|
4
19
|
class SongForm < Reform::Form
|
5
20
|
include Reform::Form::ActiveModel
|
@@ -31,6 +46,27 @@ class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
|
|
31
46
|
it { class_with_model.model_name.must_be_kind_of ActiveModel::Name }
|
32
47
|
it { class_with_model.model_name.to_s.must_equal "Album" }
|
33
48
|
|
49
|
+
let (:class_with_isolated_model) {
|
50
|
+
Class.new(Reform::Form) do
|
51
|
+
include Reform::Form::ActiveModel
|
52
|
+
|
53
|
+
model "isolated_rails_engine/lyric", namespace: "isolated_rails_engine"
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
it { class_with_isolated_model.model_name.must_be_kind_of ActiveModel::Name }
|
58
|
+
it { class_with_isolated_model.model_name.to_s.must_equal "IsolatedRailsEngine::Lyric" }
|
59
|
+
|
60
|
+
let (:class_with_namespace_model) {
|
61
|
+
Class.new(Reform::Form) do
|
62
|
+
include Reform::Form::ActiveModel
|
63
|
+
|
64
|
+
model "normal_rails_engine/lyric"
|
65
|
+
end
|
66
|
+
}
|
67
|
+
|
68
|
+
it { class_with_namespace_model.model_name.must_be_kind_of ActiveModel::Name }
|
69
|
+
it { class_with_namespace_model.model_name.to_s.must_equal "NormalRailsEngine::Lyric" }
|
34
70
|
|
35
71
|
let (:subclass_of_class_with_model) {
|
36
72
|
Class.new(class_with_model)
|
@@ -39,6 +75,13 @@ class NewActiveModelTest < MiniTest::Spec # TODO: move to test/rails/
|
|
39
75
|
it { subclass_of_class_with_model.model_name.must_be_kind_of ActiveModel::Name }
|
40
76
|
it { subclass_of_class_with_model.model_name.to_s.must_equal 'Album' }
|
41
77
|
|
78
|
+
unless Reform.rails3_0?
|
79
|
+
it { form.class.model_name.route_key.must_equal "new_active_model_test_songs" }
|
80
|
+
it { class_with_model.model_name.route_key.must_equal "albums" }
|
81
|
+
it { class_with_isolated_model.model_name.route_key.must_equal "lyrics" }
|
82
|
+
it { class_with_namespace_model.model_name.route_key.must_equal "normal_rails_engine_lyrics" }
|
83
|
+
it { subclass_of_class_with_model.model_name.route_key.must_equal 'albums' }
|
84
|
+
end
|
42
85
|
|
43
86
|
describe "class named Song::Form" do
|
44
87
|
it do
|
data/test/changed_test.rb
CHANGED
@@ -1,69 +1,41 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
require 'reform/form/coercion'
|
3
3
|
|
4
|
-
class ChangedTest <
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
property :title
|
4
|
+
class ChangedTest < MiniTest::Spec
|
5
|
+
Song = Struct.new(:title, :album, :composer)
|
6
|
+
Album = Struct.new(:name, :songs, :artist)
|
7
|
+
Artist = Struct.new(:name)
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
property :length, type: Integer
|
13
|
-
validates :title, :presence => true
|
14
|
-
end
|
9
|
+
class AlbumForm < Reform::Form
|
10
|
+
property :name
|
15
11
|
|
16
12
|
collection :songs do
|
17
13
|
property :title
|
18
|
-
validates :title, :presence => true
|
19
|
-
end
|
20
14
|
|
21
|
-
|
22
|
-
property :label do
|
15
|
+
property :composer do
|
23
16
|
property :name
|
24
|
-
property :location
|
25
|
-
validates :name, :presence => true
|
26
17
|
end
|
27
|
-
# TODO: make band a required object.
|
28
18
|
end
|
29
|
-
|
30
|
-
validates :title, :presence => true
|
31
19
|
end
|
32
20
|
|
33
|
-
|
21
|
+
let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
22
|
+
let (:composer) { Artist.new("Greg Graffin") }
|
23
|
+
let (:album) { Album.new("The Dissent Of Man", [song_with_composer]) }
|
34
24
|
|
35
|
-
|
36
|
-
let (:form) { AlbumForm.new(Album.new("Drawn Down The Moon", Song.new("The Ripper", 9), [Song.new("Black Candles"), Song.new("The Ripper")], Band.new(Label.new("Cleopatra Records")))) }
|
25
|
+
let (:form) { AlbumForm.new(album) }
|
37
26
|
|
38
|
-
|
39
|
-
it
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
describe "#validate" do
|
45
|
-
before { form.validate(
|
46
|
-
"title" => "Five", # changed.
|
47
|
-
"hit" => {"title" => "The Ripper", # same, but overridden.
|
48
|
-
"length" => "9"}, # gets coerced, then compared, so not changed.
|
49
|
-
"band" => {"label" => {"name" => "Shrapnel Records"}} # only label.name changes.
|
50
|
-
) }
|
51
|
-
|
52
|
-
it { form.changed?(:title).must_equal true }
|
53
|
-
|
54
|
-
# it { form.changed?(:hit).must_equal false }
|
55
|
-
|
56
|
-
# overridden with same value is no change.
|
57
|
-
it { form.hit.changed?(:title).must_equal false }
|
58
|
-
# coerced value is identical to form's => not changed.
|
59
|
-
it { form.hit.changed?(:length).must_equal false }
|
60
|
-
|
61
|
-
# it { form.changed?(:band).must_equal true }
|
62
|
-
# it { form.band.changed?(:label).must_equal true }
|
63
|
-
it { form.band.label.changed?(:name).must_equal true }
|
27
|
+
# nothing changed after setup.
|
28
|
+
it do
|
29
|
+
form.changed?(:name).must_equal false
|
30
|
+
form.songs[0].changed?(:title).must_equal false
|
31
|
+
form.songs[0].composer.changed?(:name).must_equal false
|
32
|
+
end
|
64
33
|
|
65
|
-
|
66
|
-
|
67
|
-
|
34
|
+
# after validate, things might have changed.
|
35
|
+
it do
|
36
|
+
form.validate("name" => "Out Of Bounds", "songs" => [{"composer" => {"name" => "Ingemar Jansson & Mikael Danielsson"}}])
|
37
|
+
form.changed?(:name).must_equal true
|
38
|
+
form.songs[0].changed?(:title).must_equal false
|
39
|
+
form.songs[0].composer.changed?(:name).must_equal true
|
68
40
|
end
|
69
41
|
end
|
data/test/coercion_test.rb
CHANGED
@@ -9,20 +9,17 @@ class CoercionTest < BaseTest
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class Form < Reform::Form
|
12
|
-
include Coercion
|
12
|
+
# include Coercion
|
13
13
|
|
14
14
|
property :released_at, :type => DateTime
|
15
15
|
|
16
16
|
property :hit do
|
17
|
-
# include Coercion
|
18
17
|
property :length, :type => Integer
|
19
18
|
property :good, :type => Virtus::Attribute::Boolean
|
20
19
|
end
|
21
20
|
|
22
21
|
property :band do
|
23
|
-
include Coercion
|
24
22
|
property :label do
|
25
|
-
include Coercion
|
26
23
|
property :value, :type => Irreversible
|
27
24
|
end
|
28
25
|
end
|
@@ -66,7 +63,4 @@ class CoercionTest < BaseTest
|
|
66
63
|
end
|
67
64
|
|
68
65
|
# save
|
69
|
-
|
70
|
-
|
71
|
-
|
72
66
|
end
|
data/test/composition_test.rb
CHANGED
@@ -1,51 +1,145 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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, :virtual => true, :on => :song
|
16
|
+
property :requester, :on => :requester
|
17
|
+
property :captcha, :on => :song, :virtual => true
|
18
|
+
|
19
|
+
validates :name, :title, :channel, :presence => true
|
20
|
+
|
21
|
+
property :band, :on => :song do
|
22
|
+
property :title
|
23
|
+
end
|
4
24
|
end
|
5
25
|
|
6
|
-
let (:
|
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.mapper.must_be_kind_of Disposable::Composition }
|
7
42
|
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
11
50
|
end
|
12
51
|
|
13
|
-
|
14
|
-
|
15
|
-
|
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"})
|
16
63
|
end
|
17
|
-
end
|
18
64
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
Class.new(Reform::Representer) do
|
24
|
-
property :name, :on => :artist
|
25
|
-
property :title, :on => :song
|
26
|
-
end
|
27
|
-
).
|
28
|
-
new(:artist => duran, :song => rio)
|
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
|
29
69
|
|
30
|
-
|
31
|
-
|
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
|
+
)
|
32
79
|
end
|
33
|
-
end
|
34
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)
|
35
85
|
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
39
97
|
end
|
40
98
|
|
41
|
-
it "
|
42
|
-
|
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
|
43
105
|
end
|
44
106
|
|
45
|
-
it "
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
49
119
|
end
|
50
120
|
end
|
51
|
-
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
|
data/test/contract_test.rb
CHANGED
@@ -1,105 +1,46 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class ContractTest <
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
class ContractTest < MiniTest::Spec
|
4
|
+
Song = Struct.new(:title, :album, :composer)
|
5
|
+
Album = Struct.new(:name, :songs, :artist)
|
6
|
+
Artist = Struct.new(:name)
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
class ArtistForm < Reform::Form
|
9
|
+
property :name
|
10
|
+
end
|
11
|
+
|
12
|
+
class AlbumForm < Reform::Contract
|
13
|
+
property :name
|
14
|
+
validates :name, presence: true
|
12
15
|
|
13
16
|
collection :songs do
|
14
17
|
property :title
|
15
|
-
validates :title, :
|
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
|
18
|
+
validates :title, presence: true
|
22
19
|
|
23
|
-
property :
|
20
|
+
property :composer do
|
21
|
+
validates :name, presence: true
|
24
22
|
property :name
|
25
|
-
validates :name, :presence => true
|
26
23
|
end
|
27
|
-
# TODO: make band a required object.
|
28
24
|
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
25
|
|
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({}) }
|
26
|
+
property :artist, form: ArtistForm
|
56
27
|
end
|
57
28
|
|
29
|
+
let (:song) { Song.new("Broken") }
|
30
|
+
let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
31
|
+
let (:composer) { Artist.new("Greg Graffin") }
|
32
|
+
let (:artist) { Artist.new("Bad Religion") }
|
33
|
+
let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
|
58
34
|
|
59
|
-
|
60
|
-
# without name will always iterate.
|
61
|
-
it do
|
62
|
-
names = []
|
63
|
-
AlbumContract.representer { |dfn| names << dfn.name }
|
64
|
-
names.must_equal ["hit", "songs", "band"]
|
65
|
-
|
66
|
-
# this doesn't cache.
|
67
|
-
names = []
|
68
|
-
AlbumContract.representer { |dfn| names << dfn.name }
|
69
|
-
names.must_equal ["hit", "songs", "band"]
|
70
|
-
end
|
71
|
-
|
72
|
-
# with name caches representer per class and runs once.
|
73
|
-
it do
|
74
|
-
names = []
|
75
|
-
AlbumContract.representer(:sync) { |dfn| names << dfn.name }
|
76
|
-
names.must_equal ["hit", "songs", "band"]
|
77
|
-
|
78
|
-
# this does cache.
|
79
|
-
names = []
|
80
|
-
AlbumContract.representer(:sync) { |dfn| names << dfn.name }
|
81
|
-
names.must_equal []
|
82
|
-
end
|
83
|
-
|
84
|
-
# it allows iterating all properties, not only nested.
|
85
|
-
it do
|
86
|
-
names = []
|
87
|
-
AlbumContract.representer(:save, all: true) { |dfn| names << dfn.name }
|
88
|
-
names.must_equal ["title", "hit", "songs", "band"]
|
89
|
-
|
90
|
-
names = []
|
91
|
-
AlbumContract.representer(:save, all: true) { |dfn| names << dfn.name }
|
92
|
-
names.must_equal []
|
93
|
-
end
|
94
|
-
|
95
|
-
# test :superclass?
|
96
|
-
end
|
35
|
+
let (:form) { AlbumForm.new(album) }
|
97
36
|
|
98
|
-
|
99
|
-
|
37
|
+
# accept `property form: SongForm`.
|
38
|
+
it do
|
39
|
+
form.artist.must_be_instance_of ArtistForm
|
100
40
|
end
|
101
41
|
|
102
42
|
describe "#options_for" do
|
103
|
-
it {
|
43
|
+
it { AlbumForm.options_for(:name).inspect.must_match "#<Representable::Definition ==>name @options" }
|
44
|
+
it { AlbumForm.new(album).options_for(:name).inspect.must_match "#<Representable::Definition ==>name @options" }
|
104
45
|
end
|
105
|
-
end
|
46
|
+
end
|