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.
- data/README.md +165 -64
- data/spec/basics/basic_spec.rb +71 -76
- data/spec/basics/block_spec.rb +10 -10
- data/spec/basics/container_spec.rb +90 -90
- data/spec/basics/inherit_spec.rb +156 -156
- data/spec/basics/proc_spec.rb +94 -94
- data/spec/class_helper_methods/parents_for_spec.rb +5 -5
- data/spec/custom_classes/hash_like_spec.rb +11 -11
- data/spec/helper_spec.rb +11 -11
- data/spec/instances/basics.rb +39 -39
- data/spec/preset_classes/array_spec.rb +39 -39
- data/spec/preset_classes/hash_spec.rb +38 -38
- data/spec/preset_classes/strings.rb +53 -53
- data/spec/preset_classes/undefined.rb +4 -4
- data/spec/usage/block_spec.rb +6 -6
- data/spec/usage/proc_spec.rb +8 -8
- data/todo +21 -0
- metadata +1 -2
- data/README-part-ii.md +0 -1020
data/README.md
CHANGED
@@ -2,16 +2,30 @@ Cascading Classes
|
|
2
2
|
|
3
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.
|
4
4
|
|
5
|
-
|
5
|
+
__Warning:__ This library commits the blasphemous crime of utilizing classes as objects. Stuff like this:
|
6
6
|
|
7
|
-
Parent.
|
8
|
-
Parent.
|
9
|
-
Parent.name = Proc.new{|c| c.
|
10
|
-
|
11
|
-
Child.first_name = "sam"
|
7
|
+
Parent.lname = 'brownstein'
|
8
|
+
Parent.fname = "charlie"
|
9
|
+
Parent.name = Proc.new{|c| c.fname + ' ' + c.lname}
|
12
10
|
|
11
|
+
Child.fname = "sam"
|
13
12
|
p Child.name # => "sam brownstein"
|
14
13
|
|
14
|
+
Whereas normally you might write something like this:
|
15
|
+
|
16
|
+
class Parent
|
17
|
+
attr_accessor :fname, :lname
|
18
|
+
|
19
|
+
def name
|
20
|
+
fname + ' ' + lname
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
obj = Parent.new
|
25
|
+
obj.fname = 'sam'
|
26
|
+
obj.lname = 'brownstein'
|
27
|
+
p obj.name # => 'sam brownstien'
|
28
|
+
|
15
29
|
You have been warned!
|
16
30
|
|
17
31
|
# Take it for a quick spin
|
@@ -40,39 +54,41 @@ is equivalent to
|
|
40
54
|
extend CascadingClasses
|
41
55
|
end
|
42
56
|
|
43
|
-
Note also the difference between the *extend* and the *include* versions.
|
57
|
+
Note also the difference between the *extend* and the *include* versions. Class instances will not "inherit" cascaded properties with *extend* but will with *include*.
|
44
58
|
|
45
59
|
class Person
|
46
60
|
extend CC
|
61
|
+
|
62
|
+
cascade do
|
63
|
+
language default: "english"
|
64
|
+
end
|
47
65
|
end
|
48
66
|
|
49
|
-
Person
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
person = Person.new
|
67
|
+
Brit = Class.new(Person) # same as: class Brit < Person; end
|
68
|
+
john = Person.new
|
54
69
|
|
55
|
-
p
|
56
|
-
p
|
70
|
+
p Brit.language # => "english"
|
71
|
+
p john.language # => NoMethodError: undefined method 'language' for #<Person:0x0000...>
|
57
72
|
|
58
|
-
compare
|
73
|
+
compare that to the following:
|
59
74
|
|
60
75
|
class Person
|
61
76
|
include CC
|
62
|
-
end
|
63
77
|
|
64
|
-
|
65
|
-
|
78
|
+
cascade do
|
79
|
+
language default: "english"
|
80
|
+
end
|
66
81
|
end
|
67
82
|
|
68
|
-
|
83
|
+
Brit = Class.new(Person)
|
84
|
+
john = Person.new
|
69
85
|
|
70
|
-
p
|
71
|
-
p
|
86
|
+
p Brit.language # => "english"
|
87
|
+
p john.language # => "english"
|
72
88
|
|
73
|
-
The `
|
89
|
+
The `john` instance doesn't have the ```language``` property in one and does in another.
|
74
90
|
|
75
|
-
There are two versions to emphasize the point that
|
91
|
+
There are two versions to emphasize the point that calling *cascade* is about endowing your descendents with traits. And instances aren't inheritable. They don't have children. Consider a class hierarchy a hundred levels deep. Calling *cascade* on any one class will effect it and all its descendents. If you want instances of all those classes to be effected as well use *include*. Otherwise use *extend*.
|
76
92
|
|
77
93
|
## Continuing the tour
|
78
94
|
|
@@ -97,34 +113,21 @@ create ```name``` and ```city``` properties:
|
|
97
113
|
city default: "New York"
|
98
114
|
end
|
99
115
|
|
100
|
-
|
116
|
+
Here we provide A and all its descendents with the *name* and *city* properties.
|
101
117
|
|
102
118
|
p A.name # => "Tom"
|
103
119
|
p B.name # => "Tom"
|
104
120
|
p C.name # => "Tom"
|
105
121
|
|
106
|
-
|
107
|
-
|
108
|
-
p A.name_is_unset? # => false
|
109
|
-
p B.name_is_unset? # => true
|
110
|
-
p C.name_is_unset? # => true
|
111
|
-
|
112
|
-
p A.name # => "Tom"
|
113
|
-
p B.name # => "Tom"
|
114
|
-
p C.name # => "Tom"
|
115
|
-
|
116
|
-
Notice the dynamic class method ```name_is_unset?``` created from the ```name``` property.
|
117
|
-
|
118
|
-
Naturally properties can be read and written to. Let's update the ```name``` on B:
|
122
|
+
Descendents inherit their values unless set themeselves.
|
119
123
|
|
120
124
|
B.name = "John"
|
121
125
|
p B.name # => "John"
|
122
|
-
|
123
|
-
It's crucuial to see how this effects the descendents of ```B```. Notice how this value cascades down to ```C```
|
124
|
-
|
125
126
|
p C.name # => "John"
|
126
127
|
|
127
|
-
|
128
|
+
It's crucuial to see that C changes too
|
129
|
+
|
130
|
+
At any point in time we can add nodes anywhere on the tree:
|
128
131
|
|
129
132
|
class B2 < A
|
130
133
|
end
|
@@ -135,7 +138,7 @@ At any point in time we can add nodes anywhere along the tree: up or down:
|
|
135
138
|
class D < C
|
136
139
|
end
|
137
140
|
|
138
|
-
Our
|
141
|
+
Our simple example now looks like the following:
|
139
142
|
|
140
143
|
|
141
144
|
_______ D
|
@@ -156,9 +159,9 @@ Our tree now looks like the following:
|
|
156
159
|
\ ________ C2 -------- ..
|
157
160
|
|
158
161
|
|
159
|
-
|
162
|
+
Now we 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 to apply a set of properties on a tree in a predictible way. We can continue adding (subclassing) nodes and watch each descendent come to life endowed with a sensible values for *name* and *city*.
|
160
163
|
|
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,
|
164
|
+
We can also, at any point, introduce new properties onto the tree, or any subtree of the tree for that matter. And you can expect each descendent to inherit from its parent in real time. For example, let's add the *state* property to all descendents of *B*.
|
162
165
|
|
163
166
|
B.cascade do
|
164
167
|
state default: "MA"
|
@@ -170,16 +173,16 @@ We can also, at any point, introduce new properties onto the tree, or any subtre
|
|
170
173
|
|
171
174
|
# Another simple example
|
172
175
|
|
173
|
-
Let's try another example. This time we'll
|
176
|
+
Let's try another example. This time we'll let properties cascade to instances (requires use of ```include``` keyword). We'll begin with three properties: ```color```, ```width```, and ```height```.
|
174
177
|
|
175
178
|
class Parent
|
176
179
|
include CC
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
180
|
+
|
181
|
+
cascade do
|
182
|
+
color default: "red"
|
183
|
+
width default: 100
|
184
|
+
height default: 50
|
185
|
+
end
|
183
186
|
end
|
184
187
|
|
185
188
|
class Div1 < Parent; end
|
@@ -188,35 +191,133 @@ Let's try another example. This time we'll allow objects themselves to take afte
|
|
188
191
|
div1 = Div1.new
|
189
192
|
div2 = Div2.new
|
190
193
|
|
191
|
-
Every
|
194
|
+
Every instance and class in this hierarchy has a color, height, and width.
|
192
195
|
|
193
196
|
p Div1.color # => "red"
|
194
197
|
p div1.color # => "red"
|
195
198
|
|
196
|
-
|
197
|
-
|
198
|
-
p Div1.color # => "blue"
|
199
|
-
p div1.color # => "blue"
|
199
|
+
Set the *color* property
|
200
200
|
|
201
|
+
Div1.color = "blue"
|
201
202
|
div1.color = "black"
|
202
203
|
|
203
|
-
|
204
|
-
p div1.color # => "black"
|
205
|
-
|
206
|
-
We can collect the property values by calling ```to_hash```
|
204
|
+
We can collect *cascaded* properties values by calling ```to_hash```
|
207
205
|
|
208
206
|
p Div1.to_hash # => {:color=>"blue", :width=>100, :height=>50}
|
209
207
|
|
210
|
-
|
208
|
+
To obtain the hash that includes only properties that have been set (not inherited) pass *false*
|
211
209
|
|
212
210
|
p Div1.to_hash # => {:color=>"blue", :width=>100, :height=>50}
|
213
211
|
p Div1.to_hash(false) # => {:color=>"blue"}
|
214
212
|
|
215
|
-
|
213
|
+
only the *color* has been set on Div1. The others inherit from *Parent*
|
216
214
|
|
217
215
|
Div1.height = 75
|
218
|
-
|
219
216
|
p Div1.to_hash(false) # => {:color=>"blue", :height=>75}
|
220
217
|
|
221
|
-
|
218
|
+
## what is *default*
|
219
|
+
|
220
|
+
Consider this simple example:
|
221
|
+
|
222
|
+
Class A
|
223
|
+
extend CC
|
224
|
+
|
225
|
+
cascade do
|
226
|
+
color default: "red"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
B = Class.new(A)
|
231
|
+
C = Class.new(B)
|
232
|
+
|
233
|
+
The default value is always available to any descendent, even if the property has since been set.
|
234
|
+
|
235
|
+
C.color = "black"
|
236
|
+
p C.color(:default) # => "red"
|
237
|
+
|
238
|
+
## Arrays and Hashes follow different inheritance rules
|
239
|
+
|
240
|
+
Container types actually behave differently. Descendents don't inherit from their ancestors. They start out with empty containers.
|
241
|
+
|
242
|
+
Try it with an array:
|
243
|
+
|
244
|
+
A.cascade do
|
245
|
+
list default: []
|
246
|
+
end
|
247
|
+
|
248
|
+
A.list << 4 << 1
|
249
|
+
B.list << 5 << 1
|
250
|
+
|
251
|
+
p A.list # => [4, 1]
|
252
|
+
p B.list # => [5, 3]
|
253
|
+
|
254
|
+
And a hash:
|
255
|
+
|
256
|
+
A.cascade do
|
257
|
+
dict default: {}
|
258
|
+
end
|
259
|
+
|
260
|
+
A.dict[:width] = 400
|
261
|
+
B.dict[:height] = 20
|
262
|
+
|
263
|
+
p A.dict # => {:width=>400}
|
264
|
+
p B.dict # => {:height=>20}
|
265
|
+
|
266
|
+
## Proc properties
|
267
|
+
|
268
|
+
A property can also be a *Proc*. The object passed to it is a copy of the current class. This allows you to create properties that are derived, dynamic
|
269
|
+
|
270
|
+
A.cascade do
|
271
|
+
score default: 89
|
272
|
+
passed default: Proc.new{|me| me.score > 75}
|
273
|
+
end
|
274
|
+
|
275
|
+
Here we've created two new properties. The *passed* property gets its value from the *score* property. Note that calling *passed* evaluates the Proc in the context of the receiver. It doesn't return the Proc itself.
|
276
|
+
|
277
|
+
A.passed # => true
|
278
|
+
|
279
|
+
Now we can change the value on *score* on any descendent and *passed* will change too
|
280
|
+
|
281
|
+
B.score = 72
|
282
|
+
p B.passed # => false
|
283
|
+
|
284
|
+
## Blocks
|
285
|
+
|
286
|
+
You can pass blocks to any property. The parameters passed to the block are the property value and an array of ancestors. Here is an example:
|
287
|
+
|
288
|
+
A.cascade do
|
289
|
+
color default: "red"
|
290
|
+
end
|
291
|
+
|
292
|
+
B = Class.new(A)
|
293
|
+
C = Class.new(B)
|
294
|
+
|
295
|
+
B.color = "blue"
|
296
|
+
C.color = "white"
|
297
|
+
|
298
|
+
p C.color{|color| color} # => "red"
|
299
|
+
p C.color{|color, parents| parents} # => [B, A]
|
300
|
+
|
301
|
+
For example you could print out a list of C and its ancestors' colors
|
302
|
+
|
303
|
+
C.color{|c, parents| [c] +
|
304
|
+
parents.map{|x| x.color}
|
305
|
+
} # => ["white", "blue", "red"]
|
306
|
+
|
307
|
+
Or you can set the color on every ancestor:
|
308
|
+
|
309
|
+
C.color{|c, parents| parents.each{|p| p.color = c}}
|
310
|
+
|
311
|
+
p A.color # => "white"
|
312
|
+
p B.color # => "white"
|
313
|
+
p C.color # => "white"
|
314
|
+
|
315
|
+
## Ancestor chain
|
316
|
+
|
317
|
+
To obtain an array of parents call *ancestor_chain*
|
318
|
+
|
319
|
+
A.ancestor_chain # => []
|
320
|
+
B.ancestor_chain # => [A]
|
321
|
+
C.ancestor_chain # => [B, A]
|
222
322
|
|
323
|
+
}
|
data/spec/basics/basic_spec.rb
CHANGED
@@ -21,15 +21,11 @@ describe "basics" do
|
|
21
21
|
before do
|
22
22
|
@props = A.cascade do
|
23
23
|
wings
|
24
|
-
features
|
25
|
-
blind
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
29
|
-
it "has
|
30
|
-
@props[:wings][:type].
|
31
|
-
@props[:features][:type].must_equal :undefined_type
|
32
|
-
@props[:blind][:type].must_equal :undefined_type
|
27
|
+
it "has undefined type" do
|
28
|
+
@props[:wings][:type].should == :undefined_type
|
33
29
|
end
|
34
30
|
end
|
35
31
|
|
@@ -48,88 +44,87 @@ describe "basics" do
|
|
48
44
|
end
|
49
45
|
|
50
46
|
describe "parent class" do
|
51
|
-
it "
|
52
|
-
A.color.
|
53
|
-
A.score.
|
54
|
-
A.rank.
|
55
|
-
A.style.
|
56
|
-
A.available.
|
57
|
-
A.locations.
|
58
|
-
A.name.
|
59
|
-
A.classified.
|
47
|
+
it "is set with the defaults" do
|
48
|
+
A.color.should == "red"
|
49
|
+
A.score.should == 45.9
|
50
|
+
A.rank.should == 18
|
51
|
+
A.style.should == :spotted
|
52
|
+
A.available.should == true
|
53
|
+
A.locations.should == [:south, :north]
|
54
|
+
A.name.should eq({:first => 'jon', :last => 'smith'})
|
55
|
+
A.classified.should == true
|
60
56
|
end
|
61
57
|
end
|
62
58
|
|
63
59
|
describe "descendents" do
|
64
|
-
it "
|
65
|
-
A.color(:default).
|
60
|
+
it "can access the defaults" do
|
61
|
+
A.color(:default).should == "red"
|
66
62
|
A.color = "blue"
|
67
|
-
A.color(:default).
|
68
|
-
|
63
|
+
A.color(:default).should == "red"
|
69
64
|
B.color = "white"
|
70
|
-
B.color.
|
71
|
-
B.color(:default).
|
72
|
-
C.color(:default).
|
65
|
+
B.color.should == "white"
|
66
|
+
B.color(:default).should == "red"
|
67
|
+
C.color(:default).should == "red"
|
73
68
|
end
|
74
69
|
|
75
70
|
describe "non-container-type properties" do
|
76
|
-
it "
|
77
|
-
@props[:color][:inherit].
|
78
|
-
@props[:score][:inherit].
|
79
|
-
@props[:rank][:inherit].
|
80
|
-
@props[:style][:inherit].
|
81
|
-
@props[:available][:inherit].
|
82
|
-
|
83
|
-
B.color.
|
84
|
-
B.score.
|
85
|
-
B.rank.
|
86
|
-
B.style.
|
87
|
-
B.available.
|
88
|
-
|
89
|
-
C.color.
|
90
|
-
C.score.
|
91
|
-
C.rank.
|
92
|
-
C.style.
|
93
|
-
C.available.
|
71
|
+
it "inherit non-blank values from first non-blank ancestor" do
|
72
|
+
@props[:color][:inherit].should be_true
|
73
|
+
@props[:score][:inherit].should be_true
|
74
|
+
@props[:rank][:inherit].should be_true
|
75
|
+
@props[:style][:inherit].should be_true
|
76
|
+
@props[:available][:inherit].should be_true
|
77
|
+
|
78
|
+
B.color.should == "red"
|
79
|
+
B.score.should == 45.9
|
80
|
+
B.rank.should == 18
|
81
|
+
B.style.should == :spotted
|
82
|
+
B.available.should be_true
|
83
|
+
|
84
|
+
C.color.should == "red"
|
85
|
+
C.score.should == 45.9
|
86
|
+
C.rank.should == 18
|
87
|
+
C.style.should == :spotted
|
88
|
+
C.available.should == true
|
94
89
|
|
95
90
|
B.score = 48.2
|
96
91
|
B.style = :rugged
|
97
92
|
B.available = false
|
98
93
|
|
99
|
-
B.score.
|
100
|
-
C.score.
|
94
|
+
B.score.should == 48.2
|
95
|
+
C.score.should == 48.2
|
101
96
|
|
102
|
-
B.style.
|
103
|
-
C.style.
|
97
|
+
B.style.should == :rugged
|
98
|
+
C.style.should == :rugged
|
104
99
|
|
105
|
-
B.available.
|
106
|
-
C.available.
|
100
|
+
B.available.should be_false
|
101
|
+
C.available.should be_false
|
107
102
|
end
|
108
103
|
end
|
109
104
|
|
110
105
|
describe "container-type properties" do
|
111
106
|
it "does not inherit ancestor properties" do
|
112
|
-
@props[:locations][:inherit].
|
113
|
-
@props[:name][:inherit].
|
107
|
+
@props[:locations][:inherit].should be_false
|
108
|
+
@props[:name][:inherit].should be_false
|
114
109
|
|
115
|
-
B.locations.
|
116
|
-
C.locations.
|
117
|
-
B.name.
|
118
|
-
C.name.
|
110
|
+
B.locations.should == Array.new
|
111
|
+
C.locations.should == Array.new
|
112
|
+
B.name.should == Hash.new
|
113
|
+
C.name.should == Hash.new
|
119
114
|
|
120
115
|
B.locations = [:east, :west]
|
121
116
|
B.name = {:first => 'adam', :last => 'phillips'}
|
122
117
|
|
123
|
-
B.locations.
|
124
|
-
C.locations.
|
125
|
-
B.name.
|
126
|
-
C.name.
|
118
|
+
B.locations.should == [:east, :west]
|
119
|
+
C.locations.should == Array.new
|
120
|
+
B.name.should == ({:first => 'adam', :last => 'phillips'})
|
121
|
+
C.name.should == Hash.new
|
127
122
|
end
|
128
123
|
end
|
129
124
|
|
130
125
|
describe "proc properties" do
|
131
126
|
it "inherits by default" do
|
132
|
-
@props[:classified][:inherit].
|
127
|
+
@props[:classified][:inherit].should == true
|
133
128
|
end
|
134
129
|
|
135
130
|
it "evaluates the proc in context of each descendent" do
|
@@ -137,9 +132,9 @@ describe "basics" do
|
|
137
132
|
B.score = 30.1
|
138
133
|
C.score = 45.2
|
139
134
|
|
140
|
-
A.classified.
|
141
|
-
B.classified.
|
142
|
-
C.classified.
|
135
|
+
A.classified.should be_true
|
136
|
+
B.classified.should be_false
|
137
|
+
C.classified.should be_true
|
143
138
|
end
|
144
139
|
end
|
145
140
|
end
|
@@ -152,25 +147,25 @@ describe "basics" do
|
|
152
147
|
|
153
148
|
it "expects two parameters: property_name, ancestors" do
|
154
149
|
A.color{|color_a, parents|
|
155
|
-
color_a.
|
156
|
-
parents.
|
150
|
+
color_a.should == "red"
|
151
|
+
parents.should == []
|
157
152
|
}
|
158
153
|
|
159
154
|
B.color{|color_b, parents|
|
160
|
-
color_b.
|
161
|
-
parents.
|
155
|
+
color_b.should == "blue"
|
156
|
+
parents.should == [A]
|
162
157
|
}
|
163
158
|
|
164
159
|
C.color{|color_c, parents|
|
165
|
-
color_c.
|
166
|
-
parents.
|
160
|
+
color_c.should == "purple"
|
161
|
+
parents.should == [B, A]
|
167
162
|
}
|
168
163
|
end
|
169
164
|
|
170
165
|
it "returns last expression of block" do
|
171
|
-
A.color{|my_color| my_color.reverse}.
|
172
|
-
B.color{|my_color| my_color.capitalize}.
|
173
|
-
C.color{|my_color| my_color * 2}.
|
166
|
+
A.color{|my_color| my_color.reverse}.should == "der"
|
167
|
+
B.color{|my_color| my_color.capitalize}.should == "Blue"
|
168
|
+
C.color{|my_color| my_color * 2}.should == "purplepurple"
|
174
169
|
end
|
175
170
|
end
|
176
171
|
|
@@ -182,17 +177,17 @@ describe "basics" do
|
|
182
177
|
end
|
183
178
|
|
184
179
|
it "descendents whose property is blank, inherit (invoke) the proc" do
|
185
|
-
B.color.
|
180
|
+
B.color.should == "Red"
|
186
181
|
|
187
182
|
sec = Time.now.sec
|
188
|
-
B.rank.
|
189
|
-
C.rank.
|
183
|
+
B.rank.should == sec
|
184
|
+
C.rank.should == sec
|
190
185
|
|
191
186
|
sleep(2)
|
192
187
|
|
193
188
|
sec = Time.now.sec
|
194
|
-
B.rank.
|
195
|
-
C.rank.
|
189
|
+
B.rank.should == sec
|
190
|
+
C.rank.should == sec
|
196
191
|
end
|
197
192
|
end
|
198
193
|
end
|
@@ -218,13 +213,13 @@ describe "to_hash" do
|
|
218
213
|
end
|
219
214
|
|
220
215
|
it "returns hash of property name/values" do
|
221
|
-
A.to_hash.
|
216
|
+
A.to_hash.should ==({:color => "red",
|
222
217
|
:name => {:first => 'jon',
|
223
218
|
:last => 'mackey'}
|
224
219
|
})
|
225
220
|
end
|
226
221
|
|
227
222
|
it "applies default getter logic: non-container-types inherit, container-types don't" do
|
228
|
-
B.to_hash.
|
223
|
+
B.to_hash.should ==({:color => "red", :name => {}})
|
229
224
|
end
|
230
225
|
end
|