disposable 0.0.6 → 0.0.7
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/CHANGES.md +4 -0
- data/README.md +40 -54
- data/database.sqlite3 +0 -0
- data/disposable.gemspec +5 -5
- data/gemfiles/Gemfile.rails-3.0.lock +8 -12
- data/gemfiles/Gemfile.rails-3.2.lock +5 -9
- data/gemfiles/Gemfile.rails-4.0 +1 -0
- data/gemfiles/Gemfile.rails-4.0.lock +16 -20
- data/gemfiles/Gemfile.rails-4.1.lock +19 -23
- data/lib/disposable/composition.rb +3 -0
- data/lib/disposable/twin.rb +24 -164
- data/lib/disposable/twin/composition.rb +27 -0
- data/lib/disposable/twin/new.rb +30 -0
- data/lib/disposable/twin/option.rb +2 -16
- data/lib/disposable/twin/representer.rb +29 -0
- data/lib/disposable/twin/save.rb +43 -0
- data/lib/disposable/twin/save_.rb +21 -0
- data/lib/disposable/twin/struct.rb +14 -0
- data/lib/disposable/version.rb +1 -1
- data/test/test_helper.rb +3 -3
- data/test/twin/composition_test.rb +35 -27
- data/test/twin/twin_test.rb +66 -85
- metadata +14 -52
- data/test/twin/active_record_test.rb +0 -227
@@ -12,9 +12,12 @@ module Disposable
|
|
12
12
|
# map( {cd: [[:id], [:name]], band: [[:id, :band_id], [:title]]} )
|
13
13
|
# end
|
14
14
|
#
|
15
|
+
# Composition adds #initialize to the includer.
|
16
|
+
#
|
15
17
|
# album = Album.new(cd: CD.find(1), band: Band.new)
|
16
18
|
# album.id #=> 1
|
17
19
|
# album.title = "Ten Foot Pole"
|
20
|
+
# album.band_id #=> nil
|
18
21
|
#
|
19
22
|
# It allows accessing the contained models using the `#[]` reader.
|
20
23
|
module Composition
|
data/lib/disposable/twin.rb
CHANGED
@@ -1,46 +1,20 @@
|
|
1
1
|
require 'uber/inheritable_attr'
|
2
2
|
require 'representable/decorator'
|
3
3
|
require 'representable/hash'
|
4
|
+
require 'disposable/twin/representer'
|
5
|
+
require 'disposable/twin/option'
|
6
|
+
|
7
|
+
# Twin.new(model/composition hash, options)
|
8
|
+
# assign hash to @fields
|
9
|
+
# write: write to @fields
|
10
|
+
# sync/save is the only way to write back to the model.
|
4
11
|
|
5
12
|
module Disposable
|
6
13
|
class Twin
|
7
|
-
class Definition < Representable::Definition
|
8
|
-
def dynamic_options
|
9
|
-
super + [:twin]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
class Decorator < Representable::Decorator
|
15
|
-
include Representable::Hash
|
16
|
-
include AllowSymbols
|
17
|
-
|
18
|
-
# DISCUSS: same in reform, is that a bug in represntable?
|
19
|
-
def self.clone # called in inheritable_attr :representer_class.
|
20
|
-
Class.new(self) # By subclassing, representable_attrs.clone is called.
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.build_config
|
24
|
-
Config.new(Definition)
|
25
|
-
end
|
26
|
-
|
27
|
-
def twin_names
|
28
|
-
representable_attrs.
|
29
|
-
find_all { |attr| attr[:twin] }.
|
30
|
-
collect { |attr| attr.name.to_sym }
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
|
35
14
|
extend Uber::InheritableAttr
|
36
15
|
inheritable_attr :representer_class
|
37
16
|
self.representer_class = Class.new(Decorator)
|
38
17
|
|
39
|
-
inheritable_attr :_model
|
40
|
-
|
41
|
-
def self.model(name)
|
42
|
-
self._model = name
|
43
|
-
end
|
44
18
|
|
45
19
|
def self.property(name, options={}, &block)
|
46
20
|
options[:private_name] = options.delete(:as) || name
|
@@ -48,8 +22,8 @@ module Disposable
|
|
48
22
|
|
49
23
|
representer_class.property(name, options, &block).tap do |definition|
|
50
24
|
mod = Module.new do
|
51
|
-
define_method(name)
|
52
|
-
define_method("#{name}=") { |value|
|
25
|
+
define_method(name) { read_property(name, options[:private_name]) }
|
26
|
+
define_method("#{name}=") { |value| write_property(name, options[:private_name], value) } # TODO: this is more like prototyping.
|
53
27
|
end
|
54
28
|
include mod
|
55
29
|
end
|
@@ -59,157 +33,43 @@ module Disposable
|
|
59
33
|
property(name, options.merge(:collection => true), &block)
|
60
34
|
end
|
61
35
|
|
62
|
-
# this method should only be called in finders, and considered semi-private. it should only be called once as the top stack entry.
|
63
|
-
def self.from(model, *args) # TODO: private.
|
64
|
-
new(model, *args)
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.new(model={}, object_map=ObjectMap.new)
|
68
|
-
super(model, object_map)
|
69
|
-
end
|
70
|
-
|
71
36
|
|
72
|
-
# TODO: improve speed when setting up a twin.
|
73
37
|
module Initialize
|
74
|
-
def initialize(model,
|
38
|
+
def initialize(model, options={})
|
75
39
|
@fields = {}
|
40
|
+
@model = model
|
76
41
|
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
def setup!(model, object_map)
|
81
|
-
options = {}
|
82
|
-
options, model = model, self.class._model.new if model.is_a?(Hash)
|
83
|
-
|
84
|
-
|
85
|
-
# model, options = nil, model if model.is_a?(Hash) # sorry but i wanna have the same API as ActiveRecord here.
|
86
|
-
@model = model #|| self.class._model.new
|
87
|
-
|
88
|
-
object_map[@model] = self # DISCUSS: how to we handle compositions here?
|
89
|
-
|
90
|
-
from_hash(
|
91
|
-
self.class.new_representer.new(@model).to_hash(:object_map => object_map). # always read from model, even when it's new.
|
92
|
-
merge(options)
|
93
|
-
)
|
42
|
+
from_hash(options) # assigns known properties from options.
|
94
43
|
end
|
95
44
|
end
|
96
|
-
include Initialize
|
45
|
+
include Initialize
|
97
46
|
|
98
47
|
|
99
|
-
require 'disposable/twin/finders'
|
100
|
-
extend Finders
|
101
|
-
|
102
|
-
# hash for #update_attributes (model API): {title: "Future World", album: <Album>}
|
103
|
-
def self.save_representer
|
104
|
-
# TODO: do that only at compile-time!
|
105
|
-
save = Class.new(write_representer) # inherit configuration
|
106
|
-
save.representable_attrs.
|
107
|
-
find_all { |attr| attr[:twin] }.
|
108
|
-
each { |attr| attr.merge!(
|
109
|
-
:representable => true,
|
110
|
-
:serialize => lambda { |obj, args| obj.send(:model) }) }
|
111
|
-
|
112
|
-
save.representable_attrs.each do |attr|
|
113
|
-
attr.merge!(:as => attr[:private_name])
|
114
|
-
end
|
115
|
-
|
116
|
-
save
|
117
|
-
end
|
118
|
-
|
119
|
-
# transform incoming model into twin API hash.
|
120
|
-
def self.new_representer
|
121
|
-
representer = Class.new(representer_class) # inherit configuration
|
122
|
-
|
123
|
-
# wrap incoming nested model in its Twin.
|
124
|
-
representer.representable_attrs.
|
125
|
-
find_all { |attr| attr[:twin] }.
|
126
|
-
each { |attr| attr.merge!(
|
127
|
-
:prepare => lambda { |object, args|
|
128
|
-
if twin = args.user_options[:object_map][object]
|
129
|
-
twin
|
130
|
-
else
|
131
|
-
args.binding[:twin].evaluate(nil).new(object, args.user_options[:object_map])
|
132
|
-
end
|
133
|
-
}) }
|
134
|
-
|
135
|
-
# song_title => model.title
|
136
|
-
representer.representable_attrs.each do |attr|
|
137
|
-
attr.merge!(
|
138
|
-
:getter => lambda { |args|
|
139
|
-
args.represented.send("#{args.binding[:private_name]}") }, # DISCUSS: can't we do that with representable's mechanics?
|
140
|
-
)
|
141
|
-
end
|
142
|
-
|
143
|
-
representer
|
144
|
-
end
|
145
|
-
|
146
48
|
# read/write to twin using twin's API (e.g. #record= not #album=).
|
147
49
|
def self.write_representer
|
148
50
|
representer = Class.new(representer_class) # inherit configuration
|
149
51
|
end
|
150
52
|
|
151
|
-
|
152
|
-
def
|
153
|
-
|
154
|
-
|
155
|
-
each { |attr| attr.merge!(
|
156
|
-
:representable => true,
|
157
|
-
:serialize => lambda do |twin, args|
|
158
|
-
processed = args.user_options[:processed_map]
|
159
|
-
|
160
|
-
twin.save(processed) unless processed[twin] # don't call save if it is already scheduled.
|
161
|
-
end
|
162
|
-
)}
|
163
|
-
|
164
|
-
representer
|
53
|
+
private
|
54
|
+
def read_property(name, private_name)
|
55
|
+
return @fields[name.to_s] if @fields.has_key?(name.to_s)
|
56
|
+
@fields[name.to_s] = read_from_model(private_name)
|
165
57
|
end
|
166
58
|
|
59
|
+
def read_from_model(getter)
|
60
|
+
model.send(getter)
|
61
|
+
end
|
167
62
|
|
168
|
-
|
169
|
-
|
170
|
-
processed_map[self] = true
|
171
|
-
|
172
|
-
pre_save = self.class.pre_save_representer.new(self)
|
173
|
-
pre_save.to_hash(:include => pre_save.twin_names, :processed_map => processed_map) # #save on nested Twins.
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
# what we do right now
|
178
|
-
# call save on all nested twins - how does that work with dependencies (eg Album needs Song id)?
|
179
|
-
# extract all ORM attributes
|
180
|
-
# write to model
|
181
|
-
|
182
|
-
sync_attrs = self.class.save_representer.new(self).to_hash
|
183
|
-
# puts "sync> #{sync_attrs.inspect}"
|
184
|
-
# this is ORM-specific:
|
185
|
-
model.update_attributes(sync_attrs) # this also does `album: #<Album>`
|
186
|
-
|
187
|
-
# FIXME: sync again, here, or just id?
|
188
|
-
self.id = model.id
|
63
|
+
def write_property(name, private_name, value)
|
64
|
+
@fields[name.to_s] = value
|
189
65
|
end
|
190
66
|
|
191
|
-
|
192
|
-
def from_hash(options={})
|
67
|
+
def from_hash(options)
|
193
68
|
self.class.write_representer.new(self).from_hash(options)
|
194
69
|
end
|
195
70
|
|
196
71
|
attr_reader :model # TODO: test
|
197
72
|
|
198
|
-
|
199
|
-
class ObjectMap < Hash
|
200
|
-
end
|
201
|
-
|
202
|
-
# class Composition < self
|
203
|
-
# def initialize(hash)
|
204
|
-
# hash = hash.first
|
205
|
-
# composition = Class.new do
|
206
|
-
# include Disposable::Composition
|
207
|
-
# map( {:song => [:song_title], :requester => [:name]})
|
208
|
-
# self
|
209
|
-
# end.new(hash)
|
210
|
-
|
211
|
-
# super(composition)
|
212
|
-
# end
|
213
|
-
# end
|
73
|
+
include Option
|
214
74
|
end
|
215
75
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Disposable
|
2
|
+
class Twin
|
3
|
+
class Composition
|
4
|
+
include Disposable::Composition
|
5
|
+
|
6
|
+
extend Uber::InheritableAttr
|
7
|
+
inheritable_attr :twin_classes
|
8
|
+
self.twin_classes = {}
|
9
|
+
|
10
|
+
# this creates one Twin per composed.
|
11
|
+
def self.property(name, options, &block)
|
12
|
+
twin_classes[options[:on]] ||= Class.new(Twin)
|
13
|
+
twin_classes[options[:on]].property(name, options, &block)
|
14
|
+
|
15
|
+
map options[:on] => [[name]] # why is Composition::map so awkward?
|
16
|
+
end
|
17
|
+
# TODO: test and implement ::collection
|
18
|
+
|
19
|
+
def initialize(composed)
|
20
|
+
twins = {}
|
21
|
+
composed.each { |name, model| twins[name] = self.class.twin_classes[name].new(model) }
|
22
|
+
|
23
|
+
super(twins)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Disposable
|
2
|
+
class Twin
|
3
|
+
# transform incoming model into twin API hash.
|
4
|
+
def self.new_representer
|
5
|
+
representer = Class.new(representer_class) # inherit configuration
|
6
|
+
|
7
|
+
# wrap incoming nested model in its Twin.
|
8
|
+
representer.representable_attrs.
|
9
|
+
find_all { |attr| attr[:twin] }.
|
10
|
+
each { |attr| attr.merge!(
|
11
|
+
:prepare => lambda { |object, args|
|
12
|
+
if twin = args.user_options[:object_map][object]
|
13
|
+
twin
|
14
|
+
else
|
15
|
+
args.binding[:twin].evaluate(nil).new(object, args.user_options[:object_map])
|
16
|
+
end
|
17
|
+
}) }
|
18
|
+
|
19
|
+
# song_title => model.title
|
20
|
+
representer.representable_attrs.each do |attr|
|
21
|
+
attr.merge!(
|
22
|
+
:getter => lambda { |args|
|
23
|
+
args.represented.send("#{args.binding[:private_name]}") }, # DISCUSS: can't we do that with representable's mechanics?
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
representer
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -3,24 +3,10 @@ module Disposable::Twin::Option
|
|
3
3
|
base.extend ClassMethods
|
4
4
|
end
|
5
5
|
|
6
|
-
|
7
|
-
def setup!(model, options)
|
8
|
-
# FIXME: merge that with original Twin.
|
9
|
-
|
10
|
-
@model = model #|| self.class._model.new
|
11
|
-
|
12
|
-
from_hash(
|
13
|
-
self.class.new_representer.new(@model).to_hash
|
14
|
-
)
|
15
|
-
|
16
|
-
# TODO: just make new_representer with :getter for option!
|
17
|
-
# IDEA: how can we bring that in line with Composition?
|
18
|
-
from_hash(options)
|
19
|
-
end
|
20
|
-
|
21
6
|
module ClassMethods
|
22
7
|
def option(name, options={})
|
23
|
-
|
8
|
+
# default: nil will always set an option in the, even when not in the incoming options.
|
9
|
+
property(name, options.merge(:readable => false, :default => nil))
|
24
10
|
end
|
25
11
|
end
|
26
12
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Disposable
|
2
|
+
class Twin
|
3
|
+
class Decorator < Representable::Decorator
|
4
|
+
include Representable::Hash
|
5
|
+
include AllowSymbols
|
6
|
+
|
7
|
+
# DISCUSS: same in reform, is that a bug in represntable?
|
8
|
+
def self.clone # called in inheritable_attr :representer_class.
|
9
|
+
Class.new(self) # By subclassing, representable_attrs.clone is called.
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.build_config
|
13
|
+
Config.new(Definition)
|
14
|
+
end
|
15
|
+
|
16
|
+
def twin_names
|
17
|
+
representable_attrs.
|
18
|
+
find_all { |attr| attr[:twin] }.
|
19
|
+
collect { |attr| attr.name.to_sym }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Definition < Representable::Definition
|
24
|
+
def dynamic_options
|
25
|
+
super + [:twin]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Disposable
|
2
|
+
class Twin
|
3
|
+
# call save on all nested twins.
|
4
|
+
def self.pre_save_representer
|
5
|
+
representer = Class.new(write_representer)
|
6
|
+
representer.representable_attrs.
|
7
|
+
each { |attr| attr.merge!(
|
8
|
+
:representable => true,
|
9
|
+
:serialize => lambda do |twin, args|
|
10
|
+
processed = args.user_options[:processed_map]
|
11
|
+
|
12
|
+
twin.save(processed) unless processed[twin] # don't call save if it is already scheduled.
|
13
|
+
end
|
14
|
+
)}
|
15
|
+
|
16
|
+
representer
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# it's important to stress that #save is the only entry point where we hit the database after initialize.
|
21
|
+
def save(processed_map=ObjectMap.new) # use that in Reform::AR.
|
22
|
+
processed_map[self] = true
|
23
|
+
|
24
|
+
pre_save = self.class.pre_save_representer.new(self)
|
25
|
+
pre_save.to_hash(:include => pre_save.twin_names, :processed_map => processed_map) # #save on nested Twins.
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
# what we do right now
|
30
|
+
# call save on all nested twins - how does that work with dependencies (eg Album needs Song id)?
|
31
|
+
# extract all ORM attributes
|
32
|
+
# write to model
|
33
|
+
|
34
|
+
sync_attrs = self.class.save_representer.new(self).to_hash
|
35
|
+
# puts "sync> #{sync_attrs.inspect}"
|
36
|
+
# this is ORM-specific:
|
37
|
+
model.update_attributes(sync_attrs) # this also does `album: #<Album>`
|
38
|
+
|
39
|
+
# FIXME: sync again, here, or just id?
|
40
|
+
self.id = model.id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Disposable
|
2
|
+
class Twin
|
3
|
+
# hash for #update_attributes (model API): {title: "Future World", album: <Album>}
|
4
|
+
def self.save_representer
|
5
|
+
# TODO: do that only at compile-time!
|
6
|
+
save = Class.new(write_representer) # inherit configuration
|
7
|
+
save.representable_attrs.
|
8
|
+
find_all { |attr| attr[:twin] }.
|
9
|
+
each { |attr| attr.merge!(
|
10
|
+
:representable => true,
|
11
|
+
:serialize => lambda { |obj, args| obj.send(:model) }) }
|
12
|
+
|
13
|
+
save.representable_attrs.each do |attr|
|
14
|
+
attr.merge!(:as => attr[:private_name])
|
15
|
+
end
|
16
|
+
|
17
|
+
save
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Disposable
|
2
|
+
class Twin
|
3
|
+
# Twin that uses a hash to populate.
|
4
|
+
#
|
5
|
+
# Twin.new(id: 1)
|
6
|
+
module Struct
|
7
|
+
def initialize(model, options={})
|
8
|
+
super # call from_hash(options) # FIXME: this is wrong and already calls from_hash(options)
|
9
|
+
|
10
|
+
from_hash(model.merge(options))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|