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
data/test/example.rb
ADDED
@@ -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
|
data/test/expose_test.rb
ADDED
@@ -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
|
data/test/test_helper.rb
CHANGED
@@ -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
|