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.
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