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.
@@ -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
@@ -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) { @fields[name.to_s] } # TODO: move to separate method.
52
- define_method("#{name}=") { |value| @fields[name.to_s] = 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, object_map)
38
+ def initialize(model, options={})
75
39
  @fields = {}
40
+ @model = model
76
41
 
77
- setup!(model, object_map)
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 # TODO: simplify so the whole thing with object map gets optional.
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
- # call save on all nested twins.
152
- def self.pre_save_representer
153
- representer = Class.new(write_representer)
154
- representer.representable_attrs.
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
- # it's important to stress that #save is the only entry point where we hit the database after initialize.
169
- def save(processed_map=ObjectMap.new) # use that in Reform::AR.
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
- private
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
- property(name, options.merge(:readable => false))
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