array-compositing 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.
- data/CHANGELOG.rdoc +43 -0
- data/README.md +65 -0
- data/README.rdoc +0 -0
- data/lib/array-compositing.rb +2 -0
- data/lib/array/compositing.rb +15 -0
- data/lib/array/compositing/array_interface.rb +486 -0
- data/lib/array/compositing/parent_index_map.rb +385 -0
- data/lib/array/hooked/compositing.rb +2 -0
- data/lib/array/namespaces.rb +3 -0
- data/lib/array/requires.rb +3 -0
- data/spec/array/compositing/parent_index_map_spec.rb +442 -0
- data/spec/array/compositing_spec.rb +1098 -0
- metadata +74 -0
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,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
|