array-compositing 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,43 @@
1
+
2
+ == 3/17/12
3
+
4
+ Initial release.
5
+
6
+ == 3/18/12
7
+
8
+ Added hooks for subclassing.
9
+
10
+ == 3/19/12
11
+
12
+ Moved parent initialization to separate method (with call to initialize so existing behavior remains).
13
+ Now parent can be initialized after object initialization.
14
+
15
+ == 3/24/12
16
+
17
+ Added _without_hook methods to perform actions without calling hooks.
18
+
19
+ == 3/26/12
20
+
21
+ Fixed typo that broke set without hooks.
22
+
23
+ == 5/27/12
24
+
25
+ Added common CompositingObject support.
26
+
27
+ == 5/31/12
28
+
29
+ Added :parent_composite_object and changed :parent_composite_array to alias :parent_composite_object.
30
+
31
+ == 6/1/12
32
+
33
+ Added :configuration_instance parameter to :initialize.
34
+
35
+ == 6/15/12
36
+
37
+ Moved hooks out to hooked-array and utilized hooked-array as foundation.
38
+ Fixed dependency in gemspec.
39
+
40
+ == 6/18/12
41
+
42
+ Fixed index miscount.
43
+ Fixed index miscount fix for proper count.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Compositing Array #
2
+
3
+ http://rubygems.org/gems/array-compositing
4
+
5
+ # Description #
6
+
7
+ Provides Array::Compositing.
8
+
9
+ # Summary #
10
+
11
+ An implementation of Array that permits chaining, where children inherit changes to parent and where parent settings can be overridden in children.
12
+
13
+ # Install #
14
+
15
+ * sudo gem install array-compositing
16
+
17
+ # Usage #
18
+
19
+ ```ruby
20
+ compositing_array = Array::Compositing.new
21
+ sub_compositing_array = Array::Compositing.new( compositing_array )
22
+
23
+ compositing_array.push( :some_value )
24
+ # compositing_array
25
+ # => [ :A ]
26
+ # sub_compositing_array
27
+ # => [ :A ]
28
+
29
+ compositing_array.delete_at( 0 )
30
+ # compositing_array
31
+ # => [ ]
32
+ # sub_compositing_array
33
+ # => [ ]
34
+
35
+ sub_compositing_array.push( :some_value )
36
+ # compositing_array
37
+ # => [ ]
38
+ # sub_compositing_array
39
+ # => [ :A ]
40
+ ```
41
+
42
+ # License #
43
+
44
+ (The MIT License)
45
+
46
+ Copyright (c) Asher
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of this software and associated documentation files (the
50
+ 'Software'), to deal in the Software without restriction, including
51
+ without limitation the rights to use, copy, modify, merge, publish,
52
+ distribute, sublicense, and/or sell copies of the Software, and to
53
+ permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be
57
+ included in all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
60
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
61
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
62
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
63
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
64
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
65
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
File without changes
@@ -0,0 +1,2 @@
1
+
2
+ require_relative 'array/compositing.rb'
@@ -0,0 +1,15 @@
1
+
2
+ require 'array/hooked'
3
+ #require_relative '../../../../hooked_objects/array-hooked/lib/array-hooked.rb'
4
+
5
+ # namespaces that have to be declared ahead of time for proper load order
6
+ require_relative './namespaces'
7
+
8
+ # source file requires
9
+ require_relative './requires.rb'
10
+
11
+ class ::Array::Compositing < ::Array::Hooked
12
+
13
+ include ::Array::Compositing::ArrayInterface
14
+
15
+ end
@@ -0,0 +1,486 @@
1
+
2
+ module ::Array::Compositing::ArrayInterface
3
+
4
+ include ::Array::Hooked::ArrayInterface
5
+
6
+ instances_identify_as!( ::Array::Compositing )
7
+
8
+ ParentIndexStruct = ::Struct.new( :local_index, :replaced )
9
+
10
+ extend ::Module::Cluster
11
+
12
+ cluster( :compositing_array_interface ).before_include.cascade_to( :class ) do |hooked_instance|
13
+
14
+ hooked_instance.class_eval do
15
+
16
+ unless method_defined?( :non_cascading_set )
17
+ alias_method :non_cascading_set, :[]=
18
+ end
19
+
20
+ unless method_defined?( :non_cascading_insert )
21
+ alias_method :non_cascading_insert, :insert
22
+ end
23
+
24
+ unless method_defined?( :non_cascading_delete_at )
25
+ alias_method :non_cascading_delete_at, :delete_at
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ ################
33
+ # initialize #
34
+ ################
35
+
36
+ def initialize( parent_composite_array = nil, configuration_instance = nil, *args )
37
+
38
+ super( configuration_instance, *args )
39
+
40
+ # arrays that inherit from us
41
+ @sub_composite_arrays = [ ]
42
+
43
+ @parent_index_map = ::Array::Compositing::ParentIndexMap.new
44
+
45
+ initialize_for_parent( parent_composite_array )
46
+
47
+ end
48
+
49
+ ################################### Sub-Array Management #######################################
50
+
51
+ ###########################
52
+ # initialize_for_parent #
53
+ ###########################
54
+
55
+ def initialize_for_parent( parent_composite_array )
56
+
57
+ if @parent_composite_object = parent_composite_array
58
+
59
+ @parent_composite_object.register_sub_composite_array( self )
60
+
61
+ # record in our parent index map that parent has elements that have been inserted
62
+ parent_element_count = @parent_composite_object.count
63
+ @parent_index_map.parent_insert( 0, parent_element_count )
64
+
65
+ # initialize contents of self from parent contents
66
+ parent_element_count.times do |this_time|
67
+ # placeholders so we don't have to stub :count, etc.
68
+ undecorated_insert( 0, nil )
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ #############################
76
+ # parent_composite_object #
77
+ # parent_composite_array #
78
+ #############################
79
+
80
+ attr_accessor :parent_composite_object
81
+
82
+ alias_method :parent_composite_array, :parent_composite_object
83
+
84
+ ##################################
85
+ # register_sub_composite_array #
86
+ ##################################
87
+
88
+ def register_sub_composite_array( sub_composite_array )
89
+
90
+ @sub_composite_arrays.push( sub_composite_array )
91
+
92
+ return self
93
+
94
+ end
95
+
96
+ ####################################
97
+ # unregister_sub_composite_array #
98
+ ####################################
99
+
100
+ def unregister_sub_composite_array( sub_composite_array )
101
+
102
+ @sub_composite_arrays.delete( sub_composite_array )
103
+
104
+ return self
105
+
106
+ end
107
+
108
+ ###################################### Subclass Hooks ##########################################
109
+
110
+ ########################
111
+ # child_pre_set_hook #
112
+ ########################
113
+
114
+ def child_pre_set_hook( index, object, is_insert = false )
115
+
116
+ return object
117
+
118
+ end
119
+
120
+ #########################
121
+ # child_post_set_hook #
122
+ #########################
123
+
124
+ def child_post_set_hook( index, object, is_insert = false )
125
+
126
+ return object
127
+
128
+ end
129
+
130
+ ###########################
131
+ # child_pre_delete_hook #
132
+ ###########################
133
+
134
+ def child_pre_delete_hook( index )
135
+
136
+ # false means delete does not take place
137
+ return true
138
+
139
+ end
140
+
141
+ ############################
142
+ # child_post_delete_hook #
143
+ ############################
144
+
145
+ def child_post_delete_hook( index, object )
146
+
147
+ return object
148
+
149
+ end
150
+
151
+ ##################################### Self Management ##########################################
152
+
153
+ ########
154
+ # == #
155
+ ########
156
+
157
+ def ==( object )
158
+
159
+ load_parent_state
160
+
161
+ super
162
+
163
+ end
164
+
165
+ ##########
166
+ # to_s #
167
+ ##########
168
+
169
+ def to_s
170
+
171
+ #load_parent_state
172
+
173
+ super
174
+
175
+ end
176
+
177
+ #############
178
+ # inspect #
179
+ #############
180
+
181
+ def inspect
182
+
183
+ load_parent_state
184
+
185
+ super
186
+
187
+ end
188
+
189
+ ########
190
+ # [] #
191
+ ########
192
+
193
+ def []( local_index )
194
+
195
+ return_value = nil
196
+
197
+ if @parent_index_map.requires_lookup?( local_index )
198
+ return_value = lazy_set_parent_element_in_self( local_index )
199
+ else
200
+ return_value = super
201
+ end
202
+
203
+ return return_value
204
+
205
+ end
206
+
207
+ #########
208
+ # []= #
209
+ #########
210
+
211
+ def []=( local_index, object )
212
+
213
+ super
214
+
215
+ @sub_composite_arrays.each do |this_sub_array|
216
+ this_sub_array.instance_eval do
217
+ update_for_parent_set( local_index, object )
218
+ end
219
+ end
220
+
221
+ return object
222
+
223
+ end
224
+
225
+ alias_method :store, :[]=
226
+
227
+ ###############
228
+ # delete_at #
229
+ ###############
230
+
231
+ def delete_at( local_index )
232
+
233
+ @parent_index_map.local_delete_at( local_index )
234
+
235
+ deleted_object = non_cascading_delete_at( local_index )
236
+
237
+ @sub_composite_arrays.each do |this_sub_array|
238
+ this_sub_array.instance_eval do
239
+ update_for_parent_delete_at( local_index, deleted_object )
240
+ end
241
+ end
242
+
243
+ return deleted_object
244
+
245
+ end
246
+
247
+ #############
248
+ # freeze! #
249
+ #############
250
+
251
+ # freezes configuration and prevents ancestors from changing this configuration in the future
252
+ def freeze!
253
+
254
+ # unregister with parent composite so we don't get future updates from it
255
+ if @parent_composite_object
256
+ @parent_composite_object.unregister_sub_composite_array( self )
257
+ end
258
+
259
+ return self
260
+
261
+ end
262
+
263
+ ######################################################################################################################
264
+ private ##########################################################################################################
265
+ ######################################################################################################################
266
+
267
+ ###############################
268
+ # perform_set_between_hooks #
269
+ ###############################
270
+
271
+ def perform_set_between_hooks( local_index, object )
272
+
273
+ did_set = false
274
+
275
+ if did_set = super
276
+ @parent_index_map.local_set( local_index )
277
+ end
278
+
279
+ return did_set
280
+
281
+ end
282
+
283
+ ################################################
284
+ # perform_single_object_insert_between_hooks #
285
+ ################################################
286
+
287
+ def perform_single_object_insert_between_hooks( local_index, object )
288
+
289
+ if local_index = super
290
+
291
+ @parent_index_map.local_insert( local_index, 1 )
292
+
293
+ @sub_composite_arrays.each do |this_sub_array|
294
+ this_sub_array.instance_eval do
295
+ update_for_parent_insert( local_index, object )
296
+ end
297
+ end
298
+
299
+ end
300
+
301
+ return local_index
302
+
303
+ end
304
+
305
+ #####################################
306
+ # lazy_set_parent_element_in_self #
307
+ #####################################
308
+
309
+ def lazy_set_parent_element_in_self( local_index, *optional_object )
310
+
311
+ object = nil
312
+
313
+ case optional_object.count
314
+ when 0
315
+ parent_index = @parent_index_map.parent_index( local_index )
316
+ object = @parent_composite_object[ parent_index ]
317
+ when 1
318
+ object = optional_object[ 0 ]
319
+ end
320
+
321
+ # We call hooks manually so that we can do a direct undecorated set.
322
+ # This is because we already have an object we loaded as a place-holder that we are now updating.
323
+ # So we don't want to sort/test uniqueness/etc. We just want to insert at the actual index.
324
+
325
+ unless @without_child_hooks
326
+ object = child_pre_set_hook( local_index, object, false )
327
+ end
328
+
329
+ unless @without_hooks
330
+ object = pre_set_hook( local_index, object, false )
331
+ end
332
+
333
+ undecorated_set( local_index, object )
334
+
335
+ @parent_index_map.looked_up!( local_index )
336
+
337
+ unless @without_hooks
338
+ post_set_hook( local_index, object, false )
339
+ end
340
+
341
+ unless @without_child_hooks
342
+ child_post_set_hook( local_index, object, false )
343
+ end
344
+
345
+ return object
346
+
347
+ end
348
+
349
+ ###########################
350
+ # update_for_parent_set #
351
+ ###########################
352
+
353
+ def update_for_parent_set( parent_index, object )
354
+
355
+ unless @parent_index_map.replaced_parent_element_with_parent_index?( parent_index )
356
+
357
+ local_index = @parent_index_map.parent_set( parent_index )
358
+
359
+ if @parent_index_map.requires_lookup?( local_index )
360
+
361
+ @sub_composite_arrays.each do |this_array|
362
+ this_array.instance_eval do
363
+ update_for_parent_set( local_index, object )
364
+ end
365
+ end
366
+
367
+ end
368
+
369
+ end
370
+
371
+ end
372
+
373
+ ##############################
374
+ # update_for_parent_insert #
375
+ ##############################
376
+
377
+ def update_for_parent_insert( parent_insert_index, object )
378
+
379
+ unless @parent_index_map.replaced_parent_element_with_parent_index?( parent_insert_index )
380
+
381
+ local_index = @parent_index_map.parent_insert( parent_insert_index, 1 )
382
+
383
+ undecorated_insert( local_index, nil )
384
+
385
+ @sub_composite_arrays.each do |this_array|
386
+ this_array.instance_eval do
387
+ update_for_parent_insert( local_index, object )
388
+ end
389
+ end
390
+
391
+ end
392
+
393
+ end
394
+
395
+ #################################
396
+ # update_for_parent_delete_at #
397
+ #################################
398
+
399
+ def update_for_parent_delete_at( parent_index, object )
400
+
401
+ did_delete = false
402
+
403
+ unless @parent_index_map.replaced_parent_element_with_parent_index?( parent_index )
404
+
405
+ local_index = @parent_index_map.local_index( parent_index )
406
+
407
+ if @without_child_hooks
408
+ child_pre_delete_hook_result = true
409
+ else
410
+ child_pre_delete_hook_result = child_pre_delete_hook( local_index )
411
+ end
412
+
413
+ if child_pre_delete_hook_result
414
+
415
+ @parent_index_map.parent_delete_at( parent_index )
416
+
417
+ # I'm unclear why if we call perform_delete_between_hooks (including through non_cascading_delete_at)
418
+ # we end up smashing the last index's lazy lookup value, turning it false
419
+ # for now simply adding hooks manually here works; the only loss is a little duplicate code
420
+ # to call the local (non-child) hooks
421
+ if @without_hooks
422
+ pre_delete_hook_result = true
423
+ else
424
+ pre_delete_hook_result = pre_delete_hook( local_index )
425
+ end
426
+
427
+ if pre_delete_hook_result
428
+
429
+ object = undecorated_delete_at( local_index )
430
+
431
+ did_delete = true
432
+
433
+ unless @without_hooks
434
+ object = post_delete_hook( local_index, object )
435
+ end
436
+
437
+ unless @without_child_hooks
438
+ child_post_delete_hook( local_index, object )
439
+ end
440
+
441
+ @sub_composite_arrays.each do |this_array|
442
+ this_array.instance_eval do
443
+ update_for_parent_delete_at( local_index, object )
444
+ end
445
+ end
446
+
447
+ end
448
+
449
+ else
450
+
451
+ if @parent_index_map.requires_lookup?( local_index )
452
+ lazy_set_parent_element_in_self( local_index, object )
453
+ end
454
+
455
+ end
456
+
457
+ end
458
+
459
+ return did_delete
460
+
461
+ end
462
+
463
+ ######################
464
+ # parent_reversed! #
465
+ ######################
466
+
467
+ def parent_reversed!
468
+
469
+ @sort_order_reversed = ! @sort_order_reversed
470
+
471
+ end
472
+
473
+ #######################
474
+ # load_parent_state #
475
+ #######################
476
+
477
+ def load_parent_state
478
+
479
+ # if is used for case where duplicate is created (like :uniq) and initialization not called during dupe process
480
+ @parent_index_map.indexes_requiring_lookup.each do |this_local_index|
481
+ lazy_set_parent_element_in_self( this_local_index )
482
+ end
483
+
484
+ end
485
+
486
+ end