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,40 @@
1
+ # run me with bundle exec ruby -Itest test/example.rb
2
+
3
+ require "test_helper"
4
+
5
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
6
+
7
+ module Twin
8
+ class Album < Disposable::Twin
9
+ property :id # DISCUSS: needed for #save.
10
+ property :name
11
+ collection :songs, :twin => lambda { |*| Song }
12
+
13
+ extend Representer
14
+ include Setup
15
+ include Sync
16
+ end
17
+
18
+ class Song < Disposable::Twin
19
+ property :id # DISCUSS: needed for #save.
20
+ property :title
21
+ # property :album, :twin => Album
22
+
23
+ extend Representer
24
+ include Setup
25
+ include Sync
26
+ end
27
+ end
28
+
29
+ album = Album.last
30
+
31
+ twin = Twin::Album.new(album)
32
+ puts "existing songs (#{twin.songs.size}): #{twin.songs.inspect}"
33
+
34
+ # this is what basically should happen in the representer, returning a Twin.
35
+ twin.songs << Song.new
36
+ twin.songs.last.title = "Back To Allentown"
37
+
38
+ puts "new songs (#{twin.songs.size}): #{twin.songs.inspect}"
39
+
40
+ twin.sync
@@ -0,0 +1,92 @@
1
+ require "test_helper"
2
+ require "disposable/expose"
3
+ require "disposable/composition"
4
+
5
+ # Disposable::Expose.
6
+ class ExposeTest < MiniTest::Spec
7
+ module Model
8
+ Album = Struct.new(:id, :name)
9
+ end
10
+
11
+ module Twin
12
+ class Album < Disposable::Twin
13
+ property :id
14
+ property :title, from: :name
15
+ end
16
+ end
17
+
18
+ class AlbumExpose < Disposable::Expose
19
+ from Twin::Album.representer_class
20
+ end
21
+
22
+ let (:album) { Model::Album.new(1, "Dick Sandwich") }
23
+ subject { AlbumExpose.new(album) }
24
+
25
+ describe "readers" do
26
+ it do
27
+ subject.id.must_equal 1
28
+ subject.title.must_equal "Dick Sandwich"
29
+ end
30
+ end
31
+
32
+
33
+ describe "writers" do
34
+ it do
35
+ subject.id = 3
36
+ subject.title = "Eclipse"
37
+
38
+ subject.id.must_equal 3
39
+ subject.title.must_equal "Eclipse"
40
+ album.id.must_equal 3
41
+ album.name.must_equal "Eclipse"
42
+ end
43
+ end
44
+ end
45
+
46
+
47
+ # Disposable::Composition.
48
+ class ExposeCompositionTest < MiniTest::Spec
49
+ module Model
50
+ Band = Struct.new(:id)
51
+ Album = Struct.new(:id, :name)
52
+ end
53
+
54
+ module Twin
55
+ class Album < Disposable::Twin
56
+ property :id, on: :album
57
+ property :name, on: :album
58
+ property :band_id, from: :id, on: :band
59
+ end
60
+
61
+ class AlbumComposition < Disposable::Composition
62
+ from Twin::Album.representer_class
63
+ end
64
+ end
65
+
66
+ let (:band) { Model::Band.new(1) }
67
+ let (:album) { Model::Album.new(2, "Dick Sandwich") }
68
+ subject { Twin::AlbumComposition.new(album: album, band: band) }
69
+
70
+
71
+ describe "readers" do
72
+ it { subject.id.must_equal 2 }
73
+ it { subject.band_id.must_equal 1 }
74
+ it { subject.name.must_equal "Dick Sandwich" }
75
+ end
76
+
77
+
78
+ describe "writers" do
79
+ it do
80
+ subject.id = 3
81
+ subject.band_id = 4
82
+ subject.name = "Eclipse"
83
+
84
+ subject.id.must_equal 3
85
+ subject.band_id.must_equal 4
86
+ subject.name.must_equal "Eclipse"
87
+ band.id.must_equal 4
88
+ album.id.must_equal 3
89
+ album.name.must_equal "Eclipse"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,101 @@
1
+ require "test_helper"
2
+
3
+ class PersistedTest < MiniTest::Spec
4
+ class AlbumTwin < Disposable::Twin
5
+ feature Sync, Save
6
+ feature Persisted, Changed
7
+
8
+ property :name
9
+
10
+ property :artist do
11
+ # on_added
12
+ # on_removed
13
+ property :name
14
+ end
15
+
16
+ collection :songs do
17
+ # after_add: could also be existing user
18
+ # after_remove
19
+ # after_create: this means added+changed?(:persisted): song created and added.
20
+ # after_update
21
+ property :title
22
+ end
23
+ end
24
+
25
+ it do
26
+ artist = Artist.new
27
+ ex_song = Song.create(title: "Run For Cover")
28
+ song = Song.new
29
+ album = Album.new(artist: artist, songs: [ex_song, song])
30
+
31
+
32
+ artist.persisted?.must_equal false
33
+ album.persisted?.must_equal false
34
+ ex_song.persisted?.must_equal true
35
+ song.persisted?.must_equal false
36
+
37
+ twin = AlbumTwin.new(album)
38
+ twin.persisted?.must_equal false
39
+ twin.changed?(:persisted?).must_equal false
40
+ twin.artist.persisted?.must_equal false
41
+ twin.artist.changed?(:persisted?).must_equal false
42
+ twin.songs[0].persisted?.must_equal true
43
+ twin.songs[0].changed?(:persisted?).must_equal false
44
+ twin.songs[1].persisted?.must_equal false
45
+ twin.songs[1].changed?(:persisted?).must_equal false
46
+
47
+ twin.save
48
+
49
+ artist.persisted?.must_equal true
50
+ album.persisted?.must_equal true
51
+ ex_song.persisted?.must_equal true
52
+ song.persisted?.must_equal true
53
+
54
+ twin.persisted?.must_equal true
55
+ twin.changed?(:persisted?).must_equal true
56
+ twin.artist.persisted?.must_equal true
57
+ twin.artist.changed?(:persisted?).must_equal true
58
+ twin.songs[0].persisted?.must_equal true
59
+ twin.songs[0].changed?(:persisted?).must_equal false
60
+ twin.songs[1].persisted?.must_equal true
61
+ twin.songs[1].changed?(:persisted?).must_equal true
62
+ end
63
+
64
+
65
+ describe "#created?" do
66
+ it do
67
+ twin = AlbumTwin.new(Album.new)
68
+
69
+ twin.created?.must_equal false
70
+ twin.save
71
+ twin.created?.must_equal true
72
+ end
73
+
74
+ it do
75
+ twin = AlbumTwin.new(Album.create)
76
+
77
+ twin.created?.must_equal false
78
+ twin.save
79
+ twin.created?.must_equal false
80
+ end
81
+ end
82
+
83
+
84
+ # describe "#updated?" do
85
+ # it do
86
+ # twin = AlbumTwin.new(Album.new)
87
+
88
+ # twin.updated?.must_equal false
89
+ # twin.save
90
+ # twin.updated?.must_equal false
91
+ # end
92
+
93
+ # it do
94
+ # twin = AlbumTwin.new(Album.create)
95
+
96
+ # twin.updated?.must_equal false
97
+ # twin.save
98
+ # twin.updated?.must_equal true
99
+ # end
100
+ # end
101
+ end
@@ -1,5 +1,7 @@
1
1
  require 'disposable'
2
2
  require 'minitest/autorun'
3
+ require "pp"
4
+ require "representable/debug"
3
5
 
4
6
  class Track
5
7
  def initialize(options={})
@@ -13,3 +15,65 @@ end
13
15
  # require 'active_record'
14
16
  # require 'database_cleaner'
15
17
  # DatabaseCleaner.strategy = :truncation
18
+
19
+ require 'active_record'
20
+ class Artist < ActiveRecord::Base
21
+ has_many :albums
22
+ end
23
+
24
+ class Song < ActiveRecord::Base
25
+ belongs_to :artist
26
+ end
27
+
28
+ class Album < ActiveRecord::Base
29
+ has_many :songs
30
+ belongs_to :artist
31
+ end
32
+
33
+ ActiveRecord::Base.establish_connection(
34
+ :adapter => "sqlite3",
35
+ :database => "#{Dir.pwd}/database.sqlite3"
36
+ )
37
+
38
+ # uncomment this once to create a database for testing:
39
+
40
+ # ActiveRecord::Schema.define do
41
+ # create_table :artists do |table|
42
+ # table.column :name, :string
43
+ # table.timestamps
44
+ # end
45
+ # create_table :songs do |table|
46
+ # table.column :title, :string
47
+ # table.column :artist_id, :integer
48
+ # table.column :album_id, :integer
49
+ # table.timestamps
50
+ # end
51
+ # create_table :albums do |table|
52
+ # table.column :name, :string
53
+ # table.column :artist_id, :integer
54
+ # table.timestamps
55
+ # end
56
+ # end
57
+
58
+ module Disposable
59
+ module Comparable
60
+ def attributes(source)
61
+ source.instance_variable_get(:@fields)
62
+ end
63
+
64
+ def ==(other)
65
+ self.class == other.class and attributes(self) == attributes(other)
66
+ end
67
+ end
68
+
69
+ module Saveable
70
+ def save
71
+ @saved = true
72
+ end
73
+
74
+ def saved?
75
+ @saved
76
+ end
77
+ end
78
+ end
79
+
@@ -0,0 +1,33 @@
1
+ require "disposable/twin"
2
+ require 'reform'
3
+ require 'ostruct'
4
+ require 'benchmark'
5
+
6
+ class BandForm < Reform::Form
7
+ property :name, validates: {presence: true}
8
+
9
+ collection :songs do
10
+ property :title, validates: {presence: true}
11
+ end
12
+ end
13
+
14
+ songs = 50.times.collect { OpenStruct.new(title: "Be Stag") }
15
+ band = OpenStruct.new(name: "Teenage Bottlerock", songs: songs)
16
+
17
+ songs_params = 50.times.collect { {title: "Commando"} }
18
+
19
+ time = Benchmark.measure do
20
+ 100.times.each do
21
+ form = BandForm.new(band)
22
+ form.validate("name" => "Ramones", "songs" => songs_params)
23
+ form.save
24
+ end
25
+ end
26
+
27
+ puts time
28
+
29
+ # with old Fields.new(to_hash)
30
+ # 4.200000
31
+ # 20%
32
+ # with setup and new(fields).from_object(twin) instead of Fields.new(to_hash)
33
+ # 3.680000 0.000000 3.680000 ( 3.685796)
@@ -0,0 +1,32 @@
1
+ require "test_helper"
2
+
3
+ class BuilderTest < MiniTest::Spec
4
+ module Model
5
+ Song = Struct.new(:id, :title)
6
+ Hit = Struct.new(:id, :title)
7
+ Evergreen = Struct.new(:id, :title)
8
+ end
9
+
10
+ class Twin < Disposable::Twin
11
+ property :id
12
+ property :title
13
+ # option :is_released
14
+
15
+ include Builder
16
+ builds ->(model, options) do
17
+ return Hit if model.is_a? Model::Hit
18
+ return Evergreen if options[:evergreen]
19
+ end
20
+ end
21
+
22
+ class Hit < Twin
23
+ end
24
+
25
+ class Evergreen < Twin
26
+ end
27
+
28
+
29
+ it { Twin.build(Model::Song.new).must_be_instance_of Twin }
30
+ it { Twin.build(Model::Hit.new).must_be_instance_of Hit }
31
+ it { Twin.build(Model::Evergreen.new, evergreen: true).must_be_instance_of Evergreen }
32
+ end
@@ -0,0 +1,108 @@
1
+ require 'test_helper'
2
+ # require 'reform/form/coercion'
3
+
4
+ class ChangedWithSetupTest < MiniTest::Spec
5
+ module Model
6
+ Song = Struct.new(:title, :length, :composer)
7
+ Album = Struct.new(:name, :songs, :artist)
8
+ Artist = Struct.new(:name)
9
+ end
10
+
11
+ module Twin
12
+ class Album < Disposable::Twin
13
+ feature Setup
14
+ feature Changed
15
+ # include Coercion
16
+ property :name
17
+
18
+ collection :songs do
19
+ property :title
20
+ property :length, type: Integer
21
+
22
+ property :composer do
23
+ property :name
24
+ end
25
+ end
26
+
27
+ property :artist do
28
+ include Setup
29
+ property :name
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+ let (:song) { Model::Song.new("Broken") }
36
+ let (:composer) { Model::Artist.new(2) }
37
+ let (:song_with_composer) { Model::Song.new("Broken", 246, composer) }
38
+ let (:artist) { Model::Artist.new("Randy") }
39
+ let (:album) { Model::Album.new("The Rest Is Silence", [song, song_with_composer], artist) }
40
+ let (:twin) { Twin::Album.new(album) }
41
+
42
+ # setup: changed? is always false
43
+ it do
44
+ twin.changed?(:name).must_equal false
45
+ twin.changed?.must_equal false
46
+
47
+ twin.songs[0].changed?.must_equal false
48
+ twin.songs[0].changed?(:title).must_equal false
49
+ twin.songs[1].changed?.must_equal false
50
+ twin.songs[1].changed?(:title).must_equal false
51
+
52
+ twin.songs[1].composer.changed?(:name).must_equal false
53
+ twin.songs[1].composer.changed?.must_equal false
54
+
55
+ twin.artist.changed?(:name).must_equal false
56
+ twin.artist.changed?.must_equal false
57
+ end
58
+
59
+ # only when a property is assigned, it's changed.
60
+ it do
61
+ twin.name= "Out Of Bounds"
62
+ twin.songs[0].title= "I Have Seen"
63
+ twin.songs[1].title= "In A Rhyme"
64
+ twin.songs[1].composer.name= "Ingemar Jansson & Mikael Danielsson"
65
+ twin.artist.name = "No Fun At All"
66
+
67
+ twin.changed?(:name).must_equal true
68
+ twin.changed?.must_equal true
69
+
70
+ twin.songs[0].changed?.must_equal true
71
+ twin.songs[0].changed?(:title).must_equal true
72
+ twin.songs[1].changed?.must_equal true
73
+ twin.songs[1].changed?(:title).must_equal true
74
+
75
+ twin.songs[1].composer.changed?(:name).must_equal true
76
+ twin.songs[1].composer.changed?.must_equal true
77
+
78
+ twin.artist.changed?(:name).must_equal true
79
+ twin.artist.changed?.must_equal true
80
+
81
+ # you can also ask for nested twins by name.
82
+ twin.changed?(:songs).must_equal true
83
+ twin.songs[0].changed?(:composer).must_equal false
84
+ twin.songs[1].changed?(:composer).must_equal true
85
+ twin.changed?(:artist).must_equal true
86
+ end
87
+
88
+ # nested changes should propagate up.
89
+ it do
90
+ twin.changed?.must_equal false
91
+
92
+ twin.songs[1].composer.name = "Nofx"
93
+
94
+ twin.changed?.must_equal true
95
+
96
+ assert twin.songs.changed?
97
+ twin.songs[1].changed?.must_equal true
98
+ twin.songs[0].changed?.must_equal false
99
+
100
+ twin.artist.changed?.must_equal false
101
+ end
102
+
103
+ # setting identical value doesn't change.
104
+ it do
105
+ twin.name = "The Rest Is Silence"
106
+ twin.changed?.must_equal false
107
+ end
108
+ end