Cascading Classes

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.

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:

Parent.last_name = 'brownstein'
Parent.first_name = "charlie"
Parent.name = Proc.new{|c| c.first_name + ' ' + c.last_name}

Child.first_name = "sam"

p Child.name     #  =>  "sam brownstein"

You have been warned!

Take it for a quick spin

Assume the following simple class hierarchy:

class A
  extend CC
end

class B < A
end

class C < B
end

Notice that CC is an alias for CascadingClasses

class A
  extend CC
end

is equivalent to

class A
  extend CascadingClasses
end

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:

class Person
  extend CC
end

Person.cascade do
  language default: "english"
end

person = Person.new

p Person.language     #  =>  "english"
p person.language     #  =>  NoMethodError: undefined method 'language' for #<Person:0x0000...>

compare this to:

class Person
  include CC
end

Person.cascade do
  language default: "english"
end

person = Person.new

p Person.language     #  =>  "english"
p person.language     #  =>  "english"

The person object doesn't have the language property in one and does in another.

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

Continuing the tour

Let's create a new example

Class A
  extend CC
end

Class B < A; end

Class C < B; end

A is the top-level parent. B is its child. C is the child of B.

A  <  B  <  C

create name and city properties:

A.cascade do
  name default: "Tom"
  city default: "New York"
end

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.

p A.name     #  =>  "Tom"
p B.name     #  =>  "Tom"
p C.name     #  =>  "Tom"

Each descendent inherits its parent's value if unset.

p A.name_is_unset?     #  =>  false
p B.name_is_unset?     #  =>  true
p C.name_is_unset?     #  =>  true

p A.name     #  =>  "Tom"     
p B.name     #  =>  "Tom"
p C.name     #  =>  "Tom"

Notice the dynamic class method name_is_unset? created from the name property.

Naturally properties can be read and written to. Let's update the name on B:

B.name = "John"
p B.name     #  =>  "John"

It's crucuial to see how this effects the descendents of B. Notice how this value cascades down to C

p C.name     #  =>  "John"

At any point in time we can add nodes anywhere along the tree: up or down:

class B2 < A
end

class C2 < B2
end

class D < C
end

Our tree now looks like the following:

                                           _______  D
                                         /
                                        /
                         ________   C   --------  ..
                       /    
                      /
        ________  B   --------  ..
      /               \ 
     /                 \ ________  ..
    /        
A   --------  ..         ________  ..
    \                  / 
     \                /                   ________  ..
      \ ________  B2  --------  ..       /
                      \                 /
                       \ ________   C2  -------- ..

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 sensiblename and city.

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

B.cascade do
  state default: "MA"
end

p B.state     #  =>  "MA"
p C.state     #  =>  "MA"
p D.state     #  =>  "MA"

Another simple example

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.

class Parent
  include CC
end

Parent.cascade do
  color default: "red"
  width default: 100
  height default: 50
end

class Div1 < Parent; end
class Div2 < Parent; end

div1 = Div1.new
div2 = Div2.new

Every object and class in this hierarchy has a color, height, and width.

p Div1.color     #  =>  "red"
p div1.color     #  =>  "red"

Div1.color = "blue"

p Div1.color     #  =>  "blue"
p div1.color     #  =>  "blue"

div1.color = "black"

p Div1.color     #  =>  "blue"
p div1.color     #  =>  "black"

We can collect the property values by calling to_hash

p Div1.to_hash     #  =>  {:color=>"blue", :width=>100, :height=>50}

If you only want to see the hash of properties that have been set, pass false

p Div1.to_hash            #  =>  {:color=>"blue", :width=>100, :height=>50}
p Div1.to_hash(false)     #  =>  {:color=>"blue"}

Notice that we have only set the color property on Div1. Let's give a height different from its parent.

Div1.height = 75

p Div1.to_hash(false)     #  =>  {:color=>"blue", :height=>75}

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