gorillib 0.4.1pre → 0.4.2pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -10
- data/.rspec +1 -1
- data/.yardopts +1 -0
- data/CHANGELOG.md +47 -0
- data/Gemfile +22 -19
- data/Guardfile +23 -9
- data/README.md +12 -12
- data/Rakefile +29 -40
- data/VERSION +1 -1
- data/examples/benchmark/factories_benchmark.rb +87 -0
- data/examples/builder/ironfan.rb +1 -19
- data/examples/hash/slicing_methods.rb +101 -0
- data/gorillib.gemspec +36 -35
- data/lib/gorillib/array/deep_compact.rb +4 -3
- data/lib/gorillib/array/simple_statistics.rb +76 -0
- data/lib/gorillib/base.rb +0 -1
- data/lib/gorillib/builder.rb +15 -30
- data/lib/gorillib/collection.rb +159 -57
- data/lib/gorillib/collection/model_collection.rb +136 -43
- data/lib/gorillib/datetime/parse.rb +4 -2
- data/lib/gorillib/{array → deprecated/array}/average.rb +0 -0
- data/lib/gorillib/{array → deprecated/array}/random.rb +2 -1
- data/lib/gorillib/{array → deprecated/array}/sorted_median.rb +0 -0
- data/lib/gorillib/{array → deprecated/array}/sorted_percentile.rb +0 -0
- data/lib/gorillib/deprecated/array/sorted_sample.rb +13 -0
- data/lib/gorillib/{metaprogramming → deprecated/metaprogramming}/aliasing.rb +0 -0
- data/lib/gorillib/enumerable/sum.rb +3 -3
- data/lib/gorillib/exception/raisers.rb +92 -22
- data/lib/gorillib/factories.rb +550 -0
- data/lib/gorillib/hash/mash.rb +15 -58
- data/lib/gorillib/hashlike/deep_compact.rb +2 -2
- data/lib/gorillib/hashlike/slice.rb +55 -40
- data/lib/gorillib/model.rb +5 -3
- data/lib/gorillib/model/base.rb +33 -119
- data/lib/gorillib/model/defaults.rb +58 -14
- data/lib/gorillib/model/errors.rb +10 -0
- data/lib/gorillib/model/factories.rb +1 -367
- data/lib/gorillib/model/field.rb +40 -18
- data/lib/gorillib/model/fixup.rb +16 -0
- data/lib/gorillib/model/positional_fields.rb +35 -0
- data/lib/gorillib/model/schema_magic.rb +162 -0
- data/lib/gorillib/model/serialization.rb +1 -2
- data/lib/gorillib/model/serialization/csv.rb +59 -0
- data/lib/gorillib/pathname.rb +19 -8
- data/lib/gorillib/some.rb +2 -0
- data/lib/gorillib/string/constantize.rb +17 -10
- data/lib/gorillib/string/inflector.rb +11 -7
- data/lib/gorillib/type/boolean.rb +40 -0
- data/lib/gorillib/type/extended.rb +76 -40
- data/lib/gorillib/type/url.rb +6 -4
- data/lib/gorillib/utils/console.rb +1 -18
- data/lib/gorillib/utils/edge_cases.rb +18 -0
- data/spec/examples/builder/ironfan_spec.rb +5 -10
- data/spec/gorillib/array/compact_blank_spec.rb +36 -21
- data/spec/gorillib/array/simple_statistics_spec.rb +143 -0
- data/spec/gorillib/builder_spec.rb +16 -20
- data/spec/gorillib/collection_spec.rb +131 -35
- data/spec/gorillib/exception/raisers_spec.rb +39 -0
- data/spec/gorillib/hash/deep_compact_spec.rb +3 -3
- data/spec/gorillib/model/{record/defaults_spec.rb → defaults_spec.rb} +5 -1
- data/spec/gorillib/model/factories_spec.rb +335 -0
- data/spec/gorillib/model/{record/overlay_spec.rb → overlay_spec.rb} +0 -0
- data/spec/gorillib/model/serialization_spec.rb +2 -2
- data/spec/gorillib/model_spec.rb +19 -18
- data/spec/gorillib/pathname_spec.rb +7 -7
- data/spec/gorillib/string/truncate_spec.rb +3 -13
- data/spec/gorillib/type/extended_spec.rb +50 -2
- data/spec/gorillib/utils/capture_output_spec.rb +1 -1
- data/spec/spec_helper.rb +10 -7
- data/spec/support/factory_test_helpers.rb +76 -0
- data/spec/support/gorillib_test_helpers.rb +36 -24
- data/spec/support/model_test_helpers.rb +39 -2
- metadata +86 -51
- data/lib/alt/kernel/call_stack.rb +0 -56
- data/lib/gorillib/array/sorted_sample.rb +0 -12
- data/lib/gorillib/builder/field.rb +0 -5
- data/lib/gorillib/collection/has_collection.rb +0 -31
- data/lib/gorillib/collection/list_collection.rb +0 -58
- data/lib/gorillib/exception/confidence.rb +0 -17
- data/lib/gorillib/io/system_helpers.rb +0 -30
- data/lib/gorillib/model/record_schema.rb +0 -9
- data/lib/gorillib/utils/stub_module.rb +0 -33
- data/spec/array/average_spec.rb +0 -24
- data/spec/array/sorted_median_spec.rb +0 -18
- data/spec/array/sorted_percentile_spec.rb +0 -24
- data/spec/array/sorted_sample_spec.rb +0 -28
- data/spec/gorillib/metaprogramming/aliasing_spec.rb +0 -180
- data/spec/gorillib/model/record/factories_spec.rb +0 -335
- data/spec/support/kcode_test_helper.rb +0 -16
data/lib/gorillib/collection.rb
CHANGED
@@ -4,73 +4,116 @@ require 'gorillib/metaprogramming/class_attribute'
|
|
4
4
|
module Gorillib
|
5
5
|
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# The Collection class encapsulates the minimum functionality to let you:
|
8
|
+
#
|
9
|
+
# * store items uniquely, in order added
|
10
|
+
# * retrieve items by label
|
11
|
+
# * iterate over its values
|
12
|
+
#
|
13
|
+
# A collection is best used for representing 'plural' properties of models; it
|
14
|
+
# is *not* intended to be some radical reimagining of a generic array or
|
15
|
+
# hash. We've found its locked-down capabilities to particularly useful for
|
16
|
+
# constructing DSLs (Domain-Specific Languages). Collections are *not*
|
17
|
+
# intended to be performant: its abstraction layer comes at the price of
|
18
|
+
# additional method calls.
|
19
|
+
#
|
20
|
+
# ### Gated admission
|
21
|
+
#
|
22
|
+
# Collection provides a well-defended perimeter. Every item added to the
|
23
|
+
# collection (whether sent to the initializer, the passes through `add` method
|
24
|
+
#
|
25
|
+
# ### Familiarity with its contents
|
26
|
+
#
|
27
|
+
# Typically your model will have a familiar (but not intimate) relationship
|
28
|
+
# with its plural property:
|
29
|
+
#
|
30
|
+
# * items may have some intrinsic, uniquely-identifying feature: a `name`,
|
31
|
+
# `id`, or normalized representation. You'd like to be able to add an
|
32
|
+
# retrieve them by that intrinsic feature without having to manually juggle
|
33
|
+
# the correspondence of labels to intrinsic features.
|
34
|
+
#
|
35
|
+
# In the case of a ModelCollection,
|
36
|
+
#
|
37
|
+
# * all its items may share a common type: "a post has many `Comment`s".
|
38
|
+
#
|
39
|
+
# * a model may want items to hold a reference back to the containing model,
|
40
|
+
# or otherwise to share some common attributes. As an example, a `Graph` may
|
41
|
+
# have many `Stage` objects; the collection can inform newly-added stages
|
42
|
+
# which graph they belong to.
|
43
|
+
#
|
44
|
+
# ### Barebones enumerable methods
|
45
|
+
#
|
46
|
+
# The set of methods is purposefully sparse. If you want to use `select`,
|
47
|
+
# `invert`, etc, just invoke `to_hash` or `to_a` and work with the copy it
|
48
|
+
# gives you.
|
49
|
+
#
|
50
|
+
# Collection responds to:
|
8
51
|
# - receive!, values, to_a, each and each_value;
|
9
52
|
# - length, size, empty?, blank?
|
10
|
-
#
|
11
|
-
# A Collection additionally lets you store and retrieve things by label:
|
12
53
|
# - [], []=, include?, fetch, delete, each_pair, to_hash.
|
54
|
+
# - `key_method`: called on items to get their key; `to_key` by default.
|
55
|
+
# - `<<`: adds item under its `key_method` key
|
56
|
+
# - `receive!`s an array by auto-keying the elements, or a hash by trusting what you give it
|
13
57
|
#
|
14
58
|
# A ModelCollection adds:
|
15
|
-
# - `
|
16
|
-
# - `
|
17
|
-
# - `<<`: adds object under its `key_method` key
|
18
|
-
# - `receive!`s an array by auto-keying the elements, or a hash by trusting what you give it
|
19
|
-
# - `update_or_create: if absent, creates object with given attributes and
|
59
|
+
# - `factory`: generates new items, converts received items
|
60
|
+
# - `update_or_create: if absent, creates item with given attributes and
|
20
61
|
# `key_method => key`; if present, updates with given attributes.
|
21
62
|
#
|
22
|
-
|
23
|
-
|
63
|
+
#
|
64
|
+
class Collection
|
65
|
+
# [{Symbol => Object}] The actual store of items -- not for you to mess with
|
24
66
|
attr_reader :clxn
|
25
67
|
protected :clxn
|
26
68
|
|
27
|
-
|
28
|
-
|
29
|
-
coll.receive!(items)
|
30
|
-
coll
|
31
|
-
end
|
69
|
+
# Object that owns this collection, if any
|
70
|
+
attr_reader :belongs_to
|
32
71
|
|
33
|
-
|
72
|
+
# [String, Symbol] Method invoked on a new item to generate its collection key; :to_key by default
|
73
|
+
class_attribute :key_method, :instance_writer => false
|
74
|
+
singleton_class.send(:protected, :key_method=)
|
34
75
|
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
return false unless other.instance_of?(self.class)
|
41
|
-
clxn == other.send(:clxn)
|
76
|
+
# include Gorillib::Model
|
77
|
+
def initialize(options={})
|
78
|
+
@clxn = Hash.new
|
79
|
+
@key_method = options[:key_method] if options[:key_method]
|
80
|
+
@belongs_to = options[:belongs_to] if options[:belongs_to]
|
42
81
|
end
|
43
82
|
|
44
|
-
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# @return [
|
55
|
-
def
|
56
|
-
|
83
|
+
# Adds an item in-place. Items added to the collection (via `add`, `[]=`,
|
84
|
+
# `initialize`, etc) all pass through the `add` method: you should override
|
85
|
+
# this in subclasses to add any gatekeeper behavior.
|
86
|
+
#
|
87
|
+
# If no label is supplied, we use the result of invoking `key_method` on the
|
88
|
+
# item (or raise an error if no label *and* no key_method).
|
89
|
+
#
|
90
|
+
# It's up to you to ensure that labels make sense; this method doesn't
|
91
|
+
# demand the item's key_method match its label.
|
92
|
+
#
|
93
|
+
# @return [Object] the item
|
94
|
+
def add(item, label=nil)
|
95
|
+
label ||= label_for(item)
|
96
|
+
@clxn[label] = item
|
57
97
|
end
|
58
|
-
# same as #to_wire
|
59
|
-
def as_json(*args) to_wire(*args) ; end
|
60
|
-
# @return [String] JSON serialization of the collection's array representation
|
61
|
-
def to_json(*args) to_wire(*args).to_json(*args) ; end
|
62
98
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
99
|
+
def label_for(item)
|
100
|
+
if key_method.nil? then
|
101
|
+
raise ArgumentError, "Can't add things to a #{self.class} without some sort of label: use foo[label] = obj, or set the collection's key_method" ;
|
102
|
+
end
|
103
|
+
item.public_send(key_method)
|
68
104
|
end
|
69
105
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
106
|
+
#
|
107
|
+
# Barebones enumerable methods
|
108
|
+
#
|
109
|
+
# This set of methods is purposefully sparse. If you want to use `select`,
|
110
|
+
# `invert`, etc, just invoke `to_hash` or `to_a` and work with the copy it
|
111
|
+
# gives you.
|
112
|
+
#
|
113
|
+
|
114
|
+
delegate :[], :fetch, :delete, :include?, :to => :clxn
|
115
|
+
delegate :keys, :values, :each_pair, :each_value, :to => :clxn
|
116
|
+
delegate :length, :size, :empty?, :blank?, :to => :clxn
|
74
117
|
|
75
118
|
# @return [Array] an array holding the items
|
76
119
|
def to_a ; values ; end
|
@@ -80,21 +123,80 @@ module Gorillib
|
|
80
123
|
# iterate over each value in the collection
|
81
124
|
def each(&block); each_value(&block) ; end
|
82
125
|
|
83
|
-
#
|
126
|
+
# Adds item, returning the collection itself.
|
127
|
+
# @return [Gorillib::Collection] the collection
|
128
|
+
def <<(item)
|
129
|
+
add(item)
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
# add item with given label
|
134
|
+
def []=(label, item)
|
135
|
+
add(item, label)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Receive items in-place, replacing any existing item with that label.
|
139
|
+
#
|
140
|
+
# Individual items are added using #receive_item -- if you'd like to perform
|
141
|
+
# any conversion or modification to items, do it there
|
142
|
+
#
|
84
143
|
# @param other [{Symbol => Object}, Array<Object>] a hash of key=>item pairs or a list of items
|
85
144
|
# @return [Gorillib::Collection] the collection
|
86
145
|
def receive!(other)
|
87
|
-
|
146
|
+
if other.respond_to?(:each_pair)
|
147
|
+
other.each_pair{|label, item| receive_item(label, item) }
|
148
|
+
elsif other.respond_to?(:each)
|
149
|
+
other.each{|item| receive_item(nil, item) }
|
150
|
+
else
|
151
|
+
raise "A collection can only receive something that is enumerable: got #{other.inspect}"
|
152
|
+
end
|
88
153
|
self
|
89
154
|
end
|
90
155
|
|
91
|
-
|
156
|
+
# items arriving from the outside world should pass through receive_item,
|
157
|
+
# not directly to add.
|
158
|
+
def receive_item(label, item)
|
159
|
+
add(item, label)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Create a new collection and add the given items to it
|
163
|
+
# (if given an existing collection, just returns it directly)
|
164
|
+
def self.receive(items, *args)
|
165
|
+
return items if native?(items)
|
166
|
+
coll = new(*args)
|
167
|
+
coll.receive!(items)
|
168
|
+
coll
|
169
|
+
end
|
170
|
+
|
171
|
+
# A `native` object does not need any transformation; it is accepted directly.
|
172
|
+
# By default, an object is native if it `is_a?` this class
|
173
|
+
#
|
174
|
+
# @param obj [Object] the object that will be received
|
175
|
+
# @return [true, false] true if the item does not need conversion
|
176
|
+
def self.native?(obj)
|
177
|
+
obj.is_a?(self)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Two collections are equal if they have the same class and their contents are equal
|
181
|
+
#
|
182
|
+
# @param [Gorillib::Collection, Object] other The other collection to compare
|
183
|
+
# @return [true, false] True if attributes are equal and other is instance of the same Class
|
184
|
+
def ==(other)
|
185
|
+
return false unless other.instance_of?(self.class)
|
186
|
+
clxn == other.send(:clxn)
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [String] string describing the collection's array representation
|
190
|
+
def to_s ; to_a.to_s ; end
|
191
|
+
# @return [String] string describing the collection's array representation
|
192
|
+
def inspect
|
193
|
+
key_width = [keys.map{|key| key.to_s.length + 1 }.max.to_i, 45].min
|
194
|
+
guts = clxn.map{|key, val| "%-#{key_width}s %s" % ["#{key}:", val.inspect] }.join(",\n ")
|
195
|
+
['c{ ', guts, ' }'].join
|
196
|
+
end
|
92
197
|
|
93
|
-
|
94
|
-
|
95
|
-
def convert_collection(cc)
|
96
|
-
return cc.to_hash if cc.respond_to?(:to_hash)
|
97
|
-
raise "a #{self.class} can only receive a hash with explicitly-labelled contents."
|
198
|
+
def inspect_compact
|
199
|
+
['c{ ', keys.join(", "), ' }'].join
|
98
200
|
end
|
99
201
|
|
100
202
|
end
|
@@ -1,62 +1,155 @@
|
|
1
1
|
module Gorillib
|
2
|
-
class ModelCollection < Gorillib::Collection
|
3
|
-
# [String, Symbol] Method invoked on a new item to generate its collection key; :to_key by default
|
4
|
-
attr_accessor :key_method
|
5
|
-
# The default `key_method` invoked on a new item to generate its collection key
|
6
|
-
DEFAULT_KEY_METHOD = :to_key
|
7
2
|
|
3
|
+
#
|
4
|
+
# A collection of Models
|
5
|
+
#
|
6
|
+
# ### Item Type
|
7
|
+
#
|
8
|
+
# `item_type` is a class attribute -- you can make a "collection of Foo's" by
|
9
|
+
# subclassing ModelCollection and set the item item_type at the class level:
|
10
|
+
#
|
11
|
+
# class ClusterCollection < ModelCollection
|
12
|
+
# self.item_type = Cluster
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# A model collection serializes as an array does, but indexes labelled objects
|
18
|
+
# as a hash does.
|
19
|
+
#
|
20
|
+
#
|
21
|
+
class ModelCollection < Gorillib::Collection
|
8
22
|
# [Class, #receive] Factory for generating a new collection item.
|
9
|
-
class_attribute :
|
10
|
-
singleton_class.
|
11
|
-
|
12
|
-
def initialize(key_meth=nil, obj_factory=nil)
|
13
|
-
@factory = Gorillib::Factory(obj_factory) if obj_factory
|
14
|
-
@clxn = Hash.new
|
15
|
-
@key_method = key_meth || DEFAULT_KEY_METHOD
|
16
|
-
end
|
23
|
+
class_attribute :item_type, :instance_writer => false
|
24
|
+
singleton_class.send(:protected, :item_type=)
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
receive! [val]
|
22
|
-
self
|
26
|
+
def initialize(options={})
|
27
|
+
@item_type = Gorillib::Factory(options[:item_type]) if options[:item_type]
|
28
|
+
super
|
23
29
|
end
|
24
30
|
|
25
|
-
def
|
26
|
-
item
|
27
|
-
|
28
|
-
|
31
|
+
def receive_item(label, *args, &block)
|
32
|
+
item = item_type.receive(*args, &block)
|
33
|
+
super(label, item)
|
34
|
+
rescue StandardError => err ; err.polish("#{item_type} #{label} as #{args.inspect} to #{self}") rescue nil ; raise
|
29
35
|
end
|
30
36
|
|
31
|
-
def
|
32
|
-
if include?(
|
33
|
-
|
34
|
-
|
35
|
-
|
37
|
+
def update_or_add(label, attrs, &block)
|
38
|
+
if label && include?(label)
|
39
|
+
item = fetch(label)
|
40
|
+
item.receive!(attrs, &block)
|
41
|
+
item
|
36
42
|
else
|
37
|
-
attrs =
|
38
|
-
|
43
|
+
attrs = attrs.merge(key_method => label) if key_method && label
|
44
|
+
receive_item(label, attrs, &block)
|
39
45
|
end
|
46
|
+
rescue StandardError => err ; err.polish("#{item_type} #{label} as #{attrs} to #{self}") rescue nil ; raise
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Array] serializable array representation of the collection
|
50
|
+
def to_wire(options={})
|
51
|
+
to_a.map{|el| el.respond_to?(:to_wire) ? el.to_wire(options) : el }
|
40
52
|
end
|
53
|
+
# same as #to_wire
|
54
|
+
def as_json(*args) to_wire(*args) ; end
|
55
|
+
# @return [String] JSON serialization of the collection's array representation
|
56
|
+
def to_json(*args) to_wire(*args).to_json(*args) ; end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Collection
|
60
|
+
|
61
|
+
#
|
62
|
+
#
|
63
|
+
# class ClusterCollection < ModelCollection
|
64
|
+
# self.item_type = Cluster
|
65
|
+
# end
|
66
|
+
# class Organization
|
67
|
+
# field :clusters, ClusterCollection, default: ->{ ClusterCollection.new(common_attrs: { organization: self }) }
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
module CommonAttrs
|
71
|
+
extend Gorillib::Concern
|
72
|
+
|
73
|
+
included do
|
74
|
+
# [Class, #receive] Attributes to mix in to each added item
|
75
|
+
class_attribute :common_attrs, :instance_writer => false
|
76
|
+
singleton_class.send(:protected, :common_attrs=)
|
77
|
+
self.common_attrs = Hash.new
|
78
|
+
end
|
41
79
|
|
42
|
-
|
80
|
+
def initialize(options={})
|
81
|
+
super
|
82
|
+
@common_attrs = self.common_attrs.merge(options[:common_attrs]) if options.include?(:common_attrs)
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# * a factory-native object: item is updated with common_attrs, then added
|
87
|
+
# * raw materials for the object: item is constructed (from the merged attrs and common_attrs), then added
|
88
|
+
#
|
89
|
+
def receive_item(label, *args, &block)
|
90
|
+
attrs = args.extract_options!.merge(common_attrs)
|
91
|
+
super(label, *args, attrs, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def update_or_add(label, *args, &block)
|
95
|
+
attrs = args.extract_options!.merge(common_attrs)
|
96
|
+
super(label, *args, attrs, &block)
|
97
|
+
end
|
43
98
|
|
44
|
-
def convert_value(val)
|
45
|
-
return val unless factory
|
46
|
-
return nil if val.nil?
|
47
|
-
factory.receive(val)
|
48
99
|
end
|
49
100
|
|
50
|
-
#
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# class Smurf
|
104
|
+
# include Gorillib::Model
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# # Sets the 'village' attribute on each item it receives to the object
|
108
|
+
# # this collection belongs to.
|
109
|
+
# class SmurfCollection < ModelCollection
|
110
|
+
# include Gorillib::Collection::ItemsBelongTo
|
111
|
+
# self.item_type = Smurf
|
112
|
+
# self.parentage_method = :village
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# # SmurfVillage makes sure its SmurfCollection knows that it `belongs_to` the village
|
116
|
+
# class SmurfVillage
|
117
|
+
# include Gorillib::Model
|
118
|
+
# field :name, Symbol
|
119
|
+
# field :smurfs, SmurfCollection, default: ->{ SmurfCollection.new(belongs_to: self) }
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# # all the normal stuff works as you'd expect
|
123
|
+
# smurf_town = SmurfVillage.new('smurf_town') # #<SmurfVillage name=smurf_town>
|
124
|
+
# smurf_town.smurfs # c{ }
|
125
|
+
# smurf_town.smurfs.belongs_to # #<SmurfVillage name=smurf_town>
|
126
|
+
#
|
127
|
+
# # when a new smurf moves to town, it knows what village it belongs_to
|
128
|
+
# smurf_town.smurfs.receive_item(:novel_smurf, smurfiness: 10)
|
129
|
+
# # => #<Smurf name=:novel_smurf smurfiness=10 village=#<SmurfVillage name=smurf_town>>
|
130
|
+
#
|
131
|
+
module ItemsBelongTo
|
132
|
+
extend Gorillib::Concern
|
133
|
+
include Gorillib::Collection::CommonAttrs
|
134
|
+
|
135
|
+
included do
|
136
|
+
# [Class, #receive] Name of the attribute to set on
|
137
|
+
class_attribute :parentage_method, :instance_writer => false
|
138
|
+
singleton_class.send(:protected, :common_attrs=)
|
59
139
|
end
|
140
|
+
|
141
|
+
# add this collection's belongs_to to the common attrs, so that a
|
142
|
+
# newly-created object knows its parentage from birth.
|
143
|
+
def initialize(*args)
|
144
|
+
super
|
145
|
+
@common_attrs = self.common_attrs.merge(parentage_method => self.belongs_to)
|
146
|
+
end
|
147
|
+
|
148
|
+
def add(item, *args)
|
149
|
+
item.send("#{parentage_method}=", belongs_to)
|
150
|
+
super
|
151
|
+
end
|
152
|
+
|
60
153
|
end
|
61
154
|
|
62
155
|
end
|