cascading_classes 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README-part-ii.md +1020 -0
- data/README.md +117 -970
- data/lib/cascading_classes.rb +1 -1
- data/todo +4 -0
- metadata +7 -5
data/README.md
CHANGED
@@ -1,28 +1,20 @@
|
|
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)
|
4
|
-
|
5
1
|
Cascading Classes
|
6
2
|
|
7
|
-
This is a small
|
8
|
-
|
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:
|
3
|
+
This is a small library that helps simplify and manage deeply hierarchal data. It can be used for brainstorming. It can be used to model a tree of data of any depth and breadth. You can use it for brainstorming or to carry active content.
|
10
4
|
|
11
|
-
|
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)
|
5
|
+
__Forewarning:__ This library commits the heinous (blasphemous?) crime of focusing on classes as useful objects in their own right rather than the objects spawned form them. Stuff like this:
|
15
6
|
|
16
|
-
|
7
|
+
Parent.last_name = 'brownstein'
|
8
|
+
Parent.first_name = "charlie"
|
9
|
+
Parent.name = Proc.new{|c| c.first_name + ' ' + c.last_name}
|
17
10
|
|
18
|
-
|
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.
|
11
|
+
Child.first_name = "sam"
|
20
12
|
|
21
|
-
|
13
|
+
p Child.name # => "sam brownstein"
|
22
14
|
|
23
|
-
|
15
|
+
You have been warned!
|
24
16
|
|
25
|
-
#
|
17
|
+
# Take it for a quick spin
|
26
18
|
|
27
19
|
Assume the following simple class hierarchy:
|
28
20
|
|
@@ -36,1040 +28,195 @@ Assume the following simple class hierarchy:
|
|
36
28
|
class C < B
|
37
29
|
end
|
38
30
|
|
39
|
-
|
40
|
-
|
41
|
-
Create ```name, city and state``` properties:
|
42
|
-
|
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:
|
50
|
-
|
51
|
-
p A.name # => "Franklin"
|
52
|
-
p A.city # => "New York"
|
53
|
-
p A.state # => "NY"
|
54
|
-
|
55
|
-
furthermore, blank descendents inherit the same property values:
|
56
|
-
|
57
|
-
p B.name # => "Franklin"
|
58
|
-
p C.name # => "Franklin"
|
59
|
-
|
60
|
-
p B.city # => "New York"
|
61
|
-
p C.city # => "New York"
|
62
|
-
|
63
|
-
...
|
64
|
-
|
65
|
-
Update the properties on A's desendents:
|
66
|
-
|
67
|
-
B.name = "Charles"
|
68
|
-
C.name = "Sam"
|
69
|
-
|
70
|
-
C.city = "Rochester"
|
71
|
-
|
72
|
-
Collect property names and values:
|
73
|
-
|
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"}
|
77
|
-
|
78
|
-
pass 'false' for only those properties that were specifically set:
|
79
|
-
|
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
|
85
|
-
|
86
|
-
### ...more...
|
87
|
-
|
88
|
-
Add a second child to A based out of Boston:
|
89
|
-
|
90
|
-
B2 = Class.new(A) # same as: class B2 < A; end
|
91
|
-
|
92
|
-
B2.name = 'Thomas'
|
93
|
-
B2.city = 'Boston'
|
94
|
-
B2.state = 'MA'
|
95
|
-
|
96
|
-
Descendents of B2 will inherit these new values unless set.
|
97
|
-
|
98
|
-
class C2 < B2; end
|
99
|
-
|
100
|
-
p C2.city # => Boston
|
101
|
-
p C2.state # => MA
|
102
|
-
|
103
|
-
Now we have three properties on five nodes spanning three generations.
|
104
|
-
|
105
|
-
Let's add two more properties; these will depend on one's location:
|
106
|
-
|
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
|
111
|
-
|
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.
|
113
|
-
|
114
|
-
Watch how this works for our five node, three generation setup. Notice how each proc (read: behavior) cascades down the class hierarchy:
|
115
|
-
|
116
|
-
p A.baseball_team # => "Yankees"
|
117
|
-
p B.baseball_team # => "Yankees"
|
118
|
-
p C.baseball_team # => "Yankees"
|
119
|
-
|
120
|
-
p B2.baseball_team # => "Red Sox"
|
121
|
-
p C2.baseball_team # => "Red Sox"
|
122
|
-
|
123
|
-
If we change C2's location, its teams should follow suit:
|
124
|
-
|
125
|
-
C2.city = "New York"
|
126
|
-
C2.state = "NY"
|
127
|
-
|
128
|
-
p C2.baseball_team # => "Yankees"
|
129
|
-
p C2.basketball_team # => "Knicks"
|
130
|
-
|
131
|
-
Any new node added to the mix should be "born" with the same behavior:
|
132
|
-
|
133
|
-
class D < C; end
|
134
|
-
|
135
|
-
p D.state # => "NY"
|
136
|
-
p D.baseball_team # => "Yankees"
|
137
|
-
|
138
|
-
D.state = "MA"
|
139
|
-
p D.baseball_team # => "Red Sox"
|
140
|
-
|
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.
|
142
|
-
|
143
|
-
Finally, let's add a child to C2, located in Los Angeles, and rewrite the sports properties to fit our bulging world:
|
144
|
-
|
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
|
167
|
-
end
|
168
|
-
|
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:
|
172
|
-
|
173
|
-
A.cascade do
|
174
|
-
teams :default => Proc.new{|me|
|
175
|
-
[me.baseball_team, me.basketball_team]
|
176
|
-
}
|
177
|
-
end
|
178
|
-
|
179
|
-
test it out
|
180
|
-
|
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"]
|
195
|
-
|
196
|
-
### top it off with a block
|
197
|
-
|
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:
|
199
|
-
|
200
|
-
p D2.teams do |val, parents|
|
201
|
-
puts "D2: #{val.inspect}"
|
202
|
-
parents.each{|parent| puts "#{parent}: #{parent.teams.inspect}"}
|
203
|
-
end
|
204
|
-
|
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
|
31
|
+
Notice that ```CC``` is an alias for ```CascadingClasses```
|
221
32
|
|
222
|
-
# In Depth:
|
223
|
-
|
224
|
-
Assume a simple three node, three generation setup:
|
225
|
-
|
226
33
|
class A
|
227
|
-
|
228
|
-
end
|
229
|
-
|
230
|
-
class B < A
|
231
|
-
end
|
232
|
-
|
233
|
-
class C < B
|
34
|
+
extend CC
|
234
35
|
end
|
235
36
|
|
236
|
-
|
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.
|
37
|
+
is equivalent to
|
239
38
|
|
240
|
-
|
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
|
39
|
+
class A
|
40
|
+
extend CascadingClasses
|
250
41
|
end
|
251
42
|
|
252
|
-
|
253
|
-
|
254
|
-
### defaults
|
43
|
+
Note also the difference between the *extend* and the *include* versions. Objects of a class are able to inherit class properties created with `cascade` when *include* is used. When *extend* is used objects do not inherit *cascaded* properties. Note that subclasses *always* inherit the cascaded properties. A quick example will explain it better than words:
|
255
44
|
|
256
|
-
|
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"
|
45
|
+
class Person
|
46
|
+
extend CC
|
378
47
|
end
|
379
48
|
|
380
|
-
|
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
|
395
|
-
|
396
|
-
Defaults are not required, but if not given, the type of the property won't be known until it is first assigned.
|
397
|
-
|
398
|
-
props = A.cascade do
|
399
|
-
space_free
|
400
|
-
color
|
49
|
+
Person.cascade do
|
50
|
+
language default: "english"
|
401
51
|
end
|
402
52
|
|
403
|
-
|
404
|
-
A.space_free = 328.12
|
405
|
-
p props[:space_free][:type] # => :Float
|
406
|
-
|
407
|
-
p props[:color][:type] # => :undefined_type
|
408
|
-
A.color = "red"
|
409
|
-
p props[:color][:type] # => :String
|
410
|
-
|
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.
|
53
|
+
person = Person.new
|
412
54
|
|
413
|
-
|
55
|
+
p Person.language # => "english"
|
56
|
+
p person.language # => NoMethodError: undefined method 'language' for #<Person:0x0000...>
|
414
57
|
|
415
|
-
|
58
|
+
compare this to:
|
416
59
|
|
417
|
-
|
418
|
-
|
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
|
60
|
+
class Person
|
61
|
+
include CC
|
425
62
|
end
|
426
63
|
|
427
|
-
|
428
|
-
|
429
|
-
p props[:address][:type] # => :Hash
|
430
|
-
p props[:city][:type] # => :String
|
431
|
-
p props[:area][:type] # => :Fixnum
|
432
|
-
p props[:zip][:type] # => :Fixnum
|
433
|
-
|
434
|
-
## container types: arrays, hashes, sets, ...
|
435
|
-
|
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:
|
441
|
-
|
442
|
-
A.cascade do
|
443
|
-
urls :default => ["www.google.com", "www.nytimes.com"]
|
444
|
-
name :default => {:first => 'jon', :last => 'smith'}
|
64
|
+
Person.cascade do
|
65
|
+
language default: "english"
|
445
66
|
end
|
446
67
|
|
447
|
-
|
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"]
|
68
|
+
person = Person.new
|
457
69
|
|
458
|
-
p
|
459
|
-
p
|
460
|
-
p C.name # => {}
|
70
|
+
p Person.language # => "english"
|
71
|
+
p person.language # => "english"
|
461
72
|
|
462
|
-
|
463
|
-
B.name[:last] = "johnson"
|
464
|
-
C.name = {:first => 'terry', :last => 'williamson'}
|
73
|
+
The `person` object doesn't have the ```language``` property in one and does in another.
|
465
74
|
|
466
|
-
|
467
|
-
p B.name # => {:first => 'adam', :last => 'johnson'}
|
468
|
-
p C.name # => {:first => 'terry', :last => 'williamson'}
|
75
|
+
There are two versions to emphasize the point that objects aren't themselves inheritable. Consider a class hierarchy a hundred levels deep. A class can give birth to whole host of classes (through subclassing) made in its own image. Objects are unable to offer its own traits to the next generation of classes. They can inherit from above, but no object (or class) inherits from it. Hence the difference between *extend* and *include*
|
469
76
|
|
470
|
-
|
77
|
+
## Continuing the tour
|
471
78
|
|
472
|
-
|
79
|
+
Let's create a new example
|
473
80
|
|
474
|
-
A
|
475
|
-
|
81
|
+
Class A
|
82
|
+
extend CC
|
476
83
|
end
|
477
84
|
|
478
|
-
|
479
|
-
p B.cities # => ["boston", "new york"] (inherited from A)
|
480
|
-
p C.cities # => ["boston", "new york"] (inherited from A)
|
481
|
-
|
482
|
-
p A.cities_is_blank? # => false
|
483
|
-
p B.cities_is_blank? # => true
|
484
|
-
p C.cities_is_blank? # => true
|
485
|
-
|
486
|
-
if you think this is useful, just take care not to trip over yourself
|
487
|
-
|
488
|
-
B.cities << "london"
|
489
|
-
|
490
|
-
p B.cities_is_blank? # => true
|
491
|
-
p A.cities # => ["boston", "new york", "london"]
|
492
|
-
p B.cities # => ["boston", "new york", "london"]
|
493
|
-
|
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"
|
85
|
+
Class B < A; end
|
501
86
|
|
502
|
-
|
503
|
-
p B.cities # => ["london"]
|
87
|
+
Class C < B; end
|
504
88
|
|
505
|
-
|
89
|
+
A is the top-level parent. B is its child. C is the child of B.
|
506
90
|
|
507
|
-
B
|
508
|
-
|
509
|
-
p B.cities # ["boston", "new york"] (inherits from A)
|
510
|
-
p B.cities false # [] (own value)
|
91
|
+
A < B < C
|
511
92
|
|
512
|
-
|
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:
|
93
|
+
create ```name``` and ```city``` properties:
|
519
94
|
|
520
95
|
A.cascade do
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
p A.cuisine # => [:italian, :french]
|
525
|
-
p B.cuisine # => []
|
526
|
-
p C.cuisine # => []
|
527
|
-
|
528
|
-
p B.cuisine :inherit # => [:italian, :french]
|
529
|
-
p C.cuisine :inherit # => [:italian, :french]
|
530
|
-
|
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:
|
532
|
-
|
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)
|
541
|
-
|
542
|
-
you can ommit the ':inherit' syntax entirely:
|
543
|
-
|
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]
|
554
|
-
|
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"]
|
96
|
+
name default: "Tom"
|
97
|
+
city default: "New York"
|
564
98
|
end
|
99
|
+
|
100
|
+
Notice that properties are creating by calling ```cascade``` on a class. The act of creating another generation is done through subclassing. In this case, every subclass of ```A``` will have the class properties ```name``` and ```city```. These properties will be propagated to every descendent below ```A```.
|
565
101
|
|
566
|
-
p
|
567
|
-
p
|
102
|
+
p A.name # => "Tom"
|
103
|
+
p B.name # => "Tom"
|
104
|
+
p C.name # => "Tom"
|
568
105
|
|
569
|
-
|
106
|
+
Each descendent *inherits* its parent's value if unset.
|
570
107
|
|
571
|
-
|
108
|
+
p A.name_is_unset? # => false
|
109
|
+
p B.name_is_unset? # => true
|
110
|
+
p C.name_is_unset? # => true
|
572
111
|
|
573
|
-
|
112
|
+
p A.name # => "Tom"
|
113
|
+
p B.name # => "Tom"
|
114
|
+
p C.name # => "Tom"
|
574
115
|
|
575
|
-
|
116
|
+
Notice the dynamic class method ```name_is_unset?``` created from the ```name``` property.
|
576
117
|
|
577
|
-
|
578
|
-
p props[:colors][:inherit] # => false
|
118
|
+
Naturally properties can be read and written to. Let's update the ```name``` on B:
|
579
119
|
|
580
|
-
|
120
|
+
B.name = "John"
|
121
|
+
p B.name # => "John"
|
581
122
|
|
582
|
-
|
123
|
+
It's crucuial to see how this effects the descendents of ```B```. Notice how this value cascades down to ```C```
|
583
124
|
|
584
|
-
p
|
585
|
-
p B.states # => [:good, :bad, :disaster] (inherits from A)
|
586
|
-
p C.states # => [:good, :bad, :disaster] (inherits from A)
|
125
|
+
p C.name # => "John"
|
587
126
|
|
588
|
-
|
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:
|
127
|
+
At any point in time we can add nodes anywhere along the tree: up or down:
|
595
128
|
|
596
|
-
A
|
597
|
-
servers :inherit => true
|
598
|
-
users
|
129
|
+
class B2 < A
|
599
130
|
end
|
600
131
|
|
601
|
-
|
602
|
-
A.users = [:bigfoot, :charlie]
|
603
|
-
|
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"]
|
611
|
-
|
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]
|
619
|
-
|
620
|
-
## Proc Properites
|
621
|
-
|
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"}
|
132
|
+
class C2 < B2
|
642
133
|
end
|
643
134
|
|
644
|
-
|
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
|
653
|
-
|
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).
|
671
|
-
|
672
|
-
Let's give B its own mood proc.
|
673
|
-
|
674
|
-
B.mood = Proc.new{|me, parents|
|
675
|
-
parents.first.mood == "miserable" ? "happy" : "miserable"
|
676
|
-
}
|
677
|
-
|
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.
|
679
|
-
|
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
|
135
|
+
class D < C
|
757
136
|
end
|
758
137
|
|
759
|
-
|
760
|
-
B.weather = C.weather = nil # set the others to blank
|
761
|
-
|
762
|
-
A.mood # => "content"
|
763
|
-
B.mood # => "content"
|
764
|
-
C.mood # => "content"
|
138
|
+
Our tree now looks like the following:
|
765
139
|
|
766
|
-
Now, any descendent that redefines the mood cannot affect further downstream descendents:
|
767
140
|
|
768
|
-
|
141
|
+
_______ D
|
142
|
+
/
|
143
|
+
/
|
144
|
+
________ C -------- ..
|
145
|
+
/
|
146
|
+
/
|
147
|
+
________ B -------- ..
|
148
|
+
/ \
|
149
|
+
/ \ ________ ..
|
150
|
+
/
|
151
|
+
A -------- .. ________ ..
|
152
|
+
\ /
|
153
|
+
\ / ________ ..
|
154
|
+
\ ________ B2 -------- .. /
|
155
|
+
\ /
|
156
|
+
\ ________ C2 -------- ..
|
769
157
|
|
770
|
-
A.mood # => "content"
|
771
|
-
B.mood # => "angry"
|
772
|
-
C.mood # => "content"
|
773
158
|
|
774
|
-
|
159
|
+
We now have six nodes that span four generations (depth=4). Things have gotten considerably more complex. That's the point. It's easy for the complexity to get out of hand. But you need a way of applying properties across the whole of it in a way that is predictible and sensible. We can continue forever adding (subclassing) nodes and watch each descendent come to life endowed with a sensible```name``` and ```city```.
|
775
160
|
|
776
|
-
|
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
|
161
|
+
We can also, at any point, introduce new properties onto the tree, or any subtree of the tree. And you can expect each descendent to inherit from its parent in real time. For example, we let's add the ```state``` property to all descendents of ```B```
|
792
162
|
|
793
163
|
B.cascade do
|
794
|
-
|
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
|
164
|
+
state default: "MA"
|
800
165
|
end
|
801
166
|
|
802
|
-
|
803
|
-
|
804
|
-
|
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
|
167
|
+
p B.state # => "MA"
|
168
|
+
p C.state # => "MA"
|
169
|
+
p D.state # => "MA"
|
828
170
|
|
829
|
-
|
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]
|
171
|
+
# Another simple example
|
833
172
|
|
834
|
-
|
835
|
-
p B.sweet_tooth # => true
|
836
|
-
p C.sweet_tooth # => false
|
837
|
-
p D.sweet_tooth # => true
|
173
|
+
Let's try another example. This time we'll allow objects themselves to take after their class parent (requires use of ```include``` keyword). Objects inherit from their class, but are not inheritable themselves. We'll begin with three properties: ```color```, ```width```, and ```height```.
|
838
174
|
|
839
|
-
|
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}
|
870
|
-
end
|
871
|
-
|
872
|
-
B.location = :rural
|
873
|
-
C.location = :urban
|
874
|
-
|
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
|
175
|
+
class Parent
|
954
176
|
include CC
|
955
177
|
end
|
956
178
|
|
957
|
-
|
958
|
-
|
179
|
+
Parent.cascade do
|
180
|
+
color default: "red"
|
181
|
+
width default: 100
|
182
|
+
height default: 50
|
959
183
|
end
|
960
184
|
|
961
|
-
|
185
|
+
class Div1 < Parent; end
|
186
|
+
class Div2 < Parent; end
|
962
187
|
|
963
|
-
|
964
|
-
|
965
|
-
end
|
188
|
+
div1 = Div1.new
|
189
|
+
div2 = Div2.new
|
966
190
|
|
967
|
-
|
968
|
-
colors :default => [:red, :blue, :green], :instances => true
|
969
|
-
end
|
191
|
+
Every object and class in this hierarchy has a color, height, and width.
|
970
192
|
|
971
|
-
|
193
|
+
p Div1.color # => "red"
|
194
|
+
p div1.color # => "red"
|
972
195
|
|
973
|
-
|
974
|
-
color :default => "red"
|
975
|
-
end
|
196
|
+
Div1.color = "blue"
|
976
197
|
|
977
|
-
|
978
|
-
|
198
|
+
p Div1.color # => "blue"
|
199
|
+
p div1.color # => "blue"
|
979
200
|
|
980
|
-
|
981
|
-
p a.color{|color, parents| parents} # => [A]
|
201
|
+
div1.color = "black"
|
982
202
|
|
983
|
-
p
|
984
|
-
p
|
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.
|
203
|
+
p Div1.color # => "blue"
|
204
|
+
p div1.color # => "black"
|
987
205
|
|
988
|
-
|
206
|
+
We can collect the property values by calling ```to_hash```
|
989
207
|
|
990
|
-
|
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"
|
1016
|
-
|
1017
|
-
a.colors << :blue << :white
|
1018
|
-
|
1019
|
-
B.colors << :pink << :black
|
1020
|
-
b.colors << :black
|
1021
|
-
|
1022
|
-
p A.colors # => [:green, :yellow, :purple]
|
1023
|
-
p a.colors # => [:blue, :white]
|
1024
|
-
p B.colors # => [:pink, :black]
|
1025
|
-
p b.colors # => [:black]
|
1026
|
-
|
1027
|
-
## Custom Classes
|
208
|
+
p Div1.to_hash # => {:color=>"blue", :width=>100, :height=>50}
|
1028
209
|
|
1029
|
-
|
1030
|
-
|
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
|
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
|
210
|
+
If you only want to see the hash of properties that have been set, pass ```false```
|
1058
211
|
|
1059
|
-
|
212
|
+
p Div1.to_hash # => {:color=>"blue", :width=>100, :height=>50}
|
213
|
+
p Div1.to_hash(false) # => {:color=>"blue"}
|
1060
214
|
|
1061
|
-
|
215
|
+
Notice that we have only set the ```color``` property on ```Div1```. Let's give a height different from its parent.
|
1062
216
|
|
1063
|
-
|
217
|
+
Div1.height = 75
|
1064
218
|
|
1065
|
-
|
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
|
219
|
+
p Div1.to_hash(false) # => {:color=>"blue", :height=>75}
|
1071
220
|
|
1072
|
-
|
221
|
+
This concludes the introduction to this library. To learn about what happens when properties are arrays or hashes or to learn about dynamic properties and what that means in the context of *cascade* see [todo](#)
|
1073
222
|
|
1074
|
-
B.custom # => {}
|
1075
|
-
B.custom(:default) # => {:name => "Gabe", :profession => "student"}
|