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 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
- __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:
5
+ __Warning:__ This library commits the blasphemous crime of utilizing classes as objects. Stuff like this:
6
6
 
7
- Parent.last_name = 'brownstein'
8
- Parent.first_name = "charlie"
9
- Parent.name = Proc.new{|c| c.first_name + ' ' + c.last_name}
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. 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:
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.cascade do
50
- language default: "english"
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 Person.language # => "english"
56
- p person.language # => NoMethodError: undefined method 'language' for #<Person:0x0000...>
70
+ p Brit.language # => "english"
71
+ p john.language # => NoMethodError: undefined method 'language' for #<Person:0x0000...>
57
72
 
58
- compare this to:
73
+ compare that to the following:
59
74
 
60
75
  class Person
61
76
  include CC
62
- end
63
77
 
64
- Person.cascade do
65
- language default: "english"
78
+ cascade do
79
+ language default: "english"
80
+ end
66
81
  end
67
82
 
68
- person = Person.new
83
+ Brit = Class.new(Person)
84
+ john = Person.new
69
85
 
70
- p Person.language # => "english"
71
- p person.language # => "english"
86
+ p Brit.language # => "english"
87
+ p john.language # => "english"
72
88
 
73
- The `person` object doesn't have the ```language``` property in one and does in another.
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 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*
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
- 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```.
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
- Each descendent *inherits* its parent's value if unset.
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
- At any point in time we can add nodes anywhere along the tree: up or down:
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 tree now looks like the following:
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
- 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```.
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, we let's add the ```state``` property to all descendents of ```B```
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 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```.
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
- end
178
-
179
- Parent.cascade do
180
- color default: "red"
181
- width default: 100
182
- height default: 50
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 object and class in this hierarchy has a color, height, and width.
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
- Div1.color = "blue"
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
- p Div1.color # => "blue"
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
- If you only want to see the hash of properties that have been set, pass ```false```
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
- Notice that we have only set the ```color``` property on ```Div1```. Let's give a height different from its parent.
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
- 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](#)
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
+ }
@@ -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 unknown type of :Object" do
30
- @props[:wings][:type].must_equal :undefined_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 "has set with the default value" do
52
- A.color.must_equal "red"
53
- A.score.must_equal 45.9
54
- A.rank.must_equal 18
55
- A.style.must_equal :spotted
56
- A.available.must_equal true
57
- A.locations.must_equal [:south, :north]
58
- A.name.must_equal({:first => 'jon', :last => 'smith'})
59
- A.classified.must_equal true
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 "has access to the 'default' value" do
65
- A.color(:default).must_equal "red"
60
+ it "can access the defaults" do
61
+ A.color(:default).should == "red"
66
62
  A.color = "blue"
67
- A.color(:default).must_equal "blue"
68
-
63
+ A.color(:default).should == "red"
69
64
  B.color = "white"
70
- B.color.must_equal "white"
71
- B.color(:default).must_equal "red"
72
- C.color(:default).must_equal "red"
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 "inherits non-blank values from first non-blank ancestor" do
77
- @props[:color][:inherit].must_equal true
78
- @props[:score][:inherit].must_equal true
79
- @props[:rank][:inherit].must_equal true
80
- @props[:style][:inherit].must_equal true
81
- @props[:available][:inherit].must_equal true
82
-
83
- B.color.must_equal "red"
84
- B.score.must_equal 45.9
85
- B.rank.must_equal 18
86
- B.style.must_equal :spotted
87
- B.available.must_equal true
88
-
89
- C.color.must_equal "red"
90
- C.score.must_equal 45.9
91
- C.rank.must_equal 18
92
- C.style.must_equal :spotted
93
- C.available.must_equal true
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.must_equal 48.2
100
- C.score.must_equal 48.2
94
+ B.score.should == 48.2
95
+ C.score.should == 48.2
101
96
 
102
- B.style.must_equal :rugged
103
- C.style.must_equal :rugged
97
+ B.style.should == :rugged
98
+ C.style.should == :rugged
104
99
 
105
- B.available.must_equal false
106
- C.available.must_equal false
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].must_equal false
113
- @props[:name][:inherit].must_equal false
107
+ @props[:locations][:inherit].should be_false
108
+ @props[:name][:inherit].should be_false
114
109
 
115
- B.locations.must_equal Array.new
116
- C.locations.must_equal Array.new
117
- B.name.must_equal Hash.new
118
- C.name.must_equal Hash.new
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.must_equal [:east, :west]
124
- C.locations.must_equal Array.new
125
- B.name.must_equal({:first => 'adam', :last => 'phillips'})
126
- C.name.must_equal Hash.new
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].must_equal true
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.must_equal true
141
- B.classified.must_equal false
142
- C.classified.must_equal true
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.must_equal "red"
156
- parents.must_equal []
150
+ color_a.should == "red"
151
+ parents.should == []
157
152
  }
158
153
 
159
154
  B.color{|color_b, parents|
160
- color_b.must_equal "blue"
161
- parents.must_equal [A]
155
+ color_b.should == "blue"
156
+ parents.should == [A]
162
157
  }
163
158
 
164
159
  C.color{|color_c, parents|
165
- color_c.must_equal "purple"
166
- parents.must_equal [B, A]
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}.must_equal "der"
172
- B.color{|my_color| my_color.capitalize}.must_equal "Blue"
173
- C.color{|my_color| my_color * 2}.must_equal "purplepurple"
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.must_equal "Red"
180
+ B.color.should == "Red"
186
181
 
187
182
  sec = Time.now.sec
188
- B.rank.must_equal sec
189
- C.rank.must_equal sec
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.must_equal sec
195
- C.rank.must_equal sec
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.must_equal({:color => "red",
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.must_equal({:color => "red", :name => {}})
223
+ B.to_hash.should ==({:color => "red", :name => {}})
229
224
  end
230
225
  end