cascading_classes 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,17 +20,17 @@ describe "options" do
20
20
  describe "proc values are called alone or evaluated with instance exec" do
21
21
  it "has a global default" do
22
22
  CC.proc_inst_exec = true
23
- CC.proc_inst_exec?.must_equal true
23
+ CC.proc_inst_exec?.should == true
24
24
 
25
25
  CC.proc_inst_exec = false
26
- CC.proc_inst_exec?.must_equal false
26
+ CC.proc_inst_exec?.should == false
27
27
  end
28
28
 
29
29
  it "defaults to global setting" do
30
30
  props = A.cascade do
31
31
  color
32
32
  end
33
- props[:color][:proc_inst_exec].must_equal(CC.proc_inst_exec?)
33
+ props[:color][:proc_inst_exec].should ==(CC.proc_inst_exec?)
34
34
  end
35
35
 
36
36
  it "can be set in the 'cascade' block, overruling the global default" do
@@ -38,7 +38,7 @@ describe "options" do
38
38
  props = A.cascade do
39
39
  color :proc_inst_exec => false
40
40
  end
41
- props[:color][:proc_inst_exec].must_equal false
41
+ props[:color][:proc_inst_exec].should == false
42
42
  end
43
43
 
44
44
  it "can be set in 'opts' hash of property method, overruling others" do
@@ -50,11 +50,11 @@ describe "options" do
50
50
 
51
51
  B.color = Proc.new{|me, parents| who_is_self = self; :orange}
52
52
 
53
- B.color.must_equal :orange
54
- who_is_self.must_equal(outside_context)
53
+ B.color.should == :orange
54
+ who_is_self.should ==(outside_context)
55
55
 
56
- B.color(:undef, :undef, {:proc_inst_exec => true}).must_equal :orange
57
- who_is_self.must_equal(B)
56
+ B.color(:undef, :undef, {:proc_inst_exec => true}).should == :orange
57
+ who_is_self.should ==(B)
58
58
  end
59
59
  end
60
60
  end
data/todo CHANGED
@@ -1,3 +1,24 @@
1
+ tests
2
+ spec/basics/inherit_spec.rb
3
+
4
+ feature??
5
+ new method: first_parent OR direct_parent ??
6
+ will return ancestor_chain.first
7
+
8
+ Bug ??
9
+ Proc property descendent on non Proc ancestor doesn't inherit behavior ??
10
+ example:
11
+ class A; extend CC; end
12
+ B = Class.new A
13
+ C = Class.new B
14
+ A.cascade{ width default: 100 }
15
+ B.width = Proc.new{|me, parents| parents.last.width + 10}
16
+ p B.width # => 110 (good)
17
+ p C.width # => 110 (is this correct?)
18
+
19
+ Bug
20
+ ancestor_chain doesn't work on instances
21
+
1
22
  Bug
2
23
  to_hash not working for objects
3
24
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cascading_classes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -18,7 +18,6 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - README.md
21
- - README-part-ii.md
22
21
  - Rakefile
23
22
  - todo
24
23
  - lib/cascading_classes.rb
data/README-part-ii.md DELETED
@@ -1,1020 +0,0 @@
1
- ## WORK IN PROGESS ...
2
-
3
-
4
-
5
- Update the properties on A's desendents:
6
-
7
- B.name = "Charles"
8
- C.name = "Sam"
9
-
10
- C.city = "Rochester"
11
-
12
- Collect property names and values:
13
-
14
- p A.to_hash # => {:name => "Franklin", :city => "New York", :state => "NY"}
15
- p B.to_hash # => {:name => "Charles", :city => "New York", :state => "NY"}
16
- p C.to_hash # => {:name => "Sam", :city => "Rochester", :state => "NY"}
17
-
18
- pass 'false' for only those properties that were specifically set:
19
-
20
- p A.to_hash(false) # => {:name => "Franklin", :city => "New York", :state => "NY"}
21
- p B.to_hash(false) # => {:name => "Charles"}
22
- p C.to_hash(false) # => {:name => "Sam", :city => "Rochester"}
23
-
24
- 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
25
-
26
- ### ...more...
27
-
28
- Add a second child to A based out of Boston:
29
-
30
- B2 = Class.new(A) # same as: class B2 < A; end
31
-
32
- B2.name = 'Thomas'
33
- B2.city = 'Boston'
34
- B2.state = 'MA'
35
-
36
- Descendents of B2 will inherit these new values unless set.
37
-
38
- class C2 < B2; end
39
-
40
- p C2.city # => Boston
41
- p C2.state # => MA
42
-
43
- Now we have three properties on five nodes spanning three generations.
44
-
45
- Let's add two more properties; these will depend on one's location:
46
-
47
- A.cascade do
48
- baseball_team :default => proc{|me| me.state == 'NY' ? "Yankees" : "Red Sox"}
49
- basketball_team :default => proc{|me| me.state == 'NY' ? "Knicks" : "Celtics"}
50
- end
51
-
52
- 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.
53
-
54
- Watch how this works for our five node, three generation setup. Notice how each proc (read: behavior) cascades down the class hierarchy:
55
-
56
- p A.baseball_team # => "Yankees"
57
- p B.baseball_team # => "Yankees"
58
- p C.baseball_team # => "Yankees"
59
-
60
- p B2.baseball_team # => "Red Sox"
61
- p C2.baseball_team # => "Red Sox"
62
-
63
- If we change C2's location, its teams should follow suit:
64
-
65
- C2.city = "New York"
66
- C2.state = "NY"
67
-
68
- p C2.baseball_team # => "Yankees"
69
- p C2.basketball_team # => "Knicks"
70
-
71
- Any new node added to the mix should be "born" with the same behavior:
72
-
73
- class D < C; end
74
-
75
- p D.state # => "NY"
76
- p D.baseball_team # => "Yankees"
77
-
78
- D.state = "MA"
79
- p D.baseball_team # => "Red Sox"
80
-
81
- 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.
82
-
83
- Finally, let's add a child to C2, located in Los Angeles, and rewrite the sports properties to fit our bulging world:
84
-
85
- class D2 < C2
86
- end
87
-
88
- D2.city = "Los Angeles"
89
- D2.state = "CA"
90
-
91
- A.baseball_team = Proc.new do |me|
92
- case me.state
93
- when "NY" then "Yankees"
94
- when "MA" then "Red Sox"
95
- when "CA" then "Dodgers"
96
- else ''
97
- end
98
- end
99
-
100
- A.basketball_team = Proc.new do |me|
101
- case me.state
102
- when "NY" then "Knicks"
103
- when "MA" then "Celtics"
104
- when "CA" then "Lakers"
105
- else ""
106
- end
107
- end
108
-
109
- We now in charge of seven nodes spanning four generations.
110
-
111
- To further showcase proc properties, let's create a ```teams``` property that is a composite of one's favorite teams:
112
-
113
- A.cascade do
114
- teams :default => Proc.new{|me|
115
- [me.baseball_team, me.basketball_team]
116
- }
117
- end
118
-
119
- test it out
120
-
121
- p A.teams # => ["Yankees", "Knicks"]
122
- p B.teams # => ["Yankees", "Knicks"]
123
- p C.teams # => ["Yankees", "Knicks"]
124
- p D.teams # => ["Yankees", "Knics"]
125
- p B2.teams # => ["Red Sox", "Celtics"]
126
- p C2.teams # => ["Red Sox", "Celticks"]
127
- p D2.teams # => ["Dodgers", "Lakers"]
128
-
129
- now we can move people around and expect their teams to follow suit:
130
-
131
- D.state = "CA"
132
- D.city = "Los Angeles"
133
-
134
- p D.teams # => ["Dodgers", "Lakers"]
135
-
136
- ### top it off with a block
137
-
138
- 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:
139
-
140
- p D2.teams do |val, parents|
141
- puts "D2: #{val.inspect}"
142
- parents.each{|parent| puts "#{parent}: #{parent.teams.inspect}"}
143
- end
144
-
145
- # => D2: ["Dodgers", "Lakers"]
146
- C2: ["Red Sox", "Celtics"]
147
- B2: ["Red Sox", "Celtics"]
148
- A: ["Yankees", "Knicks"]
149
-
150
- ### quickly summarized
151
-
152
- Even simple hierarchies can turn into complex beasts in need of taming. To use this library in your projects:
153
-
154
- * require the library: ```require 'cascading_classes'```
155
- * extend or include your class with CascadingClasses or its synonym: CC
156
- * call ```cascade``` on a parent class
157
- * wrap your properties and their defaults in block
158
- * get and set properties on any node in the hierachy
159
- * add extra functionality with derived properties using procs
160
- * pass a block to easily iterate over a property's parents
161
-
162
- # In Depth:
163
-
164
- Assume a simple three node, three generation setup:
165
-
166
- class A
167
- include CC
168
- end
169
-
170
- class B < A
171
- end
172
-
173
- class C < B
174
- end
175
-
176
- 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.
177
-
178
- 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.
179
-
180
- ### basic types: strings, symbols, numbers, booleans, ...
181
-
182
- Using basic types is as it should be, dead simple:
183
-
184
- A.cascade do
185
- score :default => 45.2
186
- rank :default => 4
187
- color :default => "red"
188
- direction :default => :south
189
- available :default => true
190
- end
191
-
192
- 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, it sets the score property on A to 45.2, the rank property on A to 4, the color property on A to "red"... This is in addition to setting global, fixed defaults for those properties.
193
-
194
- ### defaults
195
-
196
- To access the default, pass ':default' to any descendent in the property call
197
-
198
- p A.score :default # => 45.2
199
- p B.score :default # => 45.2
200
- p C.score :default # => 45.2
201
-
202
- notice how the property can change, but the default stays the same:
203
-
204
- A.score = 19.2
205
- B.score = 22.1
206
- C.score = 39.7
207
-
208
- p A.score # => 19.2
209
- p B.score # => 22.1
210
- p C.score # => 39.7
211
-
212
- p A.score :default # => 45.2
213
- p B.score :default # => 45.2
214
- p C.score :default # => 45.2
215
-
216
- ### one essential rule
217
-
218
- The basic rule is that non-container properties inherit their value (when blank) by default. The definition of blank can be redefined, but is preset with reasonable values: empty string for ':String', nil for ':Float' and ':Fixnum' and symbols. Also, unset properties are usually blank.
219
-
220
- p A.score # => 19.2
221
- p B.score # => 22.1
222
- p C.score # => 39.7
223
-
224
- B.score = nil
225
- C.score = nil
226
-
227
- p B.score # => 19.2
228
- p C.score # => 19.2
229
-
230
- 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 the same thing for the ```rank``` property for nodes B and C except that it wasn't explicitly set to a blank value, but rather, being unset, defaulted to one:
231
-
232
- p A.rank # => 4
233
- p B.rank # => 4
234
- p C.rank # => 4
235
-
236
- The way this works is that when you ask B for its ```rank```, it asks itself whether its blank. If it is, it proxies to its nearest parent, otherwise it returns the 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:
237
-
238
- B.rank = 12
239
-
240
- p A.rank # => 4
241
- p B.rank # => 12
242
- p C.rank # => 12
243
-
244
- Of course, this cascading behavior may not be what you want. You can explicitly control whether a property can inherit by passing an argument in the property call:
245
-
246
- p A.score # => 19.2
247
- p B.score false # => nil
248
- p C.score true # => 19.2
249
-
250
- 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, which it duly does as it is.
251
-
252
- It's important to realize that any property that is not blank will never inherit no matter what arguments you pass (except if the argument is ':default').
253
-
254
- C.score = 17.4
255
-
256
- p A.score(true) # => 19.2
257
- p A.score(false) # => 19.2
258
- p C.score(true) # => 17.4
259
- p C.score(false) # => 17.4
260
-
261
- Here, the ```score``` property on A and C is not blank so any call to the property, regardless of the argument passed, returns its value.
262
-
263
- ### single example inheritance summary
264
-
265
- For a brief summarry of inheritance for noncontainers consider this example:
266
-
267
- A.color = "white"
268
- p A.color # => "white"
269
- p B.color # => "white"
270
- p C.color # => "white"
271
-
272
- A.color = "blue"
273
- p A.color # => "blue"
274
- p B.color # => "blue"
275
- p C.color # => "blue"
276
-
277
- B.color = "orange"
278
-
279
- p A.color # => "blue"
280
- p B.color # => "orange"
281
- p C.color # => "orange"
282
-
283
- 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 has to reach two generations higher to find a nonblank value. It returns B's instead.
284
-
285
- ### '[propname]_is_blank?'
286
-
287
- For each cascaded property, an extra method is added to every node in the hierarchy that returns whether the property is blank:
288
-
289
- A.score = 87.4
290
- B.score = C.score = nil
291
-
292
- p A.score_is_blank? # => false
293
- p B.score_is_blank? # => true
294
- p C.score_is_blank? # => true
295
-
296
- C.score = 18.2
297
- p A.score_is_blank? # => false
298
- p B.score_is_blank? # => true
299
- p C.score_is_blank? # => false
300
-
301
- p A.color_is_blank? # => false
302
- p B.color_is_blank? # => false
303
- p C.color_is_blank? # => true
304
-
305
- For good measure, notice inheritance at work for the other properties.
306
-
307
- B.available = false
308
- C.color = "purple"
309
-
310
- p C.color # => "purple" (own value)
311
- p C.rank # => 12 (inherits from B)
312
- p C.direction # => :south (inherits from A)
313
- p C.available # => false (inherits from B)
314
-
315
- ### more on defaults
316
-
317
- Each property can have a default value which is set in the ```cascade``` call. Default values are static values and can be referenced at any time by any node in the tree.
318
-
319
- Recall that the side effect of including a default value is that the parent property gets set to that value. Let's create a ```city``` property that defaults to "seatle"
320
-
321
- A.cascade do
322
- city :default => "seatle"
323
- end
324
-
325
- p A.city_is_blank? # => false
326
-
327
- p A.city # => "seatle"
328
- p B.city # => "seatle" (inherits from A)
329
-
330
- A.city = "austin"
331
- B.city = "los angeles"
332
-
333
- p A.city :default # => "seatle"
334
- p B.city :default # => "seatle"
335
- p C.city :default # => "seatle"
336
-
337
- Notice how adding the ':default' argument returns the property-wide default regardless of the node making the call.
338
-
339
- ### types
340
-
341
- Defaults are not required, but if not given, the type of the property won't be known until it is first assigned.
342
-
343
- props = A.cascade do
344
- space_free
345
- color
346
- end
347
-
348
- p props[:space_free][:type] # => :undefined_type
349
- A.space_free = 328.12
350
- p props[:space_free][:type] # => :Float
351
-
352
- p props[:color][:type] # => :undefined_type
353
- A.color = "red"
354
- p props[:color][:type] # => :String
355
-
356
- 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.
357
-
358
- 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.
359
-
360
- ### set the type manually
361
-
362
- 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:
363
-
364
- props = A.cascade do
365
- name :Hash => true
366
- address :hash => true
367
- city :String => true
368
- area :fixnum => true
369
- zip :Fixnum => true
370
- end
371
-
372
- p props[:name][:type] # => :Hash
373
- p props[:color][:type] # => :Symbol
374
- p props[:address][:type] # => :Hash
375
- p props[:city][:type] # => :String
376
- p props[:area][:type] # => :Fixnum
377
- p props[:zip][:type] # => :Fixnum
378
-
379
- ## container types: arrays, hashes, sets, ...
380
-
381
- Container properties are those that implement ```each``` or include ```Enumerable```. The definition of "blank" for arrays, hashes and sets is pre-defined to be those that are empty.
382
-
383
- ### containers and inheritance: fundamental rule
384
-
385
- 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:
386
-
387
- A.cascade do
388
- urls :default => ["www.google.com", "www.nytimes.com"]
389
- name :default => {:first => 'jon', :last => 'smith'}
390
- end
391
-
392
- p A.urls # => ["www.google.com", "www.nytimes.com"]
393
- p B.urls # => []
394
- p C.urls # => []
395
-
396
- B.urls << "www.techcrunch.com" << "www.twitter.com"
397
- C.urls = ["www.bgr.com"]
398
-
399
- p A.urls # => ["www.google.com", "www.nytimes.com"]
400
- p B.urls # => ["www.techcrunch.com", "www.twitter.com"]
401
- p C.urls # => ["www.bgr.com"]
402
-
403
- p A.name # => {:first => 'jon', :last => 'smith'}
404
- p B.name # => {}
405
- p C.name # => {}
406
-
407
- B.name[:first] = "adam"
408
- B.name[:last] = "johnson"
409
- C.name = {:first => 'terry', :last => 'williamson'}
410
-
411
- p A.name # => {:first => 'jon', :last => 'smith'}
412
- p B.name # => {:first => 'adam', :last => 'johnson'}
413
- p C.name # => {:first => 'terry', :last => 'williamson'}
414
-
415
- ### container non inheritance override
416
-
417
- You can override the default lack of inheritance for containers 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):
418
-
419
- A.cascade do
420
- cities :default=> ["boston", "new york"], :inherit => true
421
- end
422
-
423
- p A.cities # => ["boston", "new york"]
424
- p B.cities # => ["boston", "new york"] (inherited from A)
425
- p C.cities # => ["boston", "new york"] (inherited from A)
426
-
427
- p A.cities_is_blank? # => false
428
- p B.cities_is_blank? # => true
429
- p C.cities_is_blank? # => true
430
-
431
- if you think this is useful, just take care not to trip over yourself
432
-
433
- B.cities << "london"
434
-
435
- p B.cities_is_blank? # => true
436
- p A.cities # => ["boston", "new york", "london"]
437
- p B.cities # => ["boston", "new york", "london"]
438
-
439
- 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.
440
-
441
- #### overriding the override
442
-
443
- 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:
444
-
445
- B.cities(:inherit => false) << "london"
446
-
447
- p A.cities # => ["boston", "new york"]
448
- p B.cities # => ["london"]
449
-
450
- you can also ommit the the ':inherit' bit
451
-
452
- B.cities = []
453
-
454
- p B.cities # ["boston", "new york"] (inherits from A)
455
- p B.cities false # [] (own value)
456
-
457
- B.cities(false) << "london"
458
-
459
- p B.cities # ["london"]
460
-
461
- ### manual inheritance
462
-
463
- This works in the opposite way too. Container properties don't inherit by default, but you can force the behavior on an indivudual basis:
464
-
465
- A.cascade do
466
- cuisine :default => [:italian, :french]
467
- end
468
-
469
- p A.cuisine # => [:italian, :french]
470
- p B.cuisine # => []
471
- p C.cuisine # => []
472
-
473
- p B.cuisine :inherit # => [:italian, :french]
474
- p C.cuisine :inherit # => [:italian, :french]
475
-
476
- The inherit argument, if given, 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:
477
-
478
- p C.cuisine :inherit => false # => [] (own value)
479
- p C.cuisine :inherit => nil # => [] (own value)
480
- p C.cuisine :inherit => 0 # => [] (own value)
481
- p C.cuisine :inherit => 1 # => [] (own value)
482
- p C.cuisine :inherit => 2 # => [:italian, :french] (inherits from A)
483
- p C.cuisine :inherit => 83 # => [:italian, :french] (inherits from A)
484
- p C.cuisine :inherit => -1 # => [:italian, :french] (inherits from A)
485
- p C.cuisine :inherit => true # => [:italian, :french] (inherits from A)
486
-
487
- you can ommit the ':inherit' syntax entirely:
488
-
489
- p C.cuisine
490
- p C.cuisine false # => []
491
- p C.cuisine nil # => []
492
- p C.cuisine 0 # => []
493
- p C.cuisine 1 # => []
494
- p C.cuisine 2 # => [:italian, :french]
495
- p C.cuisine 83 # => [:italian, :french]
496
- p C.cuisine -1 # => [:italian, :french]
497
- p C.cuisine true # => [:italian, :french]
498
- p C.cuisine 20 # => [:italian, :french]
499
-
500
- 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.
501
-
502
- ### metadata again
503
-
504
- One way to obtain the background metadata hash is by capturing the result of the ```cascade``` call:
505
-
506
- props = A.cascade do
507
- states :default => [:good, :bad, :disaster], :inherit => true
508
- colors :default => ["blue", "orange", "red"]
509
- end
510
-
511
- p props.keys # => [:states, :colors]
512
- p props[:states].keys # => [:default, :instances_too, :getters, :setters, ...]
513
-
514
- 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:
515
-
516
- props = A.singleton_class.properties
517
-
518
- ### metadata - check 'inheritance' status
519
-
520
- 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.
521
-
522
- p props[:states][:inherit] # => true
523
- p props[:colors][:inherit] # => false
524
-
525
- ### more on inheritance and max inherit age
526
-
527
- If you override the container inheritance default and add ```:inherit => true``` as part of the ```cascade``` call, each time the property is called, the inheritance age will be set to -1. That is, it will assume you want to inherit upwards until the first parent node with a nonblank value is. 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```).
528
-
529
- p A.states # => [:good, :bad, :disaster] (inherits from A)
530
- p B.states # => [:good, :bad, :disaster] (inherits from A)
531
- p C.states # => [:good, :bad, :disaster] (inherits from A)
532
-
533
- p A.colors # => ["blue", "orange", "red"] (own value)
534
- p B.colors # => [] (own value)
535
- p C.colors # => [] (own value)
536
-
537
- ### container behavior summary
538
-
539
- 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:
540
-
541
- A.cascade do
542
- servers :inherit => true
543
- users
544
- end
545
-
546
- A.servers = ["rails", "sinatra"]
547
- A.users = [:bigfoot, :charlie]
548
-
549
- p A.servers # => ["rails", "sinatra"]
550
- p B.servers # => ["rails", "sinatra"]
551
- p B.servers false # => []
552
- p B.servers true # => ["rails", "sinatra"]
553
- p C.servers # => ["rails", "sinatra"]
554
- p C.serverse false # => []
555
- p C.servers true # => ["rails", "sinatra"]
556
-
557
- p A.users # => [:bigfoot, :charlie]
558
- p B.users # => []
559
- p B.users false # => []
560
- p B.users true # => [:bigfoot, :charlie]
561
- p C.users # => []
562
- p C.users false # => []
563
- p C.users true # => [:bigfoot, :charlie]
564
-
565
- ## Proc Properites
566
-
567
- Properties set to a proc are invoked on each property call. This makes it easy to create complex property dependencies. Note that the concept of property depending on other properties is indepenent of the concept of class hierarchies (and property inheritance). You can have a tree with a single node (no inheritance) and yet contain a complex web of dependencies. There may be a pyramid of property dependencies stacked on the one class.
568
-
569
- ### two types of proc properties
570
-
571
- This library distinguishes between two kinds of proc properties: First, the properties of type ```:Proc```, whose initial value has class: ```Proc```.
572
-
573
- Then there are properties with another type, but with a descendent that 'sets' the property to a proc. 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.
574
-
575
- ### Proc Syntax
576
-
577
- Procs take (up to) two parameters: the first is the object calling the property, the second is an array of ancestors.
578
-
579
- ### properties of type: ```:Proc```
580
-
581
- Consider the following example:
582
-
583
- A.cascade do
584
- weather :default => :sunny
585
- mood :default => Proc.new{|me| me.weather == :sunny ? "happy" : "miserable"}
586
- color :default => Proc.new{|me| me.mood == "happy" ? "rosy" : "blue"}
587
- end
588
-
589
- In this case, the color depends on the mood and the mood depends on the weather. (Take class A by itself and you have an example of property dependencies without class hierarchies)
590
-
591
- p A.weather # => :sunny
592
- p A.mood # => "happy"
593
- p A.color # => "rosy"
594
-
595
- Changing the weather to ```rainy``` will be reflected automatically in calls to ```mood``` and ```color```:
596
-
597
- A.weather = :rainy
598
-
599
- p A.mood # => "miserable"
600
- p A.color # => "blue"
601
-
602
- 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.
603
-
604
- In this case, this just means that changing the weather on B, changes its mood and color.
605
-
606
- A.weather = :sunny
607
- B.weather = :rainy
608
-
609
- p A.mood # => "happy"
610
- p B.mood # => "miserable"
611
-
612
- p A.color # => "rosy"
613
- p B.color # => "blue"
614
-
615
- 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).
616
-
617
- Let's give B its own mood proc.
618
-
619
- B.mood = Proc.new{|me, parents|
620
- parents.first.mood == "miserable" ? "happy" : "miserable"
621
- }
622
-
623
- B's mood now depends not on its own weather property but on the mood of its first parent. When A is happy, B is misearble and when A is miserable, B is happy.
624
-
625
- A.weather = :sunny
626
- B.weather = :sunny
627
-
628
- p A.mood # => "content"
629
- p B.mood # => "miserable"
630
-
631
- A.weather = :rainy
632
-
633
- p A.mood # => "miserable"
634
- p B.mood # => "happy"
635
-
636
- 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:
637
-
638
- p A.mood # => "miserable"
639
- p B.mood # => "happy"
640
- p C.mood # => "miserable"
641
-
642
- A.weather = :sunny
643
-
644
- p A.mood # => "happy"
645
- p B.mood # => "miserable"
646
- p C.mood # => "happy"
647
-
648
- C grabs B's mood proc and evaluates it in its own context. Since the mood based on the first parent's mood and B is C's first parent, C's mood is always the opposite of B's, which in turn is always the opposite of A's mood.
649
-
650
- Take a step back and consider what has been accomplished. You have created a behavior that, for some nodes, depends on the weather property and for others instead depends on the node's parent. You can bring incredibly large inheritance trees to life with just a few properly-written behaviors.
651
-
652
- ### don't forget about ':default => true'
653
-
654
- 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):
655
-
656
- B.weather = :sunny
657
-
658
- A.mood # => "happy"
659
- B.mood :default # => "happy"
660
- C.mood :default # => "happy"
661
-
662
- ### blank proc properties: manual override
663
-
664
- 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.
665
-
666
- Consider the following example:
667
-
668
- A.weather = :sunny
669
- B.weather = nil
670
- C.weather = nil
671
-
672
- A.mood = Proc.new{ "angry" }
673
- B.mood = nil
674
- C.mood = nil
675
-
676
- We've forced the ```weather``` and ```mood``` properties on B and C to be blank:
677
-
678
- B.weather_is_blank? # => true
679
- B.mood_is_blank? # => true
680
-
681
- typical calls to B's mood evalutes A's new proc:
682
-
683
- B.mood # => "angry"
684
-
685
- notice how many ways we can instead force it to evaluate the default proc:
686
-
687
- B.mood :default # => "happy"
688
- B.mood :inherit => false # => "happy"
689
- B.mood :inherit => 0 # => "happy"
690
- B.mood false # => "happy"
691
- B.mood nil # => "happy"
692
- B.mood 0 # => "happy"
693
-
694
- when proc inheritance is overriden, it reverts to the default
695
-
696
- ### blank proc properties: property-wide override
697
-
698
- You can get the same effect at the property-wide level simply by ensuring proc's don't inherit at all:
699
-
700
- A.cascade do
701
- mood :default => Proc.new{|me| me.weather == :sunny ? "content" : "depressed"}, :inherit => false
702
- end
703
-
704
- A.weather = :sunny
705
- B.weather = C.weather = nil # set the others to blank
706
-
707
- A.mood # => "content"
708
- B.mood # => "content"
709
- C.mood # => "content"
710
-
711
- Now, any descendent that redefines the mood cannot affect further downstream descendents:
712
-
713
- B.mood = Proc.new{ "angry" }
714
-
715
- A.mood # => "content"
716
- B.mood # => "angry"
717
- C.mood # => "content"
718
-
719
- ### overridding the override
720
-
721
- Of course you can force a descendent to inherit the proc:
722
-
723
- C.mood # => "content"
724
- C.mood true # => "angry"
725
-
726
- ### summary of procs
727
-
728
- All you need to remember is that proc properties inherit their proc by default but that blank properties use the default proc (if given).
729
-
730
- ### procs not of type ':Proc'
731
-
732
- 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:
733
-
734
- A.cascade do
735
- ingredients :default => Set.new([:sugar, :butter, :flour, :eggs]), :inherit => true
736
- end
737
-
738
- B.cascade do
739
- diabetic :default => false
740
- end
741
-
742
- B.ingredients = Proc.new do |me, parents|
743
- me.diabetic ? parents.first.ingredients.dup.delete(:sugar) << :splenda :
744
- parents.first.ingredients.dup.delete(:splenda) << :sugar
745
- end
746
-
747
- 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.
748
-
749
- class D < C; end
750
-
751
- C.diabetic = true
752
- D.diabetic = false
753
-
754
- p B.diabetic # => false
755
- p C.diabetic # => true
756
- p D.diabetic # => false
757
-
758
- p B.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
759
- p C.ingredients.to_a # => [:splenda, :butter, :flour, :eggs]
760
- p D.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
761
-
762
- 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:
763
-
764
- A.cascade do
765
- sweet_tooth :default => false
766
- end
767
-
768
- B.sweet_tooth = Proc.new{|me| me.ingredients.include? :sugar }
769
-
770
- B.diabetic = false
771
- C.diabetic = true
772
- D.diabetic = false
773
-
774
- p A.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
775
- p B.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
776
- p C.ingredients.to_a # => [:splenda, :butter, :flour, :eggs]
777
- p D.ingredients.to_a # => [:sugar, :butter, :flour, :eggs]
778
-
779
- p A.sweet_tooth # => false
780
- p B.sweet_tooth # => true
781
- p C.sweet_tooth # => false
782
- p D.sweet_tooth # => true
783
-
784
- 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.
785
-
786
- Notice that the previous example requires ```:inherit => true``` on the initial ingredients definition:
787
-
788
- A.cascade do
789
- ingredients :default => Set.new([:sugar, :butter, :flour, :eggs]), :inherit => true
790
- end
791
-
792
- 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.
793
-
794
- ## blocks
795
-
796
- 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.
797
-
798
- This makes it easy to obtain a property's ancestors relative to any node:
799
-
800
- A.cascade do
801
- color :default => "red"
802
- end
803
- B.color = "blue"
804
- C.color = "white"
805
-
806
- parents = C.color{|my_color, parents| parents}
807
-
808
- p parents # => [B, A]
809
-
810
- For another example, consider the following:
811
-
812
- A.cascade do
813
- location :default => :urban
814
- trendy :default => Proc.new{|me| me.location == :urban}
815
- end
816
-
817
- B.location = :rural
818
- C.location = :urban
819
-
820
- blk = Proc.new do |is_trendy, parents|
821
- puts "my ancestors are: #{parents} and I am #{is_trendy ? '' : 'not'} trendy"
822
- end
823
-
824
- p A.trendy &blk # => "my ancestors are: [] and I am trendy"
825
- p B.trendy &blk # => "my ancestors are: [A] and I am not trendy"
826
- p C.trendy &blk # => "my ancestors are: [B, A] and I am trendy"
827
-
828
- 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:
829
-
830
- A.cascade do
831
- foods :default => [:pasta, :tomatoes]
832
- end
833
-
834
- B.foods << :apples << :walnuts
835
-
836
- C.foods << :chips
837
-
838
- all = Proc.new do |my_foods, parents|
839
- my_foods + parents.inject([]){|r, parent| r += parent.foods}
840
- end
841
-
842
- p A.foods # => [:pasta, :tomatoes]
843
- p B.foods # => [:apples, :walnuts]
844
- p C.foods # => [:chips]
845
-
846
- p A.foods &all # => [:pasta, :tomatoes]
847
- p B.foods &all # => [:apples, :walnuts, :pasta, :tomatoes]
848
- p C.foods &all # => [:chips, :apples, :walnuts, :pasta, :tomatoes]
849
-
850
- For a second example, let's create an options property and a block to merge all options giving precedence to the child:
851
-
852
- A.cascade do
853
- options :hash => true
854
- end
855
-
856
- A.options = {:color => "black", :music => ["Rolling Stones"]}
857
- B.options[:music] = ["Arcade Fire"]
858
- C.options = {:color => "blond"}
859
-
860
- merged = Proc.new do |my_opts, parents|
861
- parents.reverse.inject({}){|r, child|
862
- r.merge(child.options)
863
- }.merge(my_opts)
864
- end
865
-
866
- p A.options # => {:color => "black", :music => ["Rolling Stones"]}
867
- p B.options # => {:music => ["Arcade Fire"]}
868
- p C.options # => {:color => "blond"}
869
-
870
- p A.options &merged # => {:color => "black", :music => ["Rolling Stones"]}
871
- p B.options &merged # => {:color => "black", :music => ["Arcade Fire"]}
872
- p C.options &merged # => {:color => "blond", :music => ["Arcade Fire"]}
873
-
874
- If you find yourself operating on a node's parents often, you can also create a new proc property to do it for you:
875
-
876
- A.cascade do
877
- opts :default => {:color => "black", :music => ["Rolling Stones"]}
878
- merged_opts :proc => true
879
- end
880
-
881
- A.merged_opts = Proc.new do |me, parents|
882
- parents.reverse.inject({}){|r, child|
883
- r.merge(child.opts)
884
- }.merge(me.opts)
885
- end
886
-
887
- B.options[:music] = ["Arcade Fire"]
888
- C.options[:color] = "blond"
889
-
890
- p A.merged_opts # => {:color => "black", :music => ["Rolling Stones"]}
891
- p B.merged_opts # => {:color => "black", :music => ["Arcade Fire"]}
892
- p C.merged_opts # => {:color => "blond", :music => ["Arcade Fire"]}
893
-
894
- ## instances
895
-
896
- 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.
897
-
898
- class A
899
- include CC
900
- end
901
-
902
- A.cascade do
903
- colors :default => [:red, :blue, :green]
904
- end
905
-
906
- or
907
-
908
- class A
909
- extend CC
910
- end
911
-
912
- A.cascade do
913
- colors :default => [:red, :blue, :green], :instances => true
914
- end
915
-
916
- Instances are treated as child nodes. For instance
917
-
918
- A.cascade do
919
- color :default => "red"
920
- end
921
-
922
- a = A.new
923
- b = B.new
924
-
925
- p B.color{|color, parents| parents} # => [A]
926
- p a.color{|color, parents| parents} # => [A]
927
-
928
- p C.color{|color, parents| parents} # => [B, A]
929
- p b.color{|color, parents| parents} # => [B, A]
930
-
931
- 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.
932
-
933
- Other than that, you can operate on instances just as you would any other child class:
934
-
935
- A.cascade do
936
- name :default => "George"
937
- colors :default => [:green, :yellow, :purple]
938
- end
939
-
940
- a = A.new
941
- b = B.new
942
-
943
- p A.name # => "George"
944
- p a.name # => "George" (inherits from A)
945
- p B.name # => "George" (inherits from A)
946
- p b.name # => "George" (inherits from A)
947
-
948
- A.name = "Jefferson"
949
- B.name = "Sam"
950
-
951
- p B.name # => "Sam"
952
- p b.name # => "Sam" (inherits from B)
953
-
954
- b.name = "Timothy"
955
- p b.name # => "Timothy" (own)
956
-
957
- p A.name :default # => "George"
958
- p a.name :default # => "George"
959
- p B.name :default # => "George"
960
- p b.name :default # => "George"
961
-
962
- a.colors << :blue << :white
963
-
964
- B.colors << :pink << :black
965
- b.colors << :black
966
-
967
- p A.colors # => [:green, :yellow, :purple]
968
- p a.colors # => [:blue, :white]
969
- p B.colors # => [:pink, :black]
970
- p b.colors # => [:black]
971
-
972
- ## Custom Classes
973
-
974
- Properties can be any kind of object. To work with other class types you will need to define "blank" values and "new" values.
975
-
976
- Let's implement a custom type that is a sublcass of Hash
977
-
978
- class MyCustom < Hash
979
- def initialize(*arr)
980
- return super unless arr.size > 0
981
- [*arr].flatten.each_slice(2){|k,v| self[k] = v}
982
- end
983
- end
984
-
985
- This gives us some flexible ways to initialize a hash:
986
-
987
- c1 = MyCustom.new [[:name, "Gabe"], [:profession, "student"]]
988
- c2 = MyCustom.new [:name, "Gabe"], [:profession, "student"]
989
- c3 = MyCustom.new :name, "Gabe", :profession, "student"
990
-
991
- p c1 # => {:name => "Gabe", :profession => "student"}
992
- p c2 # => {:name => "Gabe", :profession => "student"}
993
- p c3 # => {:name => "Gabe", :profession => "student"}
994
-
995
- To incorporate MyCustom into a class hierarchy, we'll do the obvious thing:
996
-
997
- A.cascade
998
- custom :default => c1,
999
- :inherit => false,
1000
- :blank => lambda{|v| v.size.zero?},
1001
- :new => lambda{ MyCustom.new }
1002
- end
1003
-
1004
- 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.
1005
-
1006
- It helps to verify that your ```blank``` and ```new``` proc work as expected and that the default value is correct using the metadata hash:
1007
-
1008
- props = A.singleton_class.properties
1009
-
1010
- c = MyCustom.new
1011
-
1012
- p props[:custom][:new].call == c # => true
1013
- p props[:custom[:blank].call(c) # => true
1014
- p props[:custom][:type] # => :MyCustom
1015
- p props[:custom][:default] == c1 # => true
1016
-
1017
- Now you can use the property anywhere in the class tree:
1018
-
1019
- B.custom # => {}
1020
- B.custom(:default) # => {:name => "Gabe", :profession => "student"}