disposable 0.0.9 → 0.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/.travis.yml +2 -5
- data/CHANGES.md +4 -0
- data/Gemfile +1 -1
- data/README.md +154 -1
- data/database.sqlite3 +0 -0
- data/disposable.gemspec +7 -7
- data/gemfiles/Gemfile.rails-3.0.lock +10 -8
- data/gemfiles/Gemfile.rails-3.2.lock +9 -7
- data/gemfiles/Gemfile.rails-4.0.lock +9 -7
- data/gemfiles/Gemfile.rails-4.1.lock +10 -8
- data/lib/disposable.rb +6 -7
- data/lib/disposable/callback.rb +174 -0
- data/lib/disposable/composition.rb +21 -58
- data/lib/disposable/expose.rb +49 -0
- data/lib/disposable/twin.rb +85 -38
- data/lib/disposable/twin/builder.rb +12 -30
- data/lib/disposable/twin/changed.rb +50 -0
- data/lib/disposable/twin/collection.rb +95 -0
- data/lib/disposable/twin/composition.rb +43 -15
- data/lib/disposable/twin/option.rb +1 -1
- data/lib/disposable/twin/persisted.rb +20 -0
- data/lib/disposable/twin/property_processor.rb +29 -0
- data/lib/disposable/twin/representer.rb +42 -14
- data/lib/disposable/twin/save.rb +19 -34
- data/lib/disposable/twin/schema.rb +31 -0
- data/lib/disposable/twin/setup.rb +38 -0
- data/lib/disposable/twin/sync.rb +114 -0
- data/lib/disposable/version.rb +1 -1
- data/test/api_semantics_test.rb +263 -0
- data/test/callback_group_test.rb +222 -0
- data/test/callbacks_test.rb +450 -0
- data/test/example.rb +40 -0
- data/test/expose_test.rb +92 -0
- data/test/persisted_test.rb +101 -0
- data/test/test_helper.rb +64 -0
- data/test/twin/benchmarking.rb +33 -0
- data/test/twin/builder_test.rb +32 -0
- data/test/twin/changed_test.rb +108 -0
- data/test/twin/collection_test.rb +223 -0
- data/test/twin/composition_test.rb +56 -25
- data/test/twin/expose_test.rb +73 -0
- data/test/twin/feature_test.rb +61 -0
- data/test/twin/from_test.rb +37 -0
- data/test/twin/inherit_test.rb +57 -0
- data/test/twin/option_test.rb +27 -0
- data/test/twin/readable_test.rb +57 -0
- data/test/twin/save_test.rb +192 -0
- data/test/twin/schema_test.rb +69 -0
- data/test/twin/setup_test.rb +139 -0
- data/test/twin/skip_unchanged_test.rb +64 -0
- data/test/twin/struct_test.rb +168 -0
- data/test/twin/sync_option_test.rb +228 -0
- data/test/twin/sync_test.rb +128 -0
- data/test/twin/twin_test.rb +49 -128
- data/test/twin/writeable_test.rb +56 -0
- metadata +106 -20
- data/STUFF +0 -4
- data/lib/disposable/twin/finders.rb +0 -29
- data/lib/disposable/twin/new.rb +0 -30
- data/lib/disposable/twin/save_.rb +0 -21
- data/test/composition_test.rb +0 -102
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FromTest < MiniTest::Spec
|
4
|
+
module Model
|
5
|
+
Album = Struct.new(:name, :composer)
|
6
|
+
Artist = Struct.new(:realname)
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
module Twin
|
11
|
+
class Album < Disposable::Twin
|
12
|
+
feature Sync
|
13
|
+
feature Save
|
14
|
+
feature Disposable::Twin::Expose
|
15
|
+
|
16
|
+
property :full_name, from: :name
|
17
|
+
|
18
|
+
property :artist, from: :composer do
|
19
|
+
property :name, from: :realname
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
let (:composer) { Model::Artist.new("AFI").extend(Disposable::Saveable) }
|
26
|
+
let (:album) { Model::Album.new("Black Sails In The Sunset", composer).extend(Disposable::Saveable) }
|
27
|
+
let (:twin) { Twin::Album.new(album) }
|
28
|
+
|
29
|
+
it do
|
30
|
+
twin.full_name.must_equal "Black Sails In The Sunset"
|
31
|
+
twin.artist.name.must_equal "AFI"
|
32
|
+
|
33
|
+
twin.save
|
34
|
+
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class InheritTest < MiniTest::Spec
|
4
|
+
module Model
|
5
|
+
Song = Struct.new(:title, :album)
|
6
|
+
Album = Struct.new(:name, :songs, :artist)
|
7
|
+
Artist = Struct.new(:name)
|
8
|
+
end
|
9
|
+
|
10
|
+
module Twin
|
11
|
+
class Album < Disposable::Twin
|
12
|
+
feature Setup
|
13
|
+
|
14
|
+
property :name, fromage: :_name
|
15
|
+
|
16
|
+
collection :songs do
|
17
|
+
property :name
|
18
|
+
end
|
19
|
+
|
20
|
+
property :artist do
|
21
|
+
property :name
|
22
|
+
|
23
|
+
def artist_id
|
24
|
+
1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class EmptyCompilation < Album
|
30
|
+
end
|
31
|
+
|
32
|
+
class Compilation < Album
|
33
|
+
property :name, writeable: false, inherit: true
|
34
|
+
|
35
|
+
property :artist, inherit: true do
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# definitions are not shared.
|
42
|
+
it do
|
43
|
+
Twin::Album.representer_class.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:fromage=>:_name, :private_name=>:name, :pass_options=>true, :parse_filter=>[], :render_filter=>[], :as=>\"name\"}>"
|
44
|
+
Twin::Compilation.representer_class.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:fromage=>:_name, :private_name=>:name, :pass_options=>true, :parse_filter=>[], :render_filter=>[], :as=>\"name\", :writeable=>false, :inherit=>true}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
let (:album) { Model::Album.new("In The Meantime And Inbetween Time", [], Model::Artist.new) }
|
49
|
+
|
50
|
+
it { Twin::Album.new(album).artist.artist_id.must_equal 1 }
|
51
|
+
|
52
|
+
# inherit inline twins when not overriding.
|
53
|
+
it { Twin::EmptyCompilation.new(album).artist.artist_id.must_equal 1 }
|
54
|
+
|
55
|
+
# inherit inline twins when overriding.
|
56
|
+
it { Twin::Compilation.new(album).artist.artist_id.must_equal 1 }
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# require "test_helper"
|
2
|
+
|
3
|
+
# class TwinOptionTest < TwinTest
|
4
|
+
# class Song < Disposable::Twin
|
5
|
+
# property :id # DISCUSS: needed for #save.
|
6
|
+
# property :title
|
7
|
+
|
8
|
+
# option :preview?
|
9
|
+
# option :highlight?
|
10
|
+
# end
|
11
|
+
|
12
|
+
# let (:song) { Model::Song.new(1, "Broken") }
|
13
|
+
# let (:twin) { Song.new(song, :preview? => false) }
|
14
|
+
|
15
|
+
|
16
|
+
# # properties are read from model.
|
17
|
+
# it { twin.id.must_equal 1 }
|
18
|
+
# it { twin.title.must_equal "Broken" }
|
19
|
+
|
20
|
+
# # option is not delegated to model.
|
21
|
+
# it { twin.preview?.must_equal false }
|
22
|
+
# # not passing option means zero.
|
23
|
+
# it { twin.highlight?.must_equal nil }
|
24
|
+
|
25
|
+
# # passing both options.
|
26
|
+
# it { Song.new(song, preview?: true, highlight?: false).preview?.must_equal true }
|
27
|
+
# end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ReadableTest < MiniTest::Spec
|
4
|
+
Credentials = Struct.new(:password, :credit_card) do
|
5
|
+
def password
|
6
|
+
raise "don't call me!"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
CreditCard = Struct.new(:name, :number) do
|
11
|
+
def number
|
12
|
+
raise "don't call me!"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class PasswordForm < Disposable::Twin
|
17
|
+
feature Setup
|
18
|
+
feature Sync
|
19
|
+
|
20
|
+
property :password, readable: false
|
21
|
+
|
22
|
+
property :credit_card do
|
23
|
+
property :name
|
24
|
+
property :number, readable: false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
let (:cred) { Credentials.new("secret", CreditCard.new("Jonny", "0987654321")) }
|
29
|
+
|
30
|
+
let (:twin) { PasswordForm.new(cred) }
|
31
|
+
|
32
|
+
it {
|
33
|
+
twin.password.must_equal nil # not readable.
|
34
|
+
twin.credit_card.name.must_equal "Jonny"
|
35
|
+
twin.credit_card.number.must_equal nil # not readable.
|
36
|
+
|
37
|
+
# manual setting on the twin works.
|
38
|
+
twin.password = "123"
|
39
|
+
twin.password.must_equal "123"
|
40
|
+
|
41
|
+
twin.credit_card.number = "456"
|
42
|
+
twin.credit_card.number.must_equal "456"
|
43
|
+
|
44
|
+
twin.sync
|
45
|
+
|
46
|
+
# it writes, but does not read.
|
47
|
+
cred.inspect.must_equal '#<struct ReadableTest::Credentials password="123", credit_card=#<struct ReadableTest::CreditCard name="Jonny", number="456">>'
|
48
|
+
|
49
|
+
# test sync{}.
|
50
|
+
hash = {}
|
51
|
+
twin.sync do |nested|
|
52
|
+
hash = nested
|
53
|
+
end
|
54
|
+
|
55
|
+
hash.must_equal("password"=> "123", "credit_card"=>{"name"=>"Jonny", "number"=>"456"})
|
56
|
+
}
|
57
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SaveTest < MiniTest::Spec
|
4
|
+
module Model
|
5
|
+
Song = Struct.new(:title, :composer)
|
6
|
+
Album = Struct.new(:name, :songs, :artist)
|
7
|
+
Artist = Struct.new(:name)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module Twin
|
12
|
+
class Album < Disposable::Twin
|
13
|
+
feature Setup
|
14
|
+
feature Sync
|
15
|
+
feature Save
|
16
|
+
|
17
|
+
property :name
|
18
|
+
|
19
|
+
collection :songs do
|
20
|
+
property :title
|
21
|
+
|
22
|
+
property :composer do
|
23
|
+
property :name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
property :artist do
|
28
|
+
property :name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
let (:song) { Model::Song.new().extend(Disposable::Saveable) }
|
35
|
+
let (:composer) { Model::Artist.new(nil).extend(Disposable::Saveable) }
|
36
|
+
let (:song_with_composer) { Model::Song.new(nil, composer).extend(Disposable::Saveable) }
|
37
|
+
let (:artist) { Model::Artist.new(nil).extend(Disposable::Saveable) }
|
38
|
+
|
39
|
+
|
40
|
+
let (:album) { Model::Album.new(nil, [song, song_with_composer], artist).extend(Disposable::Saveable) }
|
41
|
+
|
42
|
+
let (:twin) { Twin::Album.new(album) }
|
43
|
+
|
44
|
+
# with populated model.
|
45
|
+
it do
|
46
|
+
fill_out!(twin)
|
47
|
+
|
48
|
+
twin.save
|
49
|
+
|
50
|
+
# sync happened.
|
51
|
+
album.name.must_equal "Live And Dangerous"
|
52
|
+
album.songs[0].must_be_instance_of Model::Song
|
53
|
+
album.songs[1].must_be_instance_of Model::Song
|
54
|
+
album.songs[0].title.must_equal "Southbound"
|
55
|
+
album.songs[1].title.must_equal "The Boys Are Back In Town"
|
56
|
+
album.songs[1].composer.must_be_instance_of Model::Artist
|
57
|
+
album.songs[1].composer.name.must_equal "Lynott"
|
58
|
+
album.artist.must_be_instance_of Model::Artist
|
59
|
+
album.artist.name.must_equal "Thin Lizzy"
|
60
|
+
|
61
|
+
# saved?
|
62
|
+
album.saved?.must_equal true
|
63
|
+
album.songs[0].saved?.must_equal true
|
64
|
+
album.songs[1].saved?.must_equal true
|
65
|
+
album.songs[1].composer.saved?.must_equal true
|
66
|
+
album.artist.saved?.must_equal true
|
67
|
+
end
|
68
|
+
|
69
|
+
#save returns result.
|
70
|
+
it { twin.save.must_equal true }
|
71
|
+
it do
|
72
|
+
album.instance_eval { def save; false; end }
|
73
|
+
twin.save.must_equal false
|
74
|
+
end
|
75
|
+
|
76
|
+
# with save{}.
|
77
|
+
it do
|
78
|
+
twin = Twin::Album.new(album)
|
79
|
+
|
80
|
+
# this usually happens in Contract::Validate or in from_* in a representer
|
81
|
+
fill_out!(twin)
|
82
|
+
|
83
|
+
nested_hash = nil
|
84
|
+
twin.save do |hash|
|
85
|
+
nested_hash = hash
|
86
|
+
end
|
87
|
+
|
88
|
+
nested_hash.must_equal({"name"=>"Live And Dangerous", "songs"=>[{"title"=>"Southbound"}, {"title"=>"The Boys Are Back In Town", "composer"=>{"name"=>"Lynott"}}], "artist"=>{"name"=>"Thin Lizzy"}})
|
89
|
+
|
90
|
+
# nothing written to model.
|
91
|
+
album.name.must_equal nil
|
92
|
+
album.songs[0].title.must_equal nil
|
93
|
+
album.songs[1].title.must_equal nil
|
94
|
+
album.songs[1].composer.name.must_equal nil
|
95
|
+
album.artist.name.must_equal nil
|
96
|
+
|
97
|
+
# nothing saved.
|
98
|
+
# saved?
|
99
|
+
album.saved?.must_equal nil
|
100
|
+
album.songs[0].saved?.must_equal nil
|
101
|
+
album.songs[1].saved?.must_equal nil
|
102
|
+
album.songs[1].composer.saved?.must_equal nil
|
103
|
+
album.artist.saved?.must_equal nil
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# save: false
|
108
|
+
module Twin
|
109
|
+
class AlbumWithSaveFalse < Disposable::Twin
|
110
|
+
feature Setup
|
111
|
+
feature Sync
|
112
|
+
feature Save
|
113
|
+
|
114
|
+
property :name
|
115
|
+
|
116
|
+
collection :songs, save: false do
|
117
|
+
property :title
|
118
|
+
|
119
|
+
property :composer do
|
120
|
+
property :name
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
property :artist do
|
125
|
+
property :name
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# with save: false.
|
131
|
+
it do
|
132
|
+
twin = Twin::AlbumWithSaveFalse.new(album)
|
133
|
+
|
134
|
+
fill_out!(twin)
|
135
|
+
|
136
|
+
twin.save
|
137
|
+
|
138
|
+
# sync happened.
|
139
|
+
album.name.must_equal "Live And Dangerous"
|
140
|
+
album.songs[0].must_be_instance_of Model::Song
|
141
|
+
album.songs[1].must_be_instance_of Model::Song
|
142
|
+
album.songs[0].title.must_equal "Southbound"
|
143
|
+
album.songs[1].title.must_equal "The Boys Are Back In Town"
|
144
|
+
album.songs[1].composer.must_be_instance_of Model::Artist
|
145
|
+
album.songs[1].composer.name.must_equal "Lynott"
|
146
|
+
album.artist.must_be_instance_of Model::Artist
|
147
|
+
album.artist.name.must_equal "Thin Lizzy"
|
148
|
+
|
149
|
+
# saved?
|
150
|
+
album.saved?.must_equal true
|
151
|
+
album.songs[0].saved?.must_equal nil
|
152
|
+
album.songs[1].saved?.must_equal nil
|
153
|
+
album.songs[1].composer.saved?.must_equal nil # doesn't get saved.
|
154
|
+
album.artist.saved?.must_equal true
|
155
|
+
end
|
156
|
+
|
157
|
+
def fill_out!(twin)
|
158
|
+
twin.name = "Live And Dangerous"
|
159
|
+
twin.songs[0].title = "Southbound"
|
160
|
+
twin.songs[1].title = "The Boys Are Back In Town"
|
161
|
+
twin.songs[1].composer.name = "Lynott"
|
162
|
+
twin.artist.name = "Thin Lizzy"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# TODO: with block
|
168
|
+
|
169
|
+
# class SaveWithDynamicOptionsTest < MiniTest::Spec
|
170
|
+
# Song = Struct.new(:id, :title, :length) do
|
171
|
+
# include Disposable::Saveable
|
172
|
+
# end
|
173
|
+
|
174
|
+
# class SongForm < Reform::Form
|
175
|
+
# property :title#, save: false
|
176
|
+
# property :length, virtual: true
|
177
|
+
# end
|
178
|
+
|
179
|
+
# let (:song) { Song.new }
|
180
|
+
# let (:form) { SongForm.new(song) }
|
181
|
+
|
182
|
+
# # we have access to original input value and outside parameters.
|
183
|
+
# it "xxx" do
|
184
|
+
# form.validate("title" => "A Poor Man's Memory", "length" => 10)
|
185
|
+
# length_seconds = 120
|
186
|
+
# form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
|
187
|
+
|
188
|
+
# song.title.must_equal "A Poor Man's Memory"
|
189
|
+
# song.length.must_equal nil
|
190
|
+
# song.id.must_equal "10: 120"
|
191
|
+
# end
|
192
|
+
# end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
require "disposable/twin/schema"
|
4
|
+
|
5
|
+
class SchemaTest < MiniTest::Spec
|
6
|
+
module Representer
|
7
|
+
include Representable
|
8
|
+
|
9
|
+
property :id
|
10
|
+
property :title, writeable: false, deserializer: {skip_parse: "skip lambda"}
|
11
|
+
property :songs, readable: false, deserializer: {skip_parse: "another lambda", music: true, writeable: false} do
|
12
|
+
property :name, as: "Name", deserializer: {skip_parse: "a crazy cool instance method"}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Hello
|
17
|
+
def hello
|
18
|
+
"hello"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Ciao
|
23
|
+
def ciao
|
24
|
+
"ciao"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module Gday
|
29
|
+
def hello
|
30
|
+
"G'day"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it do
|
35
|
+
decorator = Disposable::Twin::Schema.from(Representer, superclass: Representable::Decorator,
|
36
|
+
include: [Hello, Gday, Ciao], # Hello will win over Gday.
|
37
|
+
options_from: :deserializer,
|
38
|
+
representer_from: lambda { |nested| nested }
|
39
|
+
)
|
40
|
+
|
41
|
+
# include: works.
|
42
|
+
decorator.new(nil).hello.must_equal "hello"
|
43
|
+
decorator.new(nil).ciao.must_equal "ciao"
|
44
|
+
|
45
|
+
decorator.representable_attrs.get(:id).inspect.must_equal "#<Representable::Definition ==>id @options={:parse_filter=>[], :render_filter=>[], :as=>\"id\"}>"
|
46
|
+
decorator.representable_attrs.get(:title).inspect.must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :parse_filter=>[], :render_filter=>[], :as=>\"title\", :skip_parse=>\"skip lambda\"}>"
|
47
|
+
|
48
|
+
songs = decorator.representable_attrs.get(:songs)
|
49
|
+
options = songs.instance_variable_get(:@options)
|
50
|
+
nested_extend = options.delete(:extend)
|
51
|
+
options.inspect.must_equal "{:readable=>false, :deserializer=>{:skip_parse=>\"another lambda\", :music=>true, :writeable=>false}, :parse_filter=>[], :render_filter=>[], :as=>\"songs\", :_inline=>true, :skip_parse=>\"another lambda\", :music=>true, :writeable=>false}"
|
52
|
+
|
53
|
+
# nested works.
|
54
|
+
nested_extend.new(nil).hello.must_equal "hello"
|
55
|
+
nested_extend.new(nil).ciao.must_equal "ciao"
|
56
|
+
|
57
|
+
nested_extend.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :parse_filter=>[], :render_filter=>[], :skip_parse=>\"a crazy cool instance method\"}>"
|
58
|
+
end
|
59
|
+
|
60
|
+
# :options_from and :include is optional
|
61
|
+
it do
|
62
|
+
decorator = Disposable::Twin::Schema.from(Representer, superclass: Representable::Decorator,
|
63
|
+
representer_from: lambda { |nested| nested }
|
64
|
+
)
|
65
|
+
|
66
|
+
decorator.representable_attrs.get(:id).inspect.must_equal "#<Representable::Definition ==>id @options={:parse_filter=>[], :render_filter=>[], :as=>\"id\"}>"
|
67
|
+
decorator.representable_attrs.get(:title).inspect.must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :parse_filter=>[], :render_filter=>[], :as=>\"title\"}>"
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TwinSetupTest < MiniTest::Spec
|
4
|
+
module Model
|
5
|
+
Song = Struct.new(:id, :title, :album, :composer)
|
6
|
+
Album = Struct.new(:id, :name, :songs, :artist)
|
7
|
+
Artist = Struct.new(:id)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module Twin
|
12
|
+
class Album < Disposable::Twin
|
13
|
+
property :id
|
14
|
+
property :name
|
15
|
+
collection :songs, twin: lambda { |*| Song }
|
16
|
+
property :artist, twin: lambda { |*| Artist }
|
17
|
+
|
18
|
+
include Setup
|
19
|
+
end
|
20
|
+
|
21
|
+
class Song < Disposable::Twin
|
22
|
+
property :id
|
23
|
+
property :composer, twin: lambda { |*| Artist }
|
24
|
+
|
25
|
+
include Setup
|
26
|
+
end
|
27
|
+
|
28
|
+
class Artist < Disposable::Twin
|
29
|
+
property :id
|
30
|
+
|
31
|
+
include Setup
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
let (:song) { Model::Song.new(1, "Broken", nil) }
|
37
|
+
let (:composer) { Model::Artist.new(2) }
|
38
|
+
let (:song_with_composer) { Model::Song.new(1, "Broken", nil, composer) }
|
39
|
+
let (:artist) { Model::Artist.new(9) }
|
40
|
+
|
41
|
+
describe "with songs: [song, song{composer}]" do
|
42
|
+
let (:album) { Model::Album.new(1, "The Rest Is Silence", [song, song_with_composer], artist) }
|
43
|
+
|
44
|
+
it do
|
45
|
+
twin = Twin::Album.new(album)
|
46
|
+
|
47
|
+
twin.songs.size.must_equal 2
|
48
|
+
twin.songs.must_be_instance_of Disposable::Twin::Collection
|
49
|
+
|
50
|
+
twin.songs[0].must_be_instance_of Twin::Song
|
51
|
+
twin.songs[0].id.must_equal 1
|
52
|
+
|
53
|
+
twin.songs[1].must_be_instance_of Twin::Song
|
54
|
+
twin.songs[1].id.must_equal 1
|
55
|
+
twin.songs[1].composer.must_be_instance_of Twin::Artist
|
56
|
+
twin.songs[1].composer.id.must_equal 2
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "with songs: [] and artist: nil" do
|
61
|
+
let (:album) { Model::Album.new(1, "The Rest Is Silence", [], nil) }
|
62
|
+
|
63
|
+
it do
|
64
|
+
twin = Twin::Album.new(album)
|
65
|
+
|
66
|
+
twin.songs.size.must_equal 0
|
67
|
+
twin.songs.must_be_instance_of Disposable::Twin::Collection
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# DISCUSS: do we need to cover that (songs: nil in model)?
|
72
|
+
# describe "with non-existent :songs" do
|
73
|
+
# let (:album) { Model::Album.new(1, "The Rest Is Silence", nil) }
|
74
|
+
|
75
|
+
# it do
|
76
|
+
# twin = Twin::Album.new(album)
|
77
|
+
|
78
|
+
# twin.songs.size.must_equal 0
|
79
|
+
# twin.songs.must_be_instance_of Disposable::Twin::Collection
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
end
|
83
|
+
|
84
|
+
# test inline twin building and setup.
|
85
|
+
class TwinSetupWithInlineTwinsTest < MiniTest::Spec
|
86
|
+
module Model
|
87
|
+
Song = Struct.new(:id, :composer)
|
88
|
+
Album = Struct.new(:id, :name, :songs, :artist)
|
89
|
+
Artist = Struct.new(:id)
|
90
|
+
end
|
91
|
+
|
92
|
+
class AlbumForm < Disposable::Twin
|
93
|
+
feature Setup
|
94
|
+
|
95
|
+
property :id
|
96
|
+
property :name
|
97
|
+
|
98
|
+
collection :songs do # default_inline_class: Disposable::Twin
|
99
|
+
property :id
|
100
|
+
|
101
|
+
property :composer do
|
102
|
+
property :id
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
property :artist do
|
107
|
+
property :id
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
let (:song) { Model::Song.new(1) }
|
112
|
+
let (:composer) { Model::Artist.new(2) }
|
113
|
+
let (:song_with_composer) { Model::Song.new(3, composer) }
|
114
|
+
let (:artist) { Model::Artist.new(9) }
|
115
|
+
let (:album) { Model::Album.new(0, "Toto Live", [song, song_with_composer], artist) }
|
116
|
+
|
117
|
+
it do
|
118
|
+
twin = AlbumForm.new(album)
|
119
|
+
# pp twin
|
120
|
+
|
121
|
+
twin.id.must_equal 0
|
122
|
+
twin.name.must_equal "Toto Live"
|
123
|
+
|
124
|
+
twin.artist.must_be_kind_of Disposable::Twin
|
125
|
+
twin.artist.id.must_equal 9
|
126
|
+
|
127
|
+
twin.songs.must_be_instance_of Disposable::Twin::Collection
|
128
|
+
|
129
|
+
# nil nested objects work (no composer)
|
130
|
+
twin.songs[0].must_be_kind_of Disposable::Twin
|
131
|
+
twin.songs[0].id.must_equal 1
|
132
|
+
|
133
|
+
twin.songs[1].must_be_kind_of Disposable::Twin
|
134
|
+
twin.songs[1].id.must_equal 3
|
135
|
+
|
136
|
+
twin.songs[1].composer.must_be_kind_of Disposable::Twin
|
137
|
+
twin.songs[1].composer.id.must_equal 2
|
138
|
+
end
|
139
|
+
end
|