disposable 0.0.9 → 0.1.0

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 +2 -5
  3. data/CHANGES.md +4 -0
  4. data/Gemfile +1 -1
  5. data/README.md +154 -1
  6. data/database.sqlite3 +0 -0
  7. data/disposable.gemspec +7 -7
  8. data/gemfiles/Gemfile.rails-3.0.lock +10 -8
  9. data/gemfiles/Gemfile.rails-3.2.lock +9 -7
  10. data/gemfiles/Gemfile.rails-4.0.lock +9 -7
  11. data/gemfiles/Gemfile.rails-4.1.lock +10 -8
  12. data/lib/disposable.rb +6 -7
  13. data/lib/disposable/callback.rb +174 -0
  14. data/lib/disposable/composition.rb +21 -58
  15. data/lib/disposable/expose.rb +49 -0
  16. data/lib/disposable/twin.rb +85 -38
  17. data/lib/disposable/twin/builder.rb +12 -30
  18. data/lib/disposable/twin/changed.rb +50 -0
  19. data/lib/disposable/twin/collection.rb +95 -0
  20. data/lib/disposable/twin/composition.rb +43 -15
  21. data/lib/disposable/twin/option.rb +1 -1
  22. data/lib/disposable/twin/persisted.rb +20 -0
  23. data/lib/disposable/twin/property_processor.rb +29 -0
  24. data/lib/disposable/twin/representer.rb +42 -14
  25. data/lib/disposable/twin/save.rb +19 -34
  26. data/lib/disposable/twin/schema.rb +31 -0
  27. data/lib/disposable/twin/setup.rb +38 -0
  28. data/lib/disposable/twin/sync.rb +114 -0
  29. data/lib/disposable/version.rb +1 -1
  30. data/test/api_semantics_test.rb +263 -0
  31. data/test/callback_group_test.rb +222 -0
  32. data/test/callbacks_test.rb +450 -0
  33. data/test/example.rb +40 -0
  34. data/test/expose_test.rb +92 -0
  35. data/test/persisted_test.rb +101 -0
  36. data/test/test_helper.rb +64 -0
  37. data/test/twin/benchmarking.rb +33 -0
  38. data/test/twin/builder_test.rb +32 -0
  39. data/test/twin/changed_test.rb +108 -0
  40. data/test/twin/collection_test.rb +223 -0
  41. data/test/twin/composition_test.rb +56 -25
  42. data/test/twin/expose_test.rb +73 -0
  43. data/test/twin/feature_test.rb +61 -0
  44. data/test/twin/from_test.rb +37 -0
  45. data/test/twin/inherit_test.rb +57 -0
  46. data/test/twin/option_test.rb +27 -0
  47. data/test/twin/readable_test.rb +57 -0
  48. data/test/twin/save_test.rb +192 -0
  49. data/test/twin/schema_test.rb +69 -0
  50. data/test/twin/setup_test.rb +139 -0
  51. data/test/twin/skip_unchanged_test.rb +64 -0
  52. data/test/twin/struct_test.rb +168 -0
  53. data/test/twin/sync_option_test.rb +228 -0
  54. data/test/twin/sync_test.rb +128 -0
  55. data/test/twin/twin_test.rb +49 -128
  56. data/test/twin/writeable_test.rb +56 -0
  57. metadata +106 -20
  58. data/STUFF +0 -4
  59. data/lib/disposable/twin/finders.rb +0 -29
  60. data/lib/disposable/twin/new.rb +0 -30
  61. data/lib/disposable/twin/save_.rb +0 -21
  62. data/test/composition_test.rb +0 -102
@@ -0,0 +1,223 @@
1
+ require 'test_helper'
2
+
3
+ # reason: unique API for collection (adding, removing, deleting, etc.)
4
+ # delay DB write until saving Twin
5
+
6
+ # TODO: eg "after delete hook (dynamic_delete)", after_add
7
+
8
+ class TwinCollectionTest < MiniTest::Spec
9
+ module Model
10
+ Song = Struct.new(:id, :title, :album)
11
+ Album = Struct.new(:id, :name, :songs, :artist)
12
+ end
13
+
14
+
15
+ module Twin
16
+ class Album < Disposable::Twin
17
+ property :id # DISCUSS: needed for #save.
18
+ property :name
19
+ collection :songs, :twin => lambda { |*| Song }
20
+ end
21
+
22
+ class Song < Disposable::Twin
23
+ property :id # DISCUSS: needed for #save.
24
+ property :title
25
+ property :album, :twin => Album
26
+ end
27
+ end
28
+
29
+ let (:song) { Model::Song.new(1, "Broken", nil) }
30
+ let (:album) { Model::Album.new(1, "The Rest Is Silence", [song]) }
31
+
32
+ describe "reader for collection" do
33
+ it do
34
+ twin = Twin::Album.new(album)
35
+
36
+ twin.songs.size.must_equal 1
37
+ twin.songs[0].title.must_equal "Broken"
38
+ twin.songs.must_be_instance_of Disposable::Twin::Collection
39
+
40
+ end
41
+ end
42
+ end
43
+
44
+ require "disposable/twin/sync"
45
+ require "disposable/twin/save"
46
+
47
+ class TwinCollectionActiveRecordTest < MiniTest::Spec
48
+ module Twin
49
+ class Album < Disposable::Twin
50
+ property :id # DISCUSS: needed for #save.
51
+ property :name
52
+ collection :songs, :twin => lambda { |*| Song }
53
+ property :artist, twin: lambda { |*| Artist }
54
+
55
+ include Sync
56
+ include Save
57
+ include Setup
58
+ include Collection::Semantics
59
+ end
60
+
61
+ class Song < Disposable::Twin
62
+ property :id # DISCUSS: needed for #save.
63
+ property :title
64
+
65
+ # property :persisted?, readonly: true # TODO: implement that!!!! for #sync
66
+
67
+ include Sync
68
+ include Save
69
+ end
70
+
71
+ class Artist < Disposable::Twin
72
+ end
73
+ end
74
+
75
+ let (:album) { Album.create(name: "The Rest Is Silence") }
76
+ let (:song1) { Song.new(title: "Snorty Pacifical Rascal") } # unsaved.
77
+ let (:song2) { Song.create(title: "At Any Cost") } # saved.
78
+ let (:twin) { Twin::Album.new(album) }
79
+
80
+ it do
81
+ # TODO: test all writers.
82
+ twin.songs << song1 # assuming that we add AR model here.
83
+ twin.songs << song2
84
+
85
+ twin.songs.size.must_equal 2
86
+
87
+ twin.songs[0].must_be_instance_of Twin::Song # twin wraps << added in twin.
88
+ twin.songs[1].must_be_instance_of Twin::Song
89
+
90
+ # twin.songs[0].persisted?.must_equal false
91
+ twin.songs[0].send(:model).persisted?.must_equal false
92
+ twin.songs[1].send(:model).persisted?.must_equal true
93
+
94
+ album.songs.size.must_equal 0 # nothing synced, yet.
95
+
96
+ # sync: delete removed items, add new?
97
+
98
+ # save
99
+ twin.save
100
+
101
+ album.persisted?.must_equal true
102
+ album.name.must_equal "The Rest Is Silence"
103
+
104
+ album.songs.size.must_equal 2 # synced!
105
+
106
+ album.songs[0].persisted?.must_equal true
107
+ album.songs[1].persisted?.must_equal true
108
+ album.songs[0].title.must_equal "Snorty Pacifical Rascal"
109
+ album.songs[1].title.must_equal "At Any Cost"
110
+ end
111
+
112
+ # test with adding to existing collection [song1] << song2
113
+
114
+ # TODO: #delete non-existent twin.
115
+ describe "#delete" do
116
+ let (:album) { Album.create(name: "The Rest Is Silence", songs: [song1]) }
117
+
118
+ it do
119
+ twin.songs.delete(twin.songs.first)
120
+
121
+ twin.songs.size.must_equal 0
122
+ album.songs.size.must_equal 1 # not synced, yet.
123
+
124
+ twin.save
125
+
126
+ twin.songs.size.must_equal 0
127
+ album.songs.size.must_equal 0
128
+ song1.persisted?.must_equal true
129
+ end
130
+
131
+ # non-existant delete.
132
+ it do
133
+ twin.songs.delete("non-existant") # won't delete anything.
134
+ twin.songs.size.must_equal 1
135
+ end
136
+ end
137
+
138
+ describe "#destroy" do
139
+ let (:album) { Album.create(name: "The Rest Is Silence", songs: [song1]) }
140
+
141
+ it do
142
+ twin.songs.destroy(twin.songs.first)
143
+
144
+ twin.songs.size.must_equal 0
145
+ album.songs.size.must_equal 1 # not synced, yet.
146
+
147
+ twin.save
148
+
149
+ twin.songs.size.must_equal 0
150
+ album.songs.size.must_equal 0
151
+ song1.persisted?.must_equal false
152
+ end
153
+ end
154
+
155
+
156
+ describe "#added" do
157
+ let (:album) { Album.create(name: "The Rest Is Silence", songs: [song1]) }
158
+
159
+ it do
160
+ twin = Twin::Album.new(album)
161
+
162
+ twin.songs.added.must_equal []
163
+ twin.songs << song2
164
+ twin.songs.added.must_equal [twin.songs[1]]
165
+ twin.songs.insert(2, song3 = Song.new)
166
+ twin.songs.added.must_equal [twin.songs[1], twin.songs[2]]
167
+
168
+ # TODO: what to do if we override an item (insert)?
169
+ end
170
+ end
171
+
172
+ describe "#deleted" do
173
+ let (:album) { Album.create(name: "The Rest Is Silence", songs: [song1, song2, song3 = Song.new]) }
174
+
175
+ it do
176
+ twin = Twin::Album.new(album)
177
+
178
+ twin.songs.deleted.must_equal []
179
+
180
+ twin.songs.delete(deleted1 = twin.songs[-1])
181
+ twin.songs.delete(deleted2 = twin.songs[-1])
182
+
183
+ twin.songs.must_equal [twin.songs[0]]
184
+
185
+ twin.songs.deleted.must_equal [deleted1, deleted2]
186
+ end
187
+
188
+ # non-existant delete.
189
+ it do
190
+ twin.songs.delete("non-existant") # won't delete anything.
191
+ twin.songs.deleted.must_equal []
192
+ end
193
+ end
194
+ end
195
+
196
+
197
+ class CollectionUnitTest < MiniTest::Spec
198
+ module Twin
199
+ class Album < Disposable::Twin
200
+ end
201
+
202
+ class Song < Disposable::Twin
203
+ property :album, twin: Twin::Album
204
+ end
205
+ end
206
+
207
+ module Model
208
+ Album = Struct.new(:id, :name, :songs, :artist)
209
+ end
210
+
211
+ let(:collection) { Disposable::Twin::Collection.new(Disposable::Twin::Twinner.new(Twin::Song.representer_class.representable_attrs.get(:album)), []) }
212
+
213
+ # #insert(index, model)
214
+ it do
215
+ collection.insert(0, Model::Album.new).must_be_instance_of Twin::Album
216
+ end
217
+
218
+ # #<<
219
+ it do
220
+ collection << Model::Album.new
221
+ collection[0].must_be_instance_of Twin::Album
222
+ end
223
+ end
@@ -1,20 +1,18 @@
1
1
  require 'test_helper'
2
2
 
3
- require 'disposable/twin/composition'
4
-
3
+ # Disposable::Twin::Composition.
5
4
  class TwinCompositionTest < MiniTest::Spec
6
- class Request < Disposable::Twin::Composition
7
- property :song_title, :on => :song, :from => :title
8
- property :song_id, :on => :song, :from => :id
9
-
10
- property :name, :on => :requester
11
- property :id, :on => :requester
5
+ class Request < Disposable::Twin
6
+ include Sync
7
+ include Save
8
+ include Composition
12
9
 
13
- # map ...
10
+ property :song_title, on: :song, from: :title
11
+ property :song_id, on: :song, from: :id
14
12
 
15
- # def id
16
- # make map(name, options)
17
- # option :played?
13
+ property :name, on: :requester
14
+ property :id, on: :requester
15
+ property :captcha, readable: false, writeable: false, on: :requester # TODO: allow both, virtual with and without :on.
18
16
  end
19
17
 
20
18
  module Model
@@ -22,24 +20,57 @@ class TwinCompositionTest < MiniTest::Spec
22
20
  Requester = Struct.new(:id, :name)
23
21
  end
24
22
 
25
- let (:requester) { Model::Requester.new(1, "Greg Howe") }
26
- let (:song) { Model::Song.new(2, "Extraction") }
23
+ let (:requester) { Model::Requester.new(1, "Greg Howe").extend(Disposable::Saveable) }
24
+ let (:song) { Model::Song.new(2, "Extraction").extend(Disposable::Saveable) }
25
+
26
+ let (:request) { Request.new(song: song, requester: requester) }
27
+
28
+ it do
29
+ request.song_title.must_equal "Extraction"
30
+ request.song_id.must_equal 2
31
+ request.name.must_equal "Greg Howe"
32
+ request.id.must_equal 1
33
+
34
+ request.song_title = "Tease"
35
+ request.name = "Wooten"
36
+
37
+
38
+ request.song_title.must_equal "Tease"
39
+ request.name.must_equal "Wooten"
40
+
41
+ # does not write to model.
42
+ song.title.must_equal "Extraction"
43
+ requester.name.must_equal "Greg Howe"
44
+
45
+
46
+ request.save
47
+
48
+ # make sure models got synced and saved.
49
+ song.id.must_equal 2
50
+ song.title.must_equal "Tease"
51
+ requester.id.must_equal 1
52
+ requester.name.must_equal "Wooten"
53
+
54
+ song.saved?.must_equal true
55
+ requester.saved?.must_equal true
56
+ end
27
57
 
28
- let (:request) { Request.new(:song => song, :requester => requester) }
58
+ # save with block.
59
+ it do
60
+ request.song_title = "Tease"
61
+ request.name = "Wooten"
62
+ request.captcha = "Awesome!"
29
63
 
30
- it { request.song_title.must_equal "Extraction" }
31
- it { request.name.must_equal "Greg Howe" }
64
+ # does not write to model.
65
+ song.title.must_equal "Extraction"
66
+ requester.name.must_equal "Greg Howe"
32
67
 
33
68
 
34
- describe "setter" do
35
- before do
36
- request.song_title = "Tease"
37
- request.name = "Wooten"
69
+ nested_hash = nil
70
+ request.save do |hash|
71
+ nested_hash = hash
38
72
  end
39
73
 
40
- it { request.song_title.must_equal "Tease" }
41
- # no writing to model.
42
- it { song.title.must_equal "Extraction" }
43
- it { request.name.must_equal "Wooten" }
74
+ nested_hash.must_equal(:song=>{"title"=>"Tease", "id"=>2}, :requester=>{"name"=>"Wooten", "id"=>1, "captcha"=>"Awesome!"})
44
75
  end
45
76
  end
@@ -0,0 +1,73 @@
1
+ require 'test_helper'
2
+
3
+ # Disposable::Twin::Expose.
4
+ class TwinExposeTest < MiniTest::Spec
5
+ class Request < Disposable::Twin
6
+ feature Sync
7
+ feature Save
8
+ feature Expose
9
+
10
+ property :song_title, from: :title
11
+ property :id
12
+ # virtual.
13
+ property :captcha, readable: false, writeable: false
14
+ # nested.
15
+ property :album do
16
+ property :name, from: :getName
17
+ end
18
+ end
19
+
20
+ module Model
21
+ Song = Struct.new(:id, :title, :album)
22
+ Album = Struct.new(:getName)
23
+ end
24
+
25
+ let (:album) { Model::Album.new("Appeal To Reason").extend(Disposable::Saveable) }
26
+ let (:song) { Model::Song.new(2, "Extraction", album).extend(Disposable::Saveable) }
27
+
28
+ let (:request) { Request.new(song) }
29
+
30
+ it do
31
+ request.song_title.must_equal "Extraction"
32
+ request.id.must_equal 2
33
+
34
+ request.song_title = "Tease"
35
+ request.id = 1
36
+
37
+
38
+ request.song_title.must_equal "Tease"
39
+ request.id.must_equal 1
40
+
41
+ # does not write to model.
42
+ song.title.must_equal "Extraction"
43
+ song.id.must_equal 2
44
+
45
+ request.save
46
+
47
+ # make sure models got synced and saved.
48
+ song.id.must_equal 1
49
+ song.title.must_equal "Tease"
50
+ song.album.must_equal album # nested objects don't get twinned or anything.
51
+
52
+ song.saved?.must_equal true
53
+ end
54
+
55
+ # save with block.
56
+ it do
57
+ request.song_title = "Tease"
58
+ request.id = 1
59
+ request.captcha = "Awesome!"
60
+
61
+ nested_hash = nil
62
+ request.save do |hash|
63
+ nested_hash = hash
64
+ end
65
+
66
+ nested_hash.must_equal({"title"=>"Tease", "id"=>1, "captcha" => "Awesome!", "album"=>{"getName"=>"Appeal To Reason"}})
67
+
68
+ # does not write to model.
69
+ song.title.must_equal "Extraction"
70
+ song.id.must_equal 2
71
+ album.getName.must_equal "Appeal To Reason"
72
+ end
73
+ end
@@ -0,0 +1,61 @@
1
+ require 'test_helper'
2
+
3
+ class FeatureTest < MiniTest::Spec
4
+ Song = Struct.new(:title, :album, :composer)
5
+ Album = Struct.new(:name, :songs, :artist)
6
+ Artist = Struct.new(:name)
7
+
8
+ module Date
9
+ def date
10
+ "May 16"
11
+ end
12
+ end
13
+
14
+ module Instrument
15
+ def instrument
16
+ "Violins"
17
+ end
18
+ end
19
+
20
+ class AlbumForm < Disposable::Twin
21
+ include Setup
22
+ feature Date
23
+ property :name
24
+
25
+ collection :songs do
26
+ include Setup
27
+ property :title
28
+
29
+ property :composer do
30
+ include Setup
31
+ feature Instrument
32
+ property :name
33
+ end
34
+ end
35
+
36
+ property :artist do
37
+ include Setup
38
+ property :name
39
+ end
40
+ end
41
+
42
+ let (:song) { Song.new("Broken") }
43
+ let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
44
+ let (:composer) { Artist.new("Greg Graffin") }
45
+ let (:artist) { Artist.new("Bad Religion") }
46
+ let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
47
+
48
+ let (:form) { AlbumForm.new(album) }
49
+
50
+ it do
51
+ form.date.must_equal "May 16"
52
+ form.artist.date.must_equal "May 16"
53
+ form.songs[0].date.must_equal "May 16"
54
+ form.songs[1].date.must_equal "May 16"
55
+ form.songs[1].composer.date.must_equal "May 16"
56
+ form.songs[1].wont_be_kind_of(Instrument)
57
+ form.songs[1].composer.must_be_kind_of(Instrument)
58
+ form.songs[1].composer.instrument.must_equal "Violins"
59
+ form.artist.date.must_equal "May 16"
60
+ end
61
+ end