cascading_classes 0.3.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,256 +1,1075 @@
1
- # CascadingClasses
1
+ todo: blank values: what are the defaults for each class type
2
+ todo: what kind of tree is this? not binary!
3
+ todo: find a city that has a pro baseball team but not a pro basketball team (or other way around)
2
4
 
3
- This library allows you to easily create properties that cascade down a class tree. For example:
5
+ Cascading Classes
4
6
 
5
- require 'cascading_classes'
7
+ This is a small ruby library to help manipulate and manage hierachal data. Whether modeling a simple hierarchy or a deeply nested tree, whether you have only a handful of properties to manage or hundreds, this library aims to help you can stay in control of your data.
6
8
 
7
- class Parent
9
+ Before you go running off playing with this library, you have to answer a few questions about your data. Think about which of the following types of hierachal data best describes your situation:
10
+
11
+ * few children spanning a small number of generations (skinny and short)
12
+ * few children spanning many generations (skinny and tall)
13
+ * many children spanning few generations (wide and short)
14
+ * many children spanning many generations (wide and tall)
15
+
16
+ Next, you'll need to come up with the essential set of properties that define your data. An essential property is __not__ one that can be derived from another. Think of all the "traits", "behaviors", and "functionality" you would like to model. An essential property is one that is necessary, one that cannot be replaced by some combination of the others, one that, without which, the purpose, the point, of your data cannot be fulfilled.
17
+
18
+ Group these essential ingredients together and then describe them one at a time. For each, what kind of object is it? What is its type? Is it a string, a number, a list, a hash? What kind of object is it? What kinds of values does it take? Next, think about what its default should be. Next, decide what value is for every new child? That is, what should the value of the property be when a new node is added to the hierarchy? Next, what
19
+ "blank" means for your property. What value or values will cause the property to be effectively null or without meaning? This is important since a property that is blank can inherit its value from a parent.
20
+
21
+ Now list those properties that not as vital or that can be derived from some combination of essential ones. For each of these, answer the same questions: What is a suitable __default__? What should its value be for every__new__ child? What value or values make the property __blank__?
22
+
23
+ Only after doing all this are ready to use this library.
24
+
25
+ # Quick Tour
26
+
27
+ Assume the following simple class hierarchy:
28
+
29
+ class A
8
30
  extend CC
9
31
  end
10
32
 
11
- class Child < Parent; end
33
+ class B < A
34
+ end
12
35
 
13
- class GrandChild < Child; end
36
+ class C < B
37
+ end
14
38
 
15
- now create properties that will cascade down all descendents
39
+ A is the top-level parent, B is one of A's (possibly many) children. C is one of B's (possibly many) children.
16
40
 
17
- Parent.cascade :eye_color, :hair_color
41
+ Create ```name, city and state``` properties:
18
42
 
19
- set a property on a parent class
43
+ A.cascade do
44
+ name :default => "Franklin"
45
+ city :default => "New York"
46
+ state :default => "NY"
47
+ end
48
+
49
+ specifying a default sets the properties on top-level class to the same value:
20
50
 
21
- Parent.eye_color = "brown"
51
+ p A.name # => "Franklin"
52
+ p A.city # => "New York"
53
+ p A.state # => "NY"
22
54
 
23
- all child classes are affected
55
+ furthermore, blank descendents inherit the same property values:
24
56
 
25
- p Child.eye_color # => "brown"
26
- p GrandChild.eye_color # => "brown"
57
+ p B.name # => "Franklin"
58
+ p C.name # => "Franklin"
27
59
 
28
- now change the property on a descendent class
60
+ p B.city # => "New York"
61
+ p C.city # => "New York"
29
62
 
30
- Child.eye_color = "blue"
63
+ ...
31
64
 
32
- only descendents of ```Child``` are affected
65
+ Update the properties on A's desendents:
66
+
67
+ B.name = "Charles"
68
+ C.name = "Sam"
33
69
 
34
- p Parent.eye_color # => "brown"
35
- p Child.eye_color # => "blue"
36
- p GrandChild.eye_color # => "blue"
70
+ C.city = "Rochester"
37
71
 
72
+ Collect property names and values:
38
73
 
39
- Getting Started
40
- ---------------
74
+ p A.to_hash # => {:name => "Franklin", :city => "New York", :state => "NY"}
75
+ p B.to_hash # => {:name => "Charles", :city => "New York", :state => "NY"}
76
+ p C.to_hash # => {:name => "Sam", :city => "Rochester", :state => "NY"}
41
77
 
42
- Assume the class structure from above:
78
+ pass 'false' for only those properties that were specifically set:
43
79
 
44
- class Parent
45
- extend CC
46
- end
80
+ p A.to_hash(false) # => {:name => "Franklin", :city => "New York", :state => "NY"}
81
+ p B.to_hash(false) # => {:name => "Charles"}
82
+ p C.to_hash(false) # => {:name => "Sam", :city => "Rochester"}
83
+
84
+ This is a pretty basic example with just three nodes spanning three generations (short and skinny). Also, there are only three properties. Let's ramp up the complexity
47
85
 
48
- class Child < Parent; end
86
+ ### ...more...
49
87
 
50
- class GrandChild < Child; end
88
+ Add a second child to A based out of Boston:
51
89
 
52
- ## Setting default values
90
+ B2 = Class.new(A) # same as: class B2 < A; end
91
+
92
+ B2.name = 'Thomas'
93
+ B2.city = 'Boston'
94
+ B2.state = 'MA'
53
95
 
54
- Setting default values for cascaded properties is easy. The first method is to bracket them in Array pairs. For example:
96
+ Descendents of B2 will inherit these new values unless set.
55
97
 
56
- Parent.cascade [:eye_color, "hazel"], [:hair_color, "brown"], :has_education
98
+ class C2 < B2; end
57
99
 
58
- sets the ```eye_color``` and ```hair_color``` properties on ```Parent``` to "hazel" and "brown" and initializes, but does not set, the ```has_education``` property.
100
+ p C2.city # => Boston
101
+ p C2.state # => MA
59
102
 
103
+ Now we have three properties on five nodes spanning three generations.
60
104
 
61
- ### Alternative syntax: all!
105
+ Let's add two more properties; these will depend on one's location:
62
106
 
63
- You also create cascaded properties via ```Parent.all!```
107
+ A.cascade do
108
+ baseball_team :default => proc{|me| me.state == 'NY' ? "Yankees" : "Red Sox"}
109
+ basketball_team :default => proc{|me| me.state == 'NY' ? "Knicks" : "Celtics"}
110
+ end
64
111
 
65
- For example, the following are equivalent:
112
+ The beauty here is that when the ```baseball_team``` property is called, the proc is evaluated in the context of the calling class. This means each property automatically gains the 'correct' baseball_team value through just one line of code. Though the current hierarchy is tiny, imagine a tree with hundreds of nodes spanning many generations.
66
113
 
67
- Parent.cascade :arms, :legs
68
- Parent.all! :arms, :legs
114
+ Watch how this works for our five node, three generation setup. Notice how each proc (read: behavior) cascades down the class hierarchy:
69
115
 
70
- this initiates, but does not set, two cascading properties: ```arms```, ```legs```
116
+ p A.baseball_team # => "Yankees"
117
+ p B.baseball_team # => "Yankees"
118
+ p C.baseball_team # => "Yankees"
71
119
 
72
- p Parent.arms # => nil
73
- p Child.arms # => nil
74
- p GrandChild.arms # => nil
120
+ p B2.baseball_team # => "Red Sox"
121
+ p C2.baseball_team # => "Red Sox"
75
122
 
76
- since no default was given, all are nil
123
+ If we change C2's location, its teams should follow suit:
77
124
 
78
- Again, to set a default use brackets:
125
+ C2.city = "New York"
126
+ C2.state = "NY"
79
127
 
80
- Parent.all! :arms, [:legs, 2]
128
+ p C2.baseball_team # => "Yankees"
129
+ p C2.basketball_team # => "Knicks"
81
130
 
131
+ Any new node added to the mix should be "born" with the same behavior:
82
132
 
83
- ### Alternative syntax: block arguments
133
+ class D < C; end
84
134
 
85
- You can also create cascading properties with a block. The major difference is that you must not prefix your property with a colon.
135
+ p D.state # => "NY"
136
+ p D.baseball_team # => "Yankees"
86
137
 
87
- For example
138
+ D.state = "MA"
139
+ p D.baseball_team # => "Red Sox"
88
140
 
89
- Parent.cascade [:eye_color, "blue"], [:hair_color, "blond"]
141
+ Whether this is the "correct" response is not the point. Moving from city to city most likely wouldn't change one's favorite baseball team. The point is that if you decide to define the baseball team property (read: behavior) this way, you can affect the whole tree at once.
90
142
 
91
- is equivalent to
143
+ Finally, let's add a child to C2, located in Los Angeles, and rewrite the sports properties to fit our bulging world:
92
144
 
93
- Parent.cascade do
94
- eye_color :default => "blue"
95
- hair_color :default => "blond"
145
+ class D2 < C2
146
+ end
147
+
148
+ D2.city = "Los Angeles"
149
+ D2.state = "CA"
150
+
151
+ A.baseball_team = Proc.new do |me|
152
+ case me.state
153
+ when "NY" then "Yankees"
154
+ when "MA" then "Red Sox"
155
+ when "CA" then "Dodgers"
156
+ else ''
157
+ end
158
+ end
159
+
160
+ A.basketball_team = Proc.new do |me|
161
+ case me.state
162
+ when "NY" then "Knicks"
163
+ when "MA" then "Celtics"
164
+ when "CA" then "Lakers"
165
+ else ""
166
+ end
96
167
  end
97
168
 
98
- or
169
+ We now in charge of seven nodes spanning four generations.
170
+
171
+ To further showcase proc properties, let's create a ```teams``` property that is a composite of one's favorite teams:
99
172
 
100
- Parent.all! do
101
- eye_color :initial => "blue"
102
- hair_color :initial => "blond"
173
+ A.cascade do
174
+ teams :default => Proc.new{|me|
175
+ [me.baseball_team, me.basketball_team]
176
+ }
103
177
  end
178
+
179
+ test it out
104
180
 
105
- ### More block examples
181
+ p A.teams # => ["Yankees", "Knicks"]
182
+ p B.teams # => ["Yankees", "Knicks"]
183
+ p C.teams # => ["Yankees", "Knicks"]
184
+ p D.teams # => ["Yankees", "Knics"]
185
+ p B2.teams # => ["Red Sox", "Celtics"]
186
+ p C2.teams # => ["Red Sox", "Celticks"]
187
+ p D2.teams # => ["Dodgers", "Lakers"]
188
+
189
+ now we can move people around and expect their teams to follow suit:
190
+
191
+ D.state = "CA"
192
+ D.city = "Los Angeles"
193
+
194
+ p D.teams # => ["Dodgers", "Lakers"]
106
195
 
107
- The folowing are all equivalent
196
+ ### top it off with a block
108
197
 
109
- Parent.cascade :address, :phone, [:email, "jon@example.com"]
198
+ For the final act of this brief tour, we will print the ```teams``` property for D2 and its ancestors. The simplest way to do this is by passing a block during the ```teams``` property call:
110
199
 
111
- Parent.cascade :address, :phone do
112
- email :initial => "jon@example.com"
200
+ p D2.teams do |val, parents|
201
+ puts "D2: #{val.inspect}"
202
+ parents.each{|parent| puts "#{parent}: #{parent.teams.inspect}"}
113
203
  end
114
204
 
115
- Parent.all! do
116
- address
117
- phone
118
- email :start => "jon@example.com"
205
+ # => D2: ["Dodgers", "Lakers"]
206
+ C2: ["Red Sox", "Celtics"]
207
+ B2: ["Red Sox", "Celtics"]
208
+ A: ["Yankees", "Knicks"]
209
+
210
+ ### quickly summarized
211
+
212
+ Even simple hierarchies can turn into complex beasts in need of taming. To use this library in your projects:
213
+
214
+ * require the library: ```require 'cascading_classes'```
215
+ * extend or include your class with CascadingClasses or its synonym: CC
216
+ * call ```cascade``` on a parent class
217
+ * wrap your properties and their defaults in block
218
+ * get and set properties on any node in the hierachy
219
+ * add extra functionality with derived properties using procs
220
+ * pass a block to easily iterate over a property's parents
221
+
222
+ # In Depth:
223
+
224
+ Assume a simple three node, three generation setup:
225
+
226
+ class A
227
+ include CC
228
+ end
229
+
230
+ class B < A
231
+ end
232
+
233
+ class C < B
234
+ end
235
+
236
+ It's important to understand that this library distinguishes among three kinds of properties: non-container types like Floats, Fixnums, Strings, Symbols, and booleans; containers types like arrays, hashes, and sets; and procs.
237
+
238
+ The reason for this is allow non-containers to inherit their value when blank, while ensuring containers don't. Also, proc properties are very different from the others. They can be used to add layers of seemingly complex functionality from a very simple, core set of properties. Proc properties (read: behaviors) can also be used to add dynamism and time-varying effects.
239
+
240
+ ### basic types: strings, symbols, numbers, booleans, ...
241
+
242
+ Using basic types is as it should be, dead simple:
243
+
244
+ A.cascade do
245
+ score :default => 45.2
246
+ rank :default => 4
247
+ color :default => "red"
248
+ direction :default => :south
249
+ available :default => true
250
+ end
251
+
252
+ we've added five properties accessible by A and its descendents. Each is of a different type and each has been given a default. It's important to understand that specifying a default has a side effect: it sets the value on the class invoking ```cascade```. That is, the default score for any property will be 45.2 and it won't change.
253
+
254
+ ### defaults
255
+
256
+ To access the default, pass ':default' to any descendent in the property call
257
+
258
+ p A.score :default # => 45.2
259
+ p B.score :default # => 45.2
260
+ p C.score :default # => 45.2
261
+
262
+ notice how the property can change, but the default stays the same:
263
+
264
+ A.score = 19.2
265
+ B.score = 22.1
266
+ C.score = 39.7
267
+
268
+ p A.score # => 19.2
269
+ p B.score # => 22.1
270
+ p C.score # => 39.7
271
+
272
+ p A.score :default # => 45.2
273
+ p B.score :default # => 45.2
274
+ p C.score :default # => 45.2
275
+
276
+ ### one essential rule
277
+
278
+ The basic rule is that non-container properties inherit their value (when blank) by default. The definition of blank can be redefined, but defaults to reasonable values: empty string for ':String', nil for ':Float' and ':Fixnum' and symbols. Also, unset properties are usually blank.
279
+
280
+ p A.score # => 19.2
281
+ p B.score # => 22.1
282
+ p C.score # => 39.7
283
+
284
+ B.score = nil
285
+ C.score = nil
286
+
287
+ p B.score # => 19.2
288
+ p C.score # => 19.2
289
+
290
+ notice how even though the ```score``` property is set to nil, it prints out the value of A's ```score```. This is inheritance at work. Next, notice how the same thing happens for the ```rank``` property except that it wasn't explicitly set to a blank value, rather they defaulted to one:
291
+
292
+ p A.rank # => 4
293
+ p B.rank # => 4
294
+ p C.rank # => 4
295
+
296
+ The way this works is that when you ask B for its ```rank```, it asks itself whether the value is blank. If it is, it proxies to its nearest parent, otherwise it returns its value. Hence, when C is asked for its ```rank```, it proxies to B, which in turn proxies to A. Notice how C returns B's rank when the proeprty on B is set:
297
+
298
+ B.rank = 12
299
+
300
+ p A.rank # => 4
301
+ p B.rank # => 2
302
+ p C.rank # => 2
303
+
304
+ Of course, this cascading behavior may not be what you want. You can explicitly control whether a property is to inherit:
305
+
306
+ p A.score # => 19.2
307
+ p B.score false # => nil
308
+ p C.score true # => 19.2
309
+
310
+ Here, we tell B just to give us its own value without proxying to A. Passing ```true``` to C tells C that it __can__ ask B for its value if its own is blank.
311
+
312
+ It's important to realize that any property that is not blank will never inherit no matter what arguments you pass (unless its ':default').
313
+
314
+ C.score = 17.4
315
+
316
+ p A.score(true) # => 19.2
317
+ p B.score # => 19.2
318
+ p C.score(true) # => 17.4
319
+
320
+ Here, A and C have set the ```score``` property, hence any call to the property regardless of the inherit argument, returns that same value.
321
+
322
+ ### single example inheritance summary
323
+
324
+ For a brief summarry of inheritance for noncontainers consider this example:
325
+
326
+ A.color = "white"
327
+ p A.color # => "white"
328
+ p B.color # => "white"
329
+ p C.color # => "white"
330
+
331
+ A.color = "blue"
332
+ p A.color # => "blue"
333
+ p B.color # => "blue"
334
+ p C.color # => "blue"
335
+
336
+ B.color = "orange"
337
+
338
+ p A.color # => "blue"
339
+ p B.color # => "orange"
340
+ p C.color # => "orange"
341
+
342
+ see how initially the ```color``` property for B and C inherits from A. When B's ```color``` is set to a nonblank value, C no longer returns the value of it two generations higher. It returns B's instead.
343
+
344
+ ### '<propname>_is_blank?'
345
+
346
+ For each cascaded property, an extra method is added to every node in the hierarchy that returns whether the property is blank:
347
+
348
+ p A.score_is_blank? # => false
349
+ p B.score_is_blank? # => false
350
+ p C.score_is_blank? # => true
351
+
352
+ p C.score = 18.2
353
+ p C.score_is_blank? # => false
354
+
355
+ p A.color_is_blank? # => false
356
+ p B.color_is_blank? # => false
357
+ p C.color_is_blank? # => true
358
+
359
+ For good measure, notice inheritance at work for the other properties.
360
+
361
+ B.available = false
362
+ C.color = "purple"
363
+
364
+ p C.score # => 18.2 (inherits from B)
365
+ p C.color # => "purple" (own value)
366
+ p C.rank # => 12 (inherits from B)
367
+ p C.direction # => :south (inherits from A)
368
+ p C.available # => false (inherits from B)
369
+
370
+ ### more on defaults
371
+
372
+ Each property can have a default value which is set in the ```cascade``` call. Default values are like global values. They are set once and can be referenced at any time by any node in the tree.
373
+
374
+ Again, recall that the side effect of giving a default is that the parent property is set too. Let's create a ```city``` property that defaults to "seatle"
375
+
376
+ A.cascade do
377
+ city :default => "seatle"
119
378
  end
120
379
 
121
- Parent.all!{ address; phone; email :default => "jon@example.com" }
380
+ p A.city_is_blank? # => false
381
+
382
+ p A.city # => "seatle"
383
+ p B.city # => "seatle" (inherits from A)
384
+
385
+ A.city = "austin"
386
+ B.city = "los angeles"
387
+
388
+ p A.city :default # => "seatle"
389
+ p B.city :default # => "seatle"
390
+ p C.city :default # => "seatle"
391
+
392
+ Notice how adding the ':default' argument returns the property-wide default regardless of the node making the call.
393
+
394
+ ### types
122
395
 
123
- You can also set a property in a block using ```set_property```
396
+ Defaults are not required, but if not given, the type of the property won't be known until it is first assigned.
124
397
 
125
- Parent.cascade do
126
- set_property :address
127
- set_property :phone
128
- set_property :email, :default => "jon@example.com"
398
+ props = A.cascade do
399
+ space_free
400
+ color
129
401
  end
130
402
 
403
+ p props[:space_free][:type] # => :undefined_type
404
+ A.space_free = 328.12
405
+ p props[:space_free][:type] # => :Float
131
406
 
132
- ## Quick Summary
407
+ p props[:color][:type] # => :undefined_type
408
+ A.color = "red"
409
+ p props[:color][:type] # => :String
133
410
 
134
- NOTE: CC is equivalent to CascadingClasses
411
+ Here we capture the result of the ```cascade``` call into the ```props``` variable. This let's us peek into the property metadata being tracked behind the scenes. Notice that every property has a type and that it defaults to ":unknown_type" unless known.
135
412
 
136
- require the gem
413
+ The type of a property is defined as the class (in symbol form) of the first assigned value. This is inferred automatically when a default is given.
137
414
 
138
- require cascading_classes
415
+ ### set the type manually
139
416
 
140
- extend or include the module
417
+ You can manually mark the type of a property during the ```cascade``` call. This is useful if you you know a property's type but not yet its initial (top-most) value or default. There's no reason to let such information go to waste:
141
418
 
142
- class Foo
143
- extend CascadingClasses
419
+ props = A.cascade do
420
+ name :Hash => true
421
+ address :hash => true
422
+ city :String => true
423
+ area :fixnum => true
424
+ zip :Fixnum => true
144
425
  end
145
426
 
146
- create some cascading properties by listing them
427
+ p props[:name][:type] # => :Hash
428
+ p props[:color][:type] # => :Symbol
429
+ p props[:address][:type] # => :Hash
430
+ p props[:city][:type] # => :String
431
+ p props[:area][:type] # => :Fixnum
432
+ p props[:zip][:type] # => :Fixnum
147
433
 
148
- A.cascade :desk, :chair, :lamp
434
+ ## container types: arrays, hashes, sets, ...
149
435
 
150
- or by using a block
436
+ Container properties are those that implement ```each``` or include ```Enumerable```. The definition of "blank" for arrays, hashes and sets has been pre-defined to be those that are empty.
437
+
438
+ ### containers and inheritance: fundamental rule
439
+
440
+ It doesn't usually make sense for a hash or array to inherit its parent's value. By default, containers don't inherit. Other than this, they behave exactly as you'd expect:
151
441
 
152
442
  A.cascade do
153
- desk
154
- chair
155
- lamp
443
+ urls :default => ["www.google.com", "www.nytimes.com"]
444
+ name :default => {:first => 'jon', :last => 'smith'}
156
445
  end
157
446
 
158
- or
447
+ p A.urls # => ["www.google.com", "www.nytimes.com"]
448
+ p B.urls # => []
449
+ p C.urls # => []
450
+
451
+ B.urls << "www.techcrunch.com" << "www.twitter.com"
452
+ C.urls = ["www.bgr.com"]
453
+
454
+ p A.urls # => ["www.google.com", "www.nytimes.com"]
455
+ p B.urls # => ["www.techcrunch.com", "www.twitter.com"]
456
+ p C.urls # => ["www.bgr.com"]
159
457
 
160
- A.cascade{ desk; chair; lamp }
458
+ p A.name # => {:first => 'jon', :last => 'smith'}
459
+ p B.name # => {}
460
+ p C.name # => {}
161
461
 
162
- get and set the property on any descendent
462
+ B.name[:first] = "adam"
463
+ B.name[:last] = "johnson"
464
+ C.name = {:first => 'terry', :last => 'williamson'}
163
465
 
164
- class B < A; end
466
+ p A.name # => {:first => 'jon', :last => 'smith'}
467
+ p B.name # => {:first => 'adam', :last => 'johnson'}
468
+ p C.name # => {:first => 'terry', :last => 'williamson'}
165
469
 
166
- B.desk = "a large, wooden structure in need of a fix"
470
+ ### container lack inheritance override
167
471
 
168
- class C < A; end
472
+ You can this lack of inheritance default at the property-wide level or during each individual call. To allow container properties to inherit by default, add an ":inherit => true" argument in the ```cascade``` call. Let's add a ```cities``` container property, allowing it to inherit (if blank):
169
473
 
170
- C.lamp = "old and rusty"
474
+ A.cascade do
475
+ cities :default=> ["boston", "new york"], :inherit => true
476
+ end
171
477
 
172
- ## What's the difference between ```extend CC``` and ```include CC```?
478
+ p A.cities # => ["boston", "new york"]
479
+ p B.cities # => ["boston", "new york"] (inherited from A)
480
+ p C.cities # => ["boston", "new york"] (inherited from A)
173
481
 
174
- First, in either case you are dealing with class instance variables.
482
+ p A.cities_is_blank? # => false
483
+ p B.cities_is_blank? # => true
484
+ p C.cities_is_blank? # => true
175
485
 
176
- A.cascade :eye_color
177
- A.eye_color = "blue"
486
+ if you think this is useful, just take care not to trip over yourself
178
487
 
179
- sets ```@eye_color``` on ```A```.
488
+ B.cities << "london"
180
489
 
181
- But if you also want the properties to cascade to instance variables, you have three options. The first is to ```include CC```, which, by default, creates the properties on instances too
490
+ p B.cities_is_blank? # => true
491
+ p A.cities # => ["boston", "new york", "london"]
492
+ p B.cities # => ["boston", "new york", "london"]
182
493
 
183
- class Parent
184
- include CC
494
+ That's likely __not__ the behavior you were expecting. What happened is that the ```cities``` property on B returned the value on A. Hence "london" was appended to A's array, not B's empty one.
495
+
496
+ #### overriding the override
497
+
498
+ If you do find it useful to override the lack of inheritance for containers at the property-wide level, you will probably want to know how to explicitly override this behavior. Simply pass in ':inherit => false' in the property call. Let's correct the previous example:
499
+
500
+ B.cities(:inherit => false) << "london"
501
+
502
+ p A.cities # => ["boston", "new york"]
503
+ p B.cities # => ["london"]
504
+
505
+ you can also ommit the the ':inherit' bit
506
+
507
+ B.cities = []
508
+
509
+ p B.cities # ["boston", "new york"] (inherits from A)
510
+ p B.cities false # [] (own value)
511
+
512
+ B.cities(false) << "london"
513
+
514
+ p B.cities # ["london"]
515
+
516
+ ### manual inheritance
517
+
518
+ This works in the opposite way too. Container properties don't inherit by default, but you can force the behavior on an indivudual basis:
519
+
520
+ A.cascade do
521
+ cuisine :default => [:italian, :french]
185
522
  end
186
523
 
187
- Parent.cascade [:one, 1]
188
- p = Parent.new
189
- p p.one # => 1
524
+ p A.cuisine # => [:italian, :french]
525
+ p B.cuisine # => []
526
+ p C.cuisine # => []
190
527
 
191
- class Child < Parent; end
528
+ p B.cuisine :inherit # => [:italian, :french]
529
+ p C.cuisine :inherit # => [:italian, :french]
192
530
 
193
- c = Child.new
194
- p c.one # => 1
531
+ The inherit argument can be one of ```false, true, nil``` or a number. When a number, it specifies the maximum inheritance age. A negative value means "infinite inheritance". As does ```true```. For example:
195
532
 
196
- The other two ways to ensure instances have cascading properties uses an additional argument:
533
+ p C.cuisine :inherit => false # => [] (own value)
534
+ p C.cuisine :inherit => nil # => [] (own value)
535
+ p C.cuisine :inherit => 0 # => [] (own value)
536
+ p C.cuisine :inherit => 1 # => [] (own value)
537
+ p C.cuisine :inherit => 2 # => [:italian, :french] (inherits from A)
538
+ p C.cuisine :inherit => 83 # => [:italian, :french] (inherits from A)
539
+ p C.cuisine :inherit => -1 # => [:italian, :french] (inherits from A)
540
+ p C.cuisine :inherit => true # => [:italian, :french] (inherits from A)
197
541
 
198
- either
542
+ you can ommit the ':inherit' syntax entirely:
199
543
 
200
- Parent.cascade [:hair_color, "blue", true]
544
+ p C.cuisine
545
+ p C.cuisine false # => []
546
+ p C.cuisine nil # => []
547
+ p C.cuisine 0 # => []
548
+ p C.cuisine 1 # => []
549
+ p C.cuisine 2 # => [:italian, :french]
550
+ p C.cuisine 83 # => [:italian, :french]
551
+ p C.cuisine -1 # => [:italian, :french]
552
+ p C.cuisine true # => [:italian, :french]
553
+ p C.cuisine 20 # => [:italian, :french]
201
554
 
202
- or
203
-
204
- Parent.cascade do
205
- hair_color :default => "blue", :instances => true
555
+ Notice why an inheritance age of '1' is not enough to reach the value on A. This is because there are two generations between C and A and a max inheritance of '1' only gets you to B, which is blank.
556
+
557
+ ### metadata again
558
+
559
+ One way to obtain the background metadata hash is by capturing the result of the ```cascade``` call:
560
+
561
+ props = A.cascade do
562
+ states :default => [:good, :bad, :disaster], :inherit => true
563
+ colors :default => ["blue", "orange", "red"]
564
+ end
565
+
566
+ p props.keys # => [:states, :colors]
567
+ p props[:states].keys # => [:default, :instances_too, :getters, :setters, ...]
568
+
569
+ The hash is located as an instance variable (read: property) on the singleton class of the top-most class. Hence, another way at it is:
570
+
571
+ props = A.singleton_class.properties
572
+
573
+ ### metadata - check 'inheritance' status
574
+
575
+ The rule is very simple: containers don't inherit, all others do. But seeing as you can override this rule, one reason you might go looking for the metadata hash is to remind yourself of a property's inheritance status. This is especially true when your collection of properties is large.
576
+
577
+ p props[:states][:inherit] # => true
578
+ p props[:colors][:inherit] # => false
579
+
580
+ ### more on inheritance and max inherit age
581
+
582
+ If you override the container inheritance default and add ```:inherit => true``` as part of the ```cascade``` call, each property call will assume the inheritance age to be -1. That is, it will assume you want to inherit upwards until the first parent node whose property value is not blank is reached. This is also true for noncontainer types, as they inherit by default (unless inheritance is turned off at the property-wide level using: ```:inherit => false```).
583
+
584
+ p A.states # => [:good, :bad, :disaster] (inherits from A)
585
+ p B.states # => [:good, :bad, :disaster] (inherits from A)
586
+ p C.states # => [:good, :bad, :disaster] (inherits from A)
587
+
588
+ p A.colors # => ["blue", "orange", "red"] (own value)
589
+ p B.colors # => [] (own value)
590
+ p C.colors # => [] (own value)
591
+
592
+ ### container behavior summary
593
+
594
+ To understand all you need to know about containers and inheritance, whether induced at the property-wide level or the individual level, study the following example:
595
+
596
+ A.cascade do
597
+ servers :inherit => true
598
+ users
206
599
  end
207
600
 
208
- will do the trick.
601
+ A.servers = ["rails", "sinatra"]
602
+ A.users = [:bigfoot, :charlie]
209
603
 
210
- In either case, even if you ```extend CC```, all instance variables of all descendents will have the ```hair_color``` property.
604
+ p A.servers # => ["rails", "sinatra"]
605
+ p B.servers # => ["rails", "sinatra"]
606
+ p B.servers false # => []
607
+ p B.servers true # => ["rails", "sinatra"]
608
+ p C.servers # => ["rails", "sinatra"]
609
+ p C.serverse false # => []
610
+ p C.servers true # => ["rails", "sinatra"]
211
611
 
212
- ## Custom getters and setters
612
+ p A.users # => [:bigfoot, :charlie]
613
+ p B.users # => []
614
+ p B.users false # => []
615
+ p B.users true # => [:bigfoot, :charlie]
616
+ p C.users # => []
617
+ p C.users false # => []
618
+ p C.users true # => [:bigfoot, :charlie]
213
619
 
214
- The easiest way to apply custom names for getting and setting a property is with the :getter/:setter options.
620
+ ## Proc Properites
215
621
 
216
- Parent.cascade do
217
- eye_color :start => "brown", :getter => :eyes, :setter => :eyes=
622
+ Properties set to a proc are invoked on each property call. This makes it simple to create complex property dependencies. Note that the concept of property dependencies is indepenent from the concept of class hierarchies (and property inheritance). A tree can be a single node (no inheritance) and yet contain a complex web of dependencies. You can create a pyramid of property dependencies all on one class.
623
+
624
+ ### two types of proc properties
625
+
626
+ This library distinguishes between two kinds of proc properties: First, the properties of type ```:Proc```, whose initial value has class: ```Proc```.
627
+
628
+ Then there are properties defined with some other type, but with a descendent that 'sets' the property to one. This makes properties very flexible. You can set the top-level ancestor to some basic value and have a descendent 'redefine' that property for itself and its descendents in a more complicated, state-dependent way.
629
+
630
+ ### Proc Syntax
631
+
632
+ Procs take (up to) two parameters: the first is the object calling the property, the second is an array of ancestors.
633
+
634
+ ### properties of type: ```:Proc```
635
+
636
+ Consider the following example:
637
+
638
+ A.cascade do
639
+ weather :default => :sunny
640
+ mood :default => Proc.new{|me| me.weather == :sunny ? "happy" : "miserable"}
641
+ color :default => Proc.new{|me| me.mood == "happy" ? "rosy" : "blue"}
218
642
  end
219
- Child.eyes = "blue"
643
+
644
+ In this case, the color depends on the mood and the mood depends on the weather.
645
+
646
+ p A.weather # => :sunny
647
+ p A.mood # => "happy"
648
+ p A.color # => "rosy"
649
+
650
+ Changing the weather to ```rainy``` will be reflected automatically in calls to ```mood``` and ```color```:
651
+
652
+ A.weather = :rainy
220
653
 
221
- now you cannot use ```:eye_color``` or ```:eye_color=``` methods to get or set the property.
654
+ p A.mood # => "miserable"
655
+ p A.color # => "blue"
656
+
657
+ Properties of type ```:Proc``` inherit their proc value from the nearest parent when blank. The proc itself, not its evaluation, is borrowed from some parent class and then evaluated in the descendent's own context.
658
+
659
+ In this case, this just means that changing the weather on B, changes its mood and color.
660
+
661
+ A.weather = :sunny
662
+ B.weather = :rainy
663
+
664
+ p A.mood # => "happy"
665
+ p B.mood # => "miserable"
666
+
667
+ p A.color # => "rosy"
668
+ p B.color # => "blue"
669
+
670
+ In this case, B graps the mood proc from A and evaluates it in its own context. The same goes for the color proc. Again, just remember that only blank properties are inherited (unless such behavior is otherwise overriden).
222
671
 
223
- p Parent.eyes # => "brown"
224
- p Parent.eye_color # => NoMethodError
672
+ Let's give B its own mood proc.
225
673
 
226
- p Child.eyes # => "blue"
227
- p GrandChild.eyes # => "blue"
674
+ B.mood = Proc.new{|me, parents|
675
+ parents.first.mood == "miserable" ? "happy" : "miserable"
676
+ }
228
677
 
229
- You can also have more than one getter or setter function
678
+ B's mood now depends on the mood of its nearest parent. It is irrelevant what the weather is, only its parent's mood matters. When A is happy, B is misearble and when A is miserable, B is happy.
230
679
 
231
- Parent.all! do
232
- has_hair :begin => false, :getters => [:has_hair?, :hair?]
680
+ A.weather = :sunny
681
+ B.weather = :sunny
682
+
683
+ p A.mood # => "content"
684
+ p B.mood # => "miserable"
685
+
686
+ A.weather = :rainy
687
+
688
+ p A.mood # => "miserable"
689
+ p B.mood # => "happy"
690
+
691
+ Now since procs are inherited and C has not set its mood property, note what it means for C to 'inherit' B's mood proc:
692
+
693
+ p A.mood # => "miserable"
694
+ p B.mood # => "happy"
695
+ p C.mood # => "miserable"
696
+
697
+ A.weather = :sunny
698
+
699
+ p A.mood # => "happy"
700
+ p B.mood # => "miserable"
701
+ p C.mood # => "happy"
702
+
703
+ C uses B's mood property. The proc decides the mood based on the first parent's mood. Hence, since B is C's parent, C's mood is always the opposite of B's.
704
+
705
+ Take a step back and consider what has been accomplished. For some classes, the mood property depends one's own value for the weather. For others, it depends only on the mood of a parent.
706
+
707
+ ### don't forget about ':default => true'
708
+
709
+ Proc inheritance is a powerful concept but it may not always be what you want. To manually override it, don't forget that you can always revert to the global default proc (the one defined in the ```cascade``` call):
710
+
711
+ B.weather = :sunny
712
+
713
+ A.mood # => "happy"
714
+ B.mood :default # => "happy"
715
+ C.mood :default # => "happy"
716
+
717
+ ### blank proc properties: manual override
718
+
719
+ Overriding inheritance in favor of the global default is so useful for procs that whenever you'd expect a blank property to return its blank value, it will instead invoke the default. What would you expect a proc property that is blank to return when forced to return its own value anyways? It doesn't make sense to return ```nil``` when some proc was expected to be called.
720
+
721
+ Consider the following example:
722
+
723
+ A.weather = :sunny
724
+ B.weather = nil
725
+ C.weather = nil
726
+
727
+ A.mood = Proc.new{ "angry" }
728
+ B.mood = nil
729
+ C.mood = nil
730
+
731
+ We've forced the ```weather``` and ```mood``` properties on B and C to be blank:
732
+
733
+ B.weather_is_blank? # => true
734
+ B.mood_is_blank? # => true
735
+
736
+ typical calls to B's mood evalutes A's new proc:
737
+
738
+ B.mood # => "angry"
739
+
740
+ notice how many ways we can instead force it to evaluate the default proc:
741
+
742
+ B.mood :default # => "happy"
743
+ B.mood :inherit => false # => "happy"
744
+ B.mood :inherit => 0 # => "happy"
745
+ B.mood false # => "happy"
746
+ B.mood nil # => "happy"
747
+ B.mood 0 # => "happy"
748
+
749
+ when proc inheritance is overriden, it reverts to the default
750
+
751
+ ### blank proc properties: property-wide override
752
+
753
+ You can get the same effect at the property-wide level simply by ensuring proc's don't inherit at all:
754
+
755
+ A.cascade do
756
+ mood :default => Proc.new{|me| me.weather == :sunny ? "content" : "depressed"}, :inherit => false
757
+ end
758
+
759
+ A.weather = :sunny
760
+ B.weather = C.weather = nil # set the others to blank
761
+
762
+ A.mood # => "content"
763
+ B.mood # => "content"
764
+ C.mood # => "content"
765
+
766
+ Now, any descendent that redefines the mood cannot affect further downstream descendents:
767
+
768
+ B.mood = Proc.new{ "angry" }
769
+
770
+ A.mood # => "content"
771
+ B.mood # => "angry"
772
+ C.mood # => "content"
773
+
774
+ ### overridding the override
775
+
776
+ Of course you can force a descendent to inherit the proc:
777
+
778
+ C.mood # => "content"
779
+ C.mood true # => "angry"
780
+
781
+ ### summary of procs
782
+
783
+ All you need to remember is that proc properties inherit their proc by default but that blank properties use the default proc (if given).
784
+
785
+ ### procs not of type ':Proc'
786
+
787
+ You can also set a property to a proc even if it has a different type. This is useful if the property is to be static for the parent, but dynamic for some descendent(s). Consider the following:
788
+
789
+ A.cascade do
790
+ ingredients :default => Set.new([:sugar, :butter, :flour, :eggs]), :inherit => true
791
+ end
792
+
793
+ B.cascade do
794
+ diabetic :default => false
795
+ end
796
+
797
+ B.ingredients = Proc.new do |me, parents|
798
+ me.diabetic ? parents.first.ingredients.dup.delete(:sugar) << :splenda :
799
+ parents.first.ingredients.dup.delete(:splenda) << :sugar
800
+ end
801
+
802
+ In this case, the ingredients property is of type ```:Set```. It is still a set even when B redefines it using a proc. Property types don't change once defined. But by 'setting' it to a proc, B and its descendents (since ```:inherit => true```) get a list of ingredients that changes based on the diabetic property.
803
+
804
+ class D < C; end
805
+
806
+ C.diabetic = true
807
+ D.diabetic = false
808
+
809
+ p B.diabetic # => false
810
+ p C.diabetic # => true
811
+ p D.diabetic # => false
812
+
813
+ p B.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
814
+ p C.ingredients.to_a # => [:splenda, :butter, :flour, :eggs]
815
+ p D.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
816
+
817
+ To illustrate this further, let's add another property to A. Let's give A and its descendents a sweet_tooth property, which takes either true or false. ```A``` sets its value to false, but his immediate descendent, B, decides that having a sweet tooth should depend upon whether one uses sugar:
818
+
819
+ A.cascade do
820
+ sweet_tooth :default => false
821
+ end
822
+
823
+ B.sweet_tooth = Proc.new{|me| me.ingredients.include? :sugar }
824
+
825
+ B.diabetic = false
826
+ C.diabetic = true
827
+ D.diabetic = false
828
+
829
+ p A.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
830
+ p B.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
831
+ p C.ingredients.to_a # => [:splenda, :butter, :flour, :eggs]
832
+ p D.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
833
+
834
+ p A.sweet_tooth # => false
835
+ p B.sweet_tooth # => true
836
+ p C.sweet_tooth # => false
837
+ p D.sweet_tooth # => true
838
+
839
+ Obviously the sweet_tooth property depends on the diabetic property, but it does so through the ingredients property for every class except A. This is an indirect dependency. That's the point. You can construct a class hierachy (read: template) having a core collection of properties with simple dependencies and then layer on more complicated properties (read: extra functionality) as needed.
840
+
841
+ Notice that the previous example requires ```:inherit => true``` on the initial ingredients definition:
842
+
843
+ A.cascade do
844
+ ingredients :default => Set.new([:sugar, :butter, :flour, :eggs]), :inherit => true
845
+ end
846
+
847
+ Again, this is because container types don't inherit by default. And that's also why the sweet_tooth property didn't need the same treatment: it's not a type of container.
848
+
849
+ ## blocks
850
+
851
+ You can pass a block to any property call. The block takes two parameters: the property value and an array of parents. Notice the subtle difference between block parameters and proc parameters: whereas procs are given the __object__ itself as the first parameter, blocks are given the __value__ of the property for that object.
852
+
853
+ This makes it easy to obtain a property's ancestors relative to any node:
854
+
855
+ A.cascade do
856
+ color :default => "red"
857
+ end
858
+ B.color = "blue"
859
+ C.color = "white"
860
+
861
+ parents = C.color{|my_color, parents| parents}
862
+
863
+ p parents # => [B, A]
864
+
865
+ For another example, consider the following:
866
+
867
+ A.cascade do
868
+ location :default => :urban
869
+ trendy :default => Proc.new{|me| me.location == :urban}
233
870
  end
234
- Child.has_hair = true
235
871
 
236
- p Parent.has_hair? # => false
237
- p Parent.hair? # => false
872
+ B.location = :rural
873
+ C.location = :urban
238
874
 
239
- p Child.has_hair? # => true
240
- p Child.hair? # => true
875
+ blk = Proc.new do |is_trendy, parents|
876
+ puts "my ancestors are: #{parents} and I am #{is_trendy ? '' : 'not'} trendy"
877
+ end
878
+
879
+ p A.trendy &blk # => "my ancestors are: [] and I am trendy"
880
+ p B.trendy &blk # => "my ancestors are: [A] and I am not trendy"
881
+ p C.trendy &blk # => "my ancestors are: [B, A] and I am trendy"
882
+
883
+ By making the ancestor chain available as a parameter, a block become a powerful tool. One particular case they come in handy is when merging a property's values across ancestors. For example, let's create a ```foods``` property and a block that concatenates a node's foods with its ancestors:
884
+
885
+ A.cascade do
886
+ foods :default => [:pasta, :tomatoes]
887
+ end
888
+
889
+ B.foods << :apples << :walnuts
890
+
891
+ C.foods << :chips
892
+
893
+ all = Proc.new do |my_foods, parents|
894
+ my_foods + parents.inject([]){|r, parent| r += parent.foods}
895
+ end
896
+
897
+ p A.foods # => [:pasta, :tomatoes]
898
+ p B.foods # => [:apples, :walnuts]
899
+ p C.foods # => [:chips]
900
+
901
+ p A.foods &all # => [:pasta, :tomatoes]
902
+ p B.foods &all # => [:apples, :walnuts, :pasta, :tomatoes]
903
+ p C.foods &all # => [:chips, :apples, :walnuts, :pasta, :tomatoes]
904
+
905
+ For a second example, let's create an options property and a block to merge all options giving precedence to the child:
906
+
907
+ A.cascade do
908
+ options :hash => true
909
+ end
910
+
911
+ A.options = {:color => "black", :music => ["Rolling Stones"]}
912
+ B.options[:music] = ["Arcade Fire"]
913
+ C.options = {:color => "blond"}
914
+
915
+ merged = Proc.new do |my_opts, parents|
916
+ parents.reverse.inject({}){|r, child|
917
+ r.merge(child.options)
918
+ }.merge(my_opts)
919
+ end
920
+
921
+ p A.options # => {:color => "black", :music => ["Rolling Stones"]}
922
+ p B.options # => {:music => ["Arcade Fire"]}
923
+ p C.options # => {:color => "blond"}
924
+
925
+ p A.options &merged # => {:color => "black", :music => ["Rolling Stones"]}
926
+ p B.options &merged # => {:color => "black", :music => ["Arcade Fire"]}
927
+ p C.options &merged # => {:color => "blond", :music => ["Arcade Fire"]}
928
+
929
+ If you find yourself operating on a node's parents often, you can also create a new proc property to do it for you:
930
+
931
+ A.cascade do
932
+ opts :default => {:color => "black", :music => ["Rolling Stones"]}
933
+ merged_opts :proc => true
934
+ end
935
+
936
+ A.merged_opts = Proc.new do |me, parents|
937
+ parents.reverse.inject({}){|r, child|
938
+ r.merge(child.opts)
939
+ }.merge(me.opts)
940
+ end
941
+
942
+ B.options[:music] = ["Arcade Fire"]
943
+ C.options[:color] = "blond"
944
+
945
+ p A.merged_opts # => {:color => "black", :music => ["Rolling Stones"]}
946
+ p B.merged_opts # => {:color => "black", :music => ["Arcade Fire"]}
947
+ p C.merged_opts # => {:color => "blond", :music => ["Arcade Fire"]}
948
+
949
+ ## instances
950
+
951
+ Everything applies equally to class instances except for a few caveats. To actually operate on instances you must either ```include``` the library (instead of ```extend```) or specify ```:instances => true``` for each property inside the ```cascade``` call.
952
+
953
+ class A
954
+ include CC
955
+ end
956
+
957
+ A.cascade do
958
+ colors :default => [:red, :blue, :green]
959
+ end
960
+
961
+ or
962
+
963
+ class A
964
+ extend CC
965
+ end
966
+
967
+ A.cascade do
968
+ colors :default => [:red, :blue, :green], :instances => true
969
+ end
970
+
971
+ Instances are treated as child nodes. For instance
972
+
973
+ A.cascade do
974
+ color :default => "red"
975
+ end
976
+
977
+ a = A.new
978
+ b = B.new
979
+
980
+ p B.color{|color, parents| parents} # => [A]
981
+ p a.color{|color, parents| parents} # => [A]
982
+
983
+ p C.color{|color, parents| parents} # => [B, A]
984
+ p b.color{|color, parents| parents} # => [B, A]
985
+
986
+ It is important to note that instances aren't as flexible as classes since they cannot be the parent of another node. For instance, B is a child of A, but it is also the parent of C. Every instance of B is a child and only a child of B. Keep this in mind when implementing your hierarchy.
987
+
988
+ Other than that, you can operate on instances just as you would any other child class:
989
+
990
+ A.cascade do
991
+ name :default => "George"
992
+ colors :default => [:green, :yellow, :purple]
993
+ end
994
+
995
+ a = A.new
996
+ b = B.new
997
+
998
+ p A.name # => "George"
999
+ p a.name # => "George" (inherits from A)
1000
+ p B.name # => "George" (inherits from A)
1001
+ p b.name # => "George" (inherits from A)
1002
+
1003
+ A.name = "Jefferson"
1004
+ B.name = "Sam"
1005
+
1006
+ p B.name # => "Sam"
1007
+ p b.name # => "Sam" (inherits from B)
1008
+
1009
+ b.name = "Timothy"
1010
+ p b.name # => "Timothy" (own)
1011
+
1012
+ p A.name :default # => "George"
1013
+ p a.name :default # => "George"
1014
+ p B.name :default # => "George"
1015
+ p b.name :default # => "George"
241
1016
 
242
- While not recommended, you can also apply custom getter/setter name in the array style syntax.
1017
+ a.colors << :blue << :white
243
1018
 
244
- Parent.cascade [:has_hair, false, true, [:has_hair?, :hair?]]
245
-
246
- this initializes the ```has_hair``` property on ```Parent``` to false and sets the getter functions to ```:has_hair?``` and ```:hair?```. Recall that the third argument in the block explicitly sets whether class instances are inherit the property.
1019
+ B.colors << :pink << :black
1020
+ b.colors << :black
247
1021
 
248
- If you wanted to apply a custom setter, it would be the fifth argument:
1022
+ p A.colors # => [:green, :yellow, :purple]
1023
+ p a.colors # => [:blue, :white]
1024
+ p B.colors # => [:pink, :black]
1025
+ p b.colors # => [:black]
249
1026
 
250
- Parent.cascade [:has_hair, false, true, :hair?, :hair!]
1027
+ ## Custom Classes
251
1028
 
252
- Compare how much worse that looks compared to the block syntax:
1029
+ Properties can be any kind of object. To work with other class types you will need to define "blank" values and "new" values.
253
1030
 
254
- Parent.cascade do
255
- hair_color :default => false, :instances => true, :getter => :hair?, :setter => :hair!
1031
+ Let's implement a custom type that is a sublcass of Hash
1032
+
1033
+ class MyCustom < Hash
1034
+ def initialize(*arr)
1035
+ return super unless arr.size > 0
1036
+ [*arr].flatten.each_slice(2){|k,v| self[k] = v}
1037
+ end
256
1038
  end
1039
+
1040
+ This gives us some flexible ways to initialize a hash:
1041
+
1042
+ c1 = MyCustom.new [[:name, "Gabe"], [:profession, "student"]]
1043
+ c2 = MyCustom.new [:name, "Gabe"], [:profession, "student"]
1044
+ c3 = MyCustom.new :name, "Gabe", :profession, "student"
1045
+
1046
+ p c1 # => {:name => "Gabe", :profession => "student"}
1047
+ p c2 # => {:name => "Gabe", :profession => "student"}
1048
+ p c3 # => {:name => "Gabe", :profession => "student"}
1049
+
1050
+ To incorporate MyCustom into a class hierarchy, we'll do the obvious thing:
1051
+
1052
+ A.cascade
1053
+ custom :default => c1,
1054
+ :inherit => false,
1055
+ :blank => lambda{|v| v.size.zero?},
1056
+ :new => lambda{ MyCustom.new }
1057
+ end
1058
+
1059
+ We didn't have to specify the inheritance since MyCustom is an ancestor of Enumerable, making it a container. It doesn't hurt to overspecify.
1060
+
1061
+ It helps to verify that your ```blank``` and ```new``` proc work as expected and that the default value is correct using the metadata hash:
1062
+
1063
+ props = A.singleton_class.properties
1064
+
1065
+ c = MyCustom.new
1066
+
1067
+ p props[:custom][:new].call == c # => true
1068
+ p props[:custom[:blank].call(c) # => true
1069
+ p props[:custom][:type] # => :MyCustom
1070
+ p props[:custom][:default] == c1 # => true
1071
+
1072
+ Now you can use the property anywhere in the class tree:
1073
+
1074
+ B.custom # => {}
1075
+ B.custom(:default) # => {:name => "Gabe", :profession => "student"}