parallel_ancestry 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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