parallel_ancestry 1.0.0

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.
@@ -0,0 +1,355 @@
1
+
2
+ module ::ParallelAncestry
3
+
4
+ InstanceAncestryStruct = ::Struct.new( :children, :parents )
5
+
6
+ extend ::Module::Cluster
7
+
8
+ # Initialize any module extended with self for inheritance hooks
9
+ cluster( :parallel_ancestry ).after_extend( :module ) do |ancestors_module|
10
+ ancestors_module.module_eval do
11
+ @ancestors_hash = { }
12
+ end
13
+ end
14
+
15
+ # Initialize any subclass of module including self for inheritance hooks
16
+ cluster( :parallel_ancestry ).after_include( :class ) do |class_instance|
17
+ if class_instance < ::Module
18
+ class_instance.module_eval do
19
+ include( ::ParallelAncestry::ModuleSubclassInheritance )
20
+ end
21
+ end
22
+ end
23
+
24
+ extend self
25
+
26
+ ##############
27
+ # children #
28
+ ##############
29
+
30
+ # Return a list of children for provided object.
31
+ # @param [Object] instance Object instance.
32
+ # @return [Array<Object>] An array containing references to children.
33
+ def children( instance )
34
+
35
+ return ancestor_struct( instance ).children ||= ::Array::Unique.new( self )
36
+
37
+ end
38
+
39
+ #############
40
+ # parents #
41
+ #############
42
+
43
+ # Return a list of parents for provided object.
44
+ # @param [Object] instance Object instance.
45
+ # @return [Array<Object>] An array containing references to immediate parents for any configuration.
46
+ def parents( instance )
47
+
48
+ return ancestor_struct( instance ).parents ||= ::Array::Unique.new( self )
49
+
50
+ end
51
+
52
+ ##################
53
+ # has_parents? #
54
+ ##################
55
+
56
+ # Return whether provided object has parents.
57
+ # @param [Object] instance Object instance.
58
+ # @return [true, false] true or false.
59
+ def has_parents?( instance )
60
+
61
+ return ! parents( instance ).empty?
62
+
63
+ end
64
+
65
+ ###################
66
+ # has_children? #
67
+ ###################
68
+
69
+ # Return whether provided object has children.
70
+ # @param [Object] instance Object instance.
71
+ # @return [true, false] true or false.
72
+ def has_children?( instance )
73
+
74
+ return ! children( instance ).empty?
75
+
76
+ end
77
+
78
+ ###############################
79
+ # register_child_for_parent #
80
+ ###############################
81
+
82
+ # Register instance as child of another instance.
83
+ # @param [Object] child Child instance.
84
+ # @param [Object] parent Parent instance.
85
+ # @return [Array<Object>] An array containing references to children.
86
+ def register_child_for_parent( child, parent )
87
+
88
+ parents_of_child = parents( child )
89
+ children_of_parent = children( parent )
90
+
91
+ # child order shouldn't be relevant
92
+ children_of_parent.push( child )
93
+
94
+ # parent order determines who wins conflicts, so we keep youngest first
95
+ parents_of_child.unshift( parent )
96
+
97
+ return self
98
+
99
+ end
100
+
101
+ ##############
102
+ # ancestor #
103
+ ##############
104
+
105
+ # Return parent for instance that matches match_ancestor_block.
106
+ # @param [Object] instance Child instance.
107
+ # @yield Block used to match parent. The parameter is the parent instance, the return value true
108
+ # or false, reflecting whether or not block matched ancestor.
109
+ # @example
110
+ # ::ParallelAncestry.ancestor( some_instance ) do |this_parent|
111
+ # if this_parent.matches_arbitrary_condition
112
+ # true
113
+ # else
114
+ # false
115
+ # end
116
+ # end
117
+ # @return [Object] A reference to parent matching block condition.
118
+ def ancestor( instance, & match_ancestor_block )
119
+
120
+ ancestor_instance = nil
121
+
122
+ parents = parents( instance )
123
+
124
+ # If we don't have ancestors explicitly declared for this instance, and if it is not
125
+ # a ::Class or ::Module (both are ::Modules) then we have an instance of a class,
126
+ # so we can use the instance's class
127
+ if parents.empty? and instance != ::Class
128
+
129
+ instance_class = instance.class
130
+ if match_ancestor_block.call( instance_class )
131
+ ancestor_instance = instance.class
132
+ end
133
+
134
+ else
135
+
136
+ parents.each do |this_parent|
137
+ # we need a way to go from multiple parents to the one that makes up this chain
138
+ # we use the match_ancestor_block to determine this - it should return true/false
139
+ if match_ancestor_block.call( this_parent )
140
+ ancestor_instance = this_parent
141
+ break
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ return ancestor_instance
148
+
149
+ end
150
+
151
+ alias_method :parent, :ancestor
152
+
153
+ ####################
154
+ # ancestor_chain #
155
+ ####################
156
+
157
+ # Returns ancestor chain defined for provided object.
158
+ # @param [Object] instance Instance for which ancestors are being looked up.
159
+ # @yield Block used to match parent. The parameter is the parent instance, the return value true
160
+ # or false, reflecting whether or not block matched ancestor.
161
+ # @example
162
+ # ::ParallelAncestry.ancestor( some_instance ) do |this_parent|
163
+ # if this_parent.matches_arbitrary_condition
164
+ # true
165
+ # else
166
+ # false
167
+ # end
168
+ # end
169
+ # @return [Array<Object>] An array containing references to parents matching block condition.
170
+ def ancestor_chain( instance, & match_ancestor_block )
171
+
172
+ ancestor_chain = [ this_ancestor = instance ]
173
+
174
+ while this_ancestor = ancestor( this_ancestor, & match_ancestor_block )
175
+ ancestor_chain.push( this_ancestor )
176
+ end
177
+
178
+ return ancestor_chain
179
+
180
+ end
181
+
182
+ ####################
183
+ # lowest_parents #
184
+ ####################
185
+
186
+ # Returns the lowest parent in each parent tree matching block condition. For simple linear
187
+ # trees, this is simply the first parent, but more complex trees quickly diverge into multiple
188
+ # branches, each of which then requires a lowest match.
189
+ # @param [Object] instance Instance for which parents are being looked up.
190
+ # @yield Block used to match parent. The parameter is the parent instance, the return value true
191
+ # or false, reflecting whether or not block matched ancestor.
192
+ # @example
193
+ # ::ParallelAncestry.lowest_parents( some_instance ) do |this_parent|
194
+ # if this_parent.matches_arbitrary_condition
195
+ # true
196
+ # else
197
+ # false
198
+ # end
199
+ # end
200
+ # @return [Array<Object>] An array containing references to lowest parent in each parent tree
201
+ # matching block condition.
202
+ def lowest_parents( instance, & match_ancestor_block )
203
+
204
+ # the first super module available for each tree
205
+
206
+ lowest_parents_array = [ ]
207
+
208
+ parents( instance ).each do |this_parent|
209
+
210
+ # if we match this parent we are done with this branch and can go to the next
211
+ if match_ancestor_block.call( this_parent )
212
+
213
+ lowest_parents_array.push( this_parent )
214
+
215
+ # otherwise our branch expands and we have to finish it before the next parent
216
+ elsif has_parents?( this_parent )
217
+
218
+ parents_for_branch = lowest_parents( this_parent, & match_ancestor_block )
219
+
220
+ lowest_parents_array.concat( parents_for_branch )
221
+
222
+ end
223
+
224
+ end
225
+
226
+ return lowest_parents_array
227
+
228
+ end
229
+
230
+ ######################
231
+ # highest_children #
232
+ ######################
233
+
234
+ # Returns the highest parent in each parent tree matching block condition. For simple linear
235
+ # trees, this is simply the first parent, but more complex trees quickly diverge into multiple
236
+ # branches, each of which then requires a highest match.
237
+ # @param [Object] instance Instance for which parents are being looked up.
238
+ # @yield Block used to match parent. The parameter is the parent instance, the return value true
239
+ # or false, reflecting whether or not block matched ancestor.
240
+ # @example
241
+ # ::ParallelAncestry.highest_parents( some_instance ) do |this_parent|
242
+ # if this_parent.matches_arbitrary_condition
243
+ # true
244
+ # else
245
+ # false
246
+ # end
247
+ # end
248
+ # @return [Array<Object>] An array containing references to highest parent in each parent tree
249
+ # matching block condition.
250
+ def highest_children( instance, & match_ancestor_block )
251
+
252
+ # the first super module available for each tree
253
+
254
+ highest_children_array = [ ]
255
+
256
+ children( instance ).each do |this_child|
257
+
258
+ # if we match this parent we are done with this branch and can go to the next
259
+ if match_ancestor_block.call( this_child )
260
+
261
+ highest_children_array.push( this_child )
262
+
263
+ # otherwise our branch expands and we have to finish it before the next parent
264
+ elsif has_children?( this_child )
265
+
266
+ children_for_branch = highest_children( this_child, & match_ancestor_block )
267
+
268
+ highest_children_array.concat( children_for_branch )
269
+
270
+ end
271
+
272
+ end
273
+
274
+ return highest_children_array
275
+
276
+ end
277
+
278
+ ####################
279
+ # match_ancestor #
280
+ ####################
281
+
282
+ # Returns the first ancestor (determined by ancestor_match_block) for which match_block is true.
283
+ # @param [Object] instance Instance for which parents are being looked up.
284
+ # @param [Proc] ancestor_match_block Proc used to match parent. The parameter is the parent
285
+ # instance, the return value true or false, reflecting whether or not block matched
286
+ # ancestor.
287
+ # @yield Block used to match parent. The parameter is the parent instance, the return value true
288
+ # or false, reflecting whether or not block matched.
289
+ # @example
290
+ # ancestor_match_block = ::Proc.new do |this_parent|
291
+ # if this_parent.matches_arbitrary_condition
292
+ # true
293
+ # else
294
+ # false
295
+ # end
296
+ # end
297
+ # ::ParallelAncestry.match_ancestor( some_instance, ancestor_match_block ) do |this_parent|
298
+ # if this_parent.matches_arbitrary_condition
299
+ # true
300
+ # else
301
+ # false
302
+ # end
303
+ # end
304
+ # @return [Object]
305
+ def match_ancestor( instance, ancestor_match_block, & match_block )
306
+
307
+ matched_ancestor = nil
308
+
309
+ this_ancestor = instance
310
+
311
+ if has_parents?( this_ancestor )
312
+
313
+ begin
314
+ if match_block.call( this_ancestor )
315
+ matched_ancestor = this_ancestor
316
+ break
317
+ end
318
+ break if this_ancestor.equal?( ::Object )
319
+ end while this_ancestor = ancestor( this_ancestor, & ancestor_match_block )
320
+
321
+ elsif match_block.call( this_ancestor )
322
+
323
+ matched_ancestor = this_ancestor
324
+
325
+ else
326
+
327
+ matched_ancestor = match_ancestor( this_ancestor.class, ancestor_match_block, & match_block )
328
+
329
+ end
330
+
331
+ return matched_ancestor
332
+
333
+ end
334
+
335
+ ##################################################################################################
336
+ private ######################################################################################
337
+ ##################################################################################################
338
+
339
+ #####################
340
+ # ancestor_struct #
341
+ #####################
342
+
343
+ def ancestor_struct( instance )
344
+
345
+ unless ancestor_struct = @ancestors_hash[ instance.__id__ ]
346
+ # fill in slots lazily
347
+ ancestor_struct = ::ParallelAncestry::InstanceAncestryStruct.new
348
+ @ancestors_hash[ instance.__id__ ] = ancestor_struct
349
+ end
350
+
351
+ return ancestor_struct
352
+
353
+ end
354
+
355
+ end
@@ -0,0 +1,24 @@
1
+
2
+ require 'module/cluster'
3
+ require 'array/unique'
4
+
5
+ module ::ParallelAncestry
6
+
7
+ end
8
+
9
+ basepath = 'parallel_ancestry/parallel_ancestry'
10
+
11
+ files = [
12
+
13
+ 'inheritance',
14
+ 'inheritance/module_subclass_inheritance',
15
+
16
+ 'module_subclass_inheritance'
17
+
18
+ ]
19
+
20
+ files.each do |this_file|
21
+ require_relative( File.join( basepath, this_file ) + '.rb' )
22
+ end
23
+
24
+ require_relative( basepath + '.rb' )
@@ -0,0 +1,262 @@
1
+
2
+ require_relative '../../lib/parallel_ancestry.rb'
3
+
4
+ describe ::ParallelAncestry::Inheritance do
5
+
6
+ it 'can enable hooks in module extended with self' do
7
+ module ::ParallelAncestry::Inheritance::ModuleMock
8
+
9
+ # mock to ensure call
10
+ def self.initialize_inheritance_for_module!
11
+ super
12
+ @ran_initialize_inheritance_for_module = true
13
+ end
14
+ def self.ran_initialize_inheritance_for_module?
15
+ returning_ran_initialize_inheritance_for_module = ( @ran_initialize_inheritance_for_module ? true : false )
16
+ @ran_initialize_inheritance_for_module = false
17
+ return returning_ran_initialize_inheritance_for_module
18
+ end
19
+ # mock to ensure call
20
+ def self.initialize_base_instance_for_include( inheriting_instance )
21
+ super
22
+ @ran_initialize_base_instance_for_include = true
23
+ end
24
+ def self.ran_initialize_base_instance_for_include?
25
+ returning_ran_initialize_base_instance_for_include = ( @ran_initialize_base_instance_for_include ? true : false )
26
+ @ran_initialize_base_instance_for_include = false
27
+ return returning_ran_initialize_base_instance_for_include
28
+ end
29
+ # mock to ensure call
30
+ def self.initialize_base_instance_for_extend( inheriting_instance )
31
+ super
32
+ @ran_initialize_base_instance_for_extend = true
33
+ end
34
+ def self.ran_initialize_base_instance_for_extend?
35
+ returning_ran_initialize_base_instance_for_extend = ( @ran_initialize_base_instance_for_extend ? true : false )
36
+ @ran_initialize_base_instance_for_extend = false
37
+ return returning_ran_initialize_base_instance_for_extend
38
+ end
39
+ # mock to ensure call
40
+ def self.initialize_inheritance( inheriting_instance )
41
+ super
42
+ @ran_initialize_inheritance = true
43
+ end
44
+ def self.ran_initialize_inheritance?
45
+ returning_ran_initialize_inheritance = ( @ran_initialize_inheritance ? true : false )
46
+ @ran_initialize_inheritance = false
47
+ return returning_ran_initialize_inheritance
48
+ end
49
+ # mock to ensure call
50
+ def self.initialize_inheriting_instance( parent_instance, inheriting_instance, is_subclass = false )
51
+ super
52
+ @ran_initialize_inheriting_instance = true
53
+ end
54
+ def self.ran_initialize_inheriting_instance?
55
+ returning_ran_initialize_inheriting_instance = ( @ran_initialize_inheriting_instance ? true : false )
56
+ @ran_initialize_inheriting_instance = false
57
+ return returning_ran_initialize_inheriting_instance
58
+ end
59
+
60
+ extend ::ParallelAncestry::Inheritance
61
+
62
+ ran_initialize_inheritance_for_module?.should == true
63
+ ran_initialize_base_instance_for_include?.should == false
64
+ ran_initialize_base_instance_for_extend?.should == false
65
+ ran_initialize_inheritance?.should == false
66
+ ran_initialize_inheriting_instance?.should == false
67
+
68
+ module IncludingModuleMock
69
+ include ::ParallelAncestry::Inheritance::ModuleMock
70
+ end
71
+
72
+ ran_initialize_inheritance_for_module?.should == false
73
+ ran_initialize_base_instance_for_include?.should == true
74
+ ran_initialize_base_instance_for_extend?.should == false
75
+ ran_initialize_inheritance?.should == true
76
+ ran_initialize_inheriting_instance?.should == false
77
+
78
+ module ExtendingModuleMock
79
+ extend ::ParallelAncestry::Inheritance::ModuleMock
80
+ end
81
+
82
+ ran_initialize_inheritance_for_module?.should == false
83
+ ran_initialize_base_instance_for_include?.should == false
84
+ ran_initialize_base_instance_for_extend?.should == true
85
+ ran_initialize_inheritance?.should == false
86
+ ran_initialize_inheriting_instance?.should == false
87
+
88
+ module SubIncludingModuleMock
89
+ include ::ParallelAncestry::Inheritance::ModuleMock::IncludingModuleMock
90
+ end
91
+
92
+ ran_initialize_inheritance_for_module?.should == false
93
+ ran_initialize_base_instance_for_include?.should == false
94
+ ran_initialize_base_instance_for_extend?.should == false
95
+ ran_initialize_inheritance?.should == true
96
+ ran_initialize_inheriting_instance?.should == true
97
+
98
+ module SubExtendingModuleMock
99
+ extend ::ParallelAncestry::Inheritance::ModuleMock::ExtendingModuleMock
100
+ end
101
+
102
+ ran_initialize_inheritance_for_module?.should == false
103
+ ran_initialize_base_instance_for_include?.should == false
104
+ ran_initialize_base_instance_for_extend?.should == false
105
+ ran_initialize_inheritance?.should == false
106
+ ran_initialize_inheriting_instance?.should == false
107
+
108
+ module SubSubIncludingModuleMock
109
+ include ::ParallelAncestry::Inheritance::ModuleMock::SubIncludingModuleMock
110
+ end
111
+
112
+ ran_initialize_inheritance_for_module?.should == false
113
+ ran_initialize_base_instance_for_include?.should == false
114
+ ran_initialize_base_instance_for_extend?.should == false
115
+ ran_initialize_inheritance?.should == true
116
+ ran_initialize_inheriting_instance?.should == true
117
+
118
+ module SubSubExtendingModuleMock
119
+ extend ::ParallelAncestry::Inheritance::ModuleMock::SubExtendingModuleMock
120
+ end
121
+
122
+ ran_initialize_inheritance_for_module?.should == false
123
+ ran_initialize_base_instance_for_include?.should == false
124
+ ran_initialize_base_instance_for_extend?.should == false
125
+ ran_initialize_inheritance?.should == false
126
+ ran_initialize_inheriting_instance?.should == false
127
+
128
+ end
129
+
130
+ end
131
+
132
+ it 'can enable hooks in module subclass including self' do
133
+ class ::ParallelAncestry::Inheritance::ModuleSubclassMock < ::Module
134
+ include ::ParallelAncestry::Inheritance
135
+ end
136
+
137
+ ::ParallelAncestry::Inheritance::ModuleSubclassInstanceMock = ::ParallelAncestry::Inheritance::ModuleSubclassMock.new
138
+ module ::ParallelAncestry::Inheritance::ModuleSubclassInstanceMock
139
+
140
+ # mock to ensure call
141
+ def self.initialize_inheritance_for_module!
142
+ super
143
+ @ran_initialize_inheritance_for_module = true
144
+ end
145
+ def self.ran_initialize_inheritance_for_module?
146
+ returning_ran_initialize_inheritance_for_module = ( @ran_initialize_inheritance_for_module ? true : false )
147
+ @ran_initialize_inheritance_for_module = false
148
+ return returning_ran_initialize_inheritance_for_module
149
+ end
150
+ # mock to ensure call
151
+ def self.initialize_base_instance_for_include( inheriting_instance )
152
+ super
153
+ @ran_initialize_base_instance_for_include = true
154
+ end
155
+ def self.ran_initialize_base_instance_for_include?
156
+ returning_ran_initialize_base_instance_for_include = ( @ran_initialize_base_instance_for_include ? true : false )
157
+ @ran_initialize_base_instance_for_include = false
158
+ return returning_ran_initialize_base_instance_for_include
159
+ end
160
+ # mock to ensure call
161
+ def self.initialize_base_instance_for_extend( inheriting_instance )
162
+ super
163
+ @ran_initialize_base_instance_for_extend = true
164
+ end
165
+ def self.ran_initialize_base_instance_for_extend?
166
+ returning_ran_initialize_base_instance_for_extend = ( @ran_initialize_base_instance_for_extend ? true : false )
167
+ @ran_initialize_base_instance_for_extend = false
168
+ return returning_ran_initialize_base_instance_for_extend
169
+ end
170
+ # mock to ensure call
171
+ def self.initialize_inheritance( inheriting_instance )
172
+ super
173
+ @ran_initialize_inheritance = true
174
+ end
175
+ def self.ran_initialize_inheritance?
176
+ returning_ran_initialize_inheritance = ( @ran_initialize_inheritance ? true : false )
177
+ @ran_initialize_inheritance = false
178
+ return returning_ran_initialize_inheritance
179
+ end
180
+ # mock to ensure call
181
+ def self.initialize_inheriting_instance( parent_instance, inheriting_instance, is_subclass = false )
182
+ super
183
+ @ran_initialize_inheriting_instance = true
184
+ end
185
+ def self.ran_initialize_inheriting_instance?
186
+ returning_ran_initialize_inheriting_instance = ( @ran_initialize_inheriting_instance ? true : false )
187
+ @ran_initialize_inheriting_instance = false
188
+ return returning_ran_initialize_inheriting_instance
189
+ end
190
+
191
+ extend ::ParallelAncestry::Inheritance
192
+
193
+ ran_initialize_inheritance_for_module?.should == true
194
+ ran_initialize_base_instance_for_include?.should == false
195
+ ran_initialize_base_instance_for_extend?.should == false
196
+ ran_initialize_inheritance?.should == false
197
+ ran_initialize_inheriting_instance?.should == false
198
+
199
+ module IncludingModuleMock
200
+ include ::ParallelAncestry::Inheritance::ModuleSubclassInstanceMock
201
+ end
202
+
203
+ ran_initialize_inheritance_for_module?.should == false
204
+ ran_initialize_base_instance_for_include?.should == true
205
+ ran_initialize_base_instance_for_extend?.should == false
206
+ ran_initialize_inheritance?.should == true
207
+ ran_initialize_inheriting_instance?.should == false
208
+
209
+ module ExtendingModuleMock
210
+ extend ::ParallelAncestry::Inheritance::ModuleSubclassInstanceMock
211
+ end
212
+
213
+ ran_initialize_inheritance_for_module?.should == false
214
+ ran_initialize_base_instance_for_include?.should == false
215
+ ran_initialize_base_instance_for_extend?.should == true
216
+ ran_initialize_inheritance?.should == false
217
+ ran_initialize_inheriting_instance?.should == false
218
+
219
+ module SubIncludingModuleMock
220
+ include ::ParallelAncestry::Inheritance::ModuleSubclassInstanceMock::IncludingModuleMock
221
+ end
222
+
223
+ ran_initialize_inheritance_for_module?.should == false
224
+ ran_initialize_base_instance_for_include?.should == false
225
+ ran_initialize_base_instance_for_extend?.should == false
226
+ ran_initialize_inheritance?.should == true
227
+ ran_initialize_inheriting_instance?.should == true
228
+
229
+ module SubExtendingModuleMock
230
+ extend ::ParallelAncestry::Inheritance::ModuleSubclassInstanceMock::ExtendingModuleMock
231
+ end
232
+
233
+ ran_initialize_inheritance_for_module?.should == false
234
+ ran_initialize_base_instance_for_include?.should == false
235
+ ran_initialize_base_instance_for_extend?.should == false
236
+ ran_initialize_inheritance?.should == false
237
+ ran_initialize_inheriting_instance?.should == false
238
+
239
+ module SubSubIncludingModuleMock
240
+ include ::ParallelAncestry::Inheritance::ModuleSubclassInstanceMock::SubIncludingModuleMock
241
+ end
242
+
243
+ ran_initialize_inheritance_for_module?.should == false
244
+ ran_initialize_base_instance_for_include?.should == false
245
+ ran_initialize_base_instance_for_extend?.should == false
246
+ ran_initialize_inheritance?.should == true
247
+ ran_initialize_inheriting_instance?.should == true
248
+
249
+ module SubSubExtendingModuleMock
250
+ extend ::ParallelAncestry::Inheritance::ModuleSubclassInstanceMock::SubExtendingModuleMock
251
+ end
252
+
253
+ ran_initialize_inheritance_for_module?.should == false
254
+ ran_initialize_base_instance_for_include?.should == false
255
+ ran_initialize_base_instance_for_extend?.should == false
256
+ ran_initialize_inheritance?.should == false
257
+ ran_initialize_inheriting_instance?.should == false
258
+
259
+ end
260
+ end
261
+
262
+ end