gorillib 0.4.1pre → 0.4.2pre
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.
- 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
|