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