disposable 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|