cascading_classes 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +190 -0
- data/Rakefile +7 -0
- data/lib/cascading_classes/cascading_classes.rb +163 -0
- data/lib/cascading_classes.rb +75 -0
- data/spec/alternative_syntax_spec.rb +146 -0
- data/spec/extended_spec.rb +133 -0
- data/spec/included_spec.rb +85 -0
- metadata +54 -0
data/README.md
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
# CascadingClasses
|
2
|
+
|
3
|
+
This library allows you to easily create properties that cascade down a class tree. For example:
|
4
|
+
|
5
|
+
require 'cascading_classes'
|
6
|
+
|
7
|
+
class Parent
|
8
|
+
extend CC
|
9
|
+
end
|
10
|
+
|
11
|
+
class Child < Parent; end
|
12
|
+
|
13
|
+
class GrandChild < Child; end
|
14
|
+
|
15
|
+
now create two properties that will cascade down all descendents
|
16
|
+
|
17
|
+
Parent.cascade :eye_color, :hair_color
|
18
|
+
|
19
|
+
set the properties on ```Parent```
|
20
|
+
|
21
|
+
Parent.eye_color = "brown"
|
22
|
+
Parent.hair_color = "black"
|
23
|
+
|
24
|
+
all child classes are affected
|
25
|
+
|
26
|
+
p Child.eye_color # => "brown"
|
27
|
+
p GrandChild.eye_color # => "brown"
|
28
|
+
|
29
|
+
p Child.hair_color # => "black"
|
30
|
+
p GrandChild.hair_color # => "black"
|
31
|
+
|
32
|
+
now change the ```eye_color``` and ```hair_color``` properties on ```Child```
|
33
|
+
|
34
|
+
Child.eye_color = "blue"
|
35
|
+
Child.hair_color = "blond"
|
36
|
+
|
37
|
+
only descendents of ```Child``` are affected
|
38
|
+
|
39
|
+
p Parent.eye_color # => "brown"
|
40
|
+
p Child.eye_color # => "blue"
|
41
|
+
p GrandChild.eye_color # => "blue"
|
42
|
+
|
43
|
+
|
44
|
+
Getting Started
|
45
|
+
---------------
|
46
|
+
|
47
|
+
Assume the class structure from above:
|
48
|
+
|
49
|
+
class Parent
|
50
|
+
extend CC
|
51
|
+
end
|
52
|
+
|
53
|
+
class Child < Parent; end
|
54
|
+
|
55
|
+
class GrandChild < Child; end
|
56
|
+
|
57
|
+
## Setting default values
|
58
|
+
|
59
|
+
Setting default values for cascaded properties are easy. The first method is to bracket them in Array pairs. For example:
|
60
|
+
|
61
|
+
Parent.cascade [:eye_color, "hazel"], [:hair_color, "brown"], :has_education
|
62
|
+
|
63
|
+
sets the ```eye_color``` and ```hair_color``` properties on ```Parent``` to "hazel" and "brown" and initializes, but does not set, the ```has_education``` property.
|
64
|
+
|
65
|
+
|
66
|
+
### Alternative syntax: all!
|
67
|
+
|
68
|
+
You also create cascaded properties via ```Parent.all!```
|
69
|
+
|
70
|
+
For example, the following are equivalent:
|
71
|
+
|
72
|
+
Parent.cascade :arms, :legs, :head
|
73
|
+
Parent.all! :arms, :legs, :head
|
74
|
+
|
75
|
+
this initiates, but does not set, two cascading properties: ```arms```, ```legs```
|
76
|
+
|
77
|
+
p Parent.arms # => nil
|
78
|
+
p Child.arms # => nil
|
79
|
+
p GrandChild.arms # => nil
|
80
|
+
|
81
|
+
since no default was given, all are nil
|
82
|
+
|
83
|
+
Again, to set a default use brackets:
|
84
|
+
|
85
|
+
Parent.all! :arms, [:legs, 2], [:head, :none]
|
86
|
+
|
87
|
+
|
88
|
+
### Alternative syntax: block arguments
|
89
|
+
|
90
|
+
You can also create cascading properties with a block. The major difference is that you must not prefix your property with a colon.
|
91
|
+
|
92
|
+
For example
|
93
|
+
|
94
|
+
Parent.cascade [:eye_color, "blue"], [:hair_color, "blond"]
|
95
|
+
|
96
|
+
is equivalent to
|
97
|
+
|
98
|
+
Parent.cascade do
|
99
|
+
eye_color :default => "blue"
|
100
|
+
hair_color :default => "blond"
|
101
|
+
end
|
102
|
+
|
103
|
+
or
|
104
|
+
|
105
|
+
Parent.all! do
|
106
|
+
eye_color :initial => "blue"
|
107
|
+
hair_color :initial => "blond"
|
108
|
+
end
|
109
|
+
|
110
|
+
#### More block examples
|
111
|
+
|
112
|
+
The folowing are all equivalent
|
113
|
+
==============================
|
114
|
+
|
115
|
+
Parent.cascade :address, :phone, [:email, "jon@example.com"]
|
116
|
+
|
117
|
+
Parent.cascade :address, :phone do
|
118
|
+
email :initial => "jon@example.com"
|
119
|
+
end
|
120
|
+
|
121
|
+
Parent.all! do
|
122
|
+
address
|
123
|
+
phone
|
124
|
+
email :start => "jon@example.com"
|
125
|
+
end
|
126
|
+
|
127
|
+
Parent.all!{ address; phone; email :default => "jon@example.com" }
|
128
|
+
|
129
|
+
You can also set a property in a block using ```set_property```
|
130
|
+
|
131
|
+
Parent.cascade do
|
132
|
+
set_property :address
|
133
|
+
set_property :phone
|
134
|
+
set_property :email, :default => "jon@example.com"
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
## Quick Summary
|
139
|
+
|
140
|
+
* require the gem: ```require cascading_classes```
|
141
|
+
* extend or include the module
|
142
|
+
class Foo
|
143
|
+
extend CascadingClasses
|
144
|
+
end
|
145
|
+
* CC is equivalent to CascadingClasses
|
146
|
+
* create some cascading properties by listing them
|
147
|
+
A.cascade :desk, :chair, :lamp
|
148
|
+
* or by using a block
|
149
|
+
A.cascade{ desk; chair; lamp }
|
150
|
+
* get and set the property on any descendent
|
151
|
+
class B < A; end
|
152
|
+
B.desk = "a large, wooden structure in need of a fix"
|
153
|
+
class C < A; end
|
154
|
+
C.lamp = "old and rusty"
|
155
|
+
|
156
|
+
### What's the difference between ```extend CC``` and ```include CC```?
|
157
|
+
|
158
|
+
First, in either case you are dealing with class instance variables and class methods. ```A.cascade :eye_color; A.eye_color``` sets ```@eye_color``` on the singleton class of A.
|
159
|
+
|
160
|
+
But if you also want the properties to cascade down to instance variables you have three options. The first way is to ```include CC```, which will create cascading instance properties by default.
|
161
|
+
|
162
|
+
For example:
|
163
|
+
|
164
|
+
class Parent
|
165
|
+
include CC
|
166
|
+
end
|
167
|
+
|
168
|
+
Parent.cascade [:eye_color, "blue"]
|
169
|
+
p = Parent.new
|
170
|
+
p p.eye_color # => "blue"
|
171
|
+
|
172
|
+
class Child < Parent; end
|
173
|
+
|
174
|
+
c = Child.new
|
175
|
+
p c.eye_color # => "blue"
|
176
|
+
|
177
|
+
The other two ways to make properties cascade down to instances is via:
|
178
|
+
|
179
|
+
Parent.cascade [:hair_color, "blue", true]
|
180
|
+
|
181
|
+
or
|
182
|
+
|
183
|
+
Parent.cascade do
|
184
|
+
hair_color :default => "blue", :instances => true
|
185
|
+
end
|
186
|
+
|
187
|
+
In either case, even if you ```extend CC```, all instance variables of all descendents will have the ```hair_color``` property.
|
188
|
+
|
189
|
+
|
190
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
module CascadingClasses
|
2
|
+
|
3
|
+
# based off
|
4
|
+
# http://blog.oleganza.com/post/115377756/correct-blankslate-in-ruby
|
5
|
+
class BlankSlate
|
6
|
+
class << self; alias __undef_method undef_method; end
|
7
|
+
keep = [:instance_eval, :object_id]
|
8
|
+
ancestors.inject([]){|res, a| res + (a.instance_methods - keep)}.uniq.
|
9
|
+
each{|m| (__undef_method(m) rescue nil) unless m =~ /^__/ }
|
10
|
+
end
|
11
|
+
|
12
|
+
class Helper < CascadingClasses::BlankSlate
|
13
|
+
|
14
|
+
def method_missing(meth, *args, &block)
|
15
|
+
case meth
|
16
|
+
when /inspect/
|
17
|
+
super
|
18
|
+
when /set_property/
|
19
|
+
__property__(args[0], *args[1..-1], &block)
|
20
|
+
else
|
21
|
+
__property__(meth, *args, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def __property__(name, args={}, &block)
|
26
|
+
@names << name unless @names.include? name
|
27
|
+
|
28
|
+
@defaults[name] = if args.include? :default
|
29
|
+
args[:default]
|
30
|
+
elsif args.include? :initial
|
31
|
+
args[:initial]
|
32
|
+
elsif args.include? :start
|
33
|
+
args[:start]
|
34
|
+
elsif args.include? :begin
|
35
|
+
args[:begin]
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
@instances_too[name] = if args.include? :classes_only
|
41
|
+
!args[:classes_only]
|
42
|
+
elsif args.include? :only_classes
|
43
|
+
!args[:only_classes]
|
44
|
+
elsif args.include? :classes_only
|
45
|
+
!args[:classes_only]
|
46
|
+
elsif args.include? :exclude_instances
|
47
|
+
!args[:exclude_instances]
|
48
|
+
elsif args.include? :skip_instances
|
49
|
+
!args[:skip_instances]
|
50
|
+
elsif args.include? :not_instances
|
51
|
+
!args[:not_instances]
|
52
|
+
elsif args.include? :apply_to_instances
|
53
|
+
args[:apply_to_instances]
|
54
|
+
elsif args.include? :both
|
55
|
+
args[:both]
|
56
|
+
elsif args.include? :instances_too
|
57
|
+
args[:instances_too]
|
58
|
+
elsif args.include? :instances
|
59
|
+
args[:instances]
|
60
|
+
elsif args.include? :instance
|
61
|
+
args[:instance]
|
62
|
+
else
|
63
|
+
@apply_to_instances
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(apply_to_instances, &block)
|
68
|
+
@apply_to_instances = apply_to_instances
|
69
|
+
@names = []
|
70
|
+
@defaults = {}
|
71
|
+
@instances_too = {} # values are true/false
|
72
|
+
|
73
|
+
return unless block_given?
|
74
|
+
if block.arity == 0
|
75
|
+
instance_eval &block
|
76
|
+
else
|
77
|
+
raise "ArgumentError: wrong number of args for block: #{block.arity} for 1"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end # class Helper
|
82
|
+
|
83
|
+
# returns an array
|
84
|
+
# apply_to_instances: whether properties should be set on instances of the classes too
|
85
|
+
def self.cascading_props(apply_to_instances, *properties, &block)
|
86
|
+
props = {} # values are defaults
|
87
|
+
instances_too = {} # values are true/false
|
88
|
+
properties.each do |prop|
|
89
|
+
if Array === prop and prop.size == 3
|
90
|
+
props[prop[0].to_sym] = prop[1]
|
91
|
+
instances_too[prop[0].to_sym] = !!prop[2]
|
92
|
+
elsif Array === prop and prop.size == 2
|
93
|
+
props[prop[0].to_sym] = prop[1]
|
94
|
+
else
|
95
|
+
props[prop.to_sym] = nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
props.each{|prop, default| instances_too[prop] =
|
100
|
+
apply_to_instances unless instances_too.include? prop}
|
101
|
+
|
102
|
+
if block_given?
|
103
|
+
a = Helper.new(apply_to_instances, &block)
|
104
|
+
names, defaults, inst =
|
105
|
+
a.instance_eval{ [@names, @defaults, @instances_too] }
|
106
|
+
names.each do |prop|
|
107
|
+
props[prop] = defaults[prop]
|
108
|
+
instances_too[prop] = inst[prop]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
props.map do |prop, default|
|
113
|
+
mod = Module.new do
|
114
|
+
|
115
|
+
define_method :extended do |klass|
|
116
|
+
klass.class_eval{ instance_variable_set "@#{prop}", default }
|
117
|
+
end
|
118
|
+
module_function :extended
|
119
|
+
|
120
|
+
define_method :included do |klass|
|
121
|
+
klass.class_eval{ instance_variable_set "@#{prop}", default }
|
122
|
+
end
|
123
|
+
module_function :included
|
124
|
+
|
125
|
+
# getter has dual role as setter:
|
126
|
+
# m.prop val <=> m.prop = val
|
127
|
+
define_method prop do |*args|
|
128
|
+
if args.empty? # getter
|
129
|
+
val = instance_variable_get "@#{prop}"
|
130
|
+
if val.nil?
|
131
|
+
# reach up into the class first, then into superclass
|
132
|
+
if self.class == Class
|
133
|
+
try_super = superclass.instance_eval{ respond_to? "#{prop}" }
|
134
|
+
return superclass.send("#{prop}") if try_super
|
135
|
+
else
|
136
|
+
try_klass_methd = self.class.send :respond_to?, prop
|
137
|
+
return self.class.send(prop) if try_klass_methd
|
138
|
+
end
|
139
|
+
end
|
140
|
+
val
|
141
|
+
else # setter
|
142
|
+
if args.size == 1 and Array === args[0]
|
143
|
+
send("#{prop}=", args[0])
|
144
|
+
else
|
145
|
+
send("#{prop}=", *args)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# todo: setter validations ??
|
151
|
+
define_method "#{prop}=" do |*vals|
|
152
|
+
if vals.size == 1 and Array === vals[0]
|
153
|
+
vals = vals[0]
|
154
|
+
end
|
155
|
+
instance_variable_set "@#{prop}", vals[0]
|
156
|
+
end
|
157
|
+
|
158
|
+
end # new module
|
159
|
+
[prop, props[prop], instances_too[prop], mod]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end # module CascadingClasses
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'cascading_classes/cascading_classes'
|
2
|
+
|
3
|
+
CC = CascadingClasses unless defined? CC
|
4
|
+
|
5
|
+
# trivialities:
|
6
|
+
# module not meant to be both included and extended
|
7
|
+
|
8
|
+
|
9
|
+
# class Foo
|
10
|
+
# include CC
|
11
|
+
# cascade :one, :two, :three, ...
|
12
|
+
# end
|
13
|
+
# Foo.one = 12
|
14
|
+
# Foo.new.one
|
15
|
+
# Bar = Class.new(Foo).one
|
16
|
+
|
17
|
+
module CascadingClasses
|
18
|
+
|
19
|
+
# VERSION: "1.0.0"
|
20
|
+
# note, VERSION will be undetected unless called directly
|
21
|
+
def self.const_missing(name)
|
22
|
+
case name
|
23
|
+
when :VERSION then "1.0.0"
|
24
|
+
else raise NameError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def respond_to?(const)
|
29
|
+
(const.to_s == 'VERSION') ? true : super
|
30
|
+
end
|
31
|
+
module_function :respond_to?
|
32
|
+
|
33
|
+
def self.included(klass)
|
34
|
+
has_all = klass.instance_methods.include? :all!
|
35
|
+
klass.instance_eval do
|
36
|
+
def cascade(*props, &block)
|
37
|
+
res = CascadingClasses.cascading_props(true, *props, &block)
|
38
|
+
res.map! do |prop, default, apply_to_instances, mod|
|
39
|
+
send(:extend, mod)
|
40
|
+
send(:include, mod) if apply_to_instances
|
41
|
+
[prop, [default, apply_to_instances]]
|
42
|
+
end
|
43
|
+
{self.name => Hash[res]}
|
44
|
+
end
|
45
|
+
|
46
|
+
class << klass
|
47
|
+
unless instance_methods.include? :all!
|
48
|
+
alias_method :all!, :cascade
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.extended(klass)
|
55
|
+
has_all = klass.instance_methods.include? :all
|
56
|
+
klass.instance_eval do
|
57
|
+
def cascade(*props, &block)
|
58
|
+
res = CascadingClasses.cascading_props(false, *props, &block)
|
59
|
+
res.map! do |prop, default, apply_to_instances, mod|
|
60
|
+
send(:extend, mod)
|
61
|
+
send(:include, mod) if apply_to_instances
|
62
|
+
[prop, [default, apply_to_instances]]
|
63
|
+
end
|
64
|
+
{self.name => Hash[res]}
|
65
|
+
end
|
66
|
+
|
67
|
+
class << klass
|
68
|
+
unless instance_methods.include? :all!
|
69
|
+
alias_method :all!, :cascade
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end # module CascadingClases
|
@@ -0,0 +1,146 @@
|
|
1
|
+
begin
|
2
|
+
require_relative 'helper_spec'
|
3
|
+
rescue NameError
|
4
|
+
require File.expand_path('helper_spec', __FILE__)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe "when a class extends CascadingClasses and an alternative syntax is used" do
|
8
|
+
|
9
|
+
# this is the same as:
|
10
|
+
# class Parent
|
11
|
+
# extend CascadingClasses
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class Child < Parent; end
|
15
|
+
#
|
16
|
+
# class GrandChild < Child; end
|
17
|
+
before do
|
18
|
+
Parent = Class.new{extend CC}
|
19
|
+
Child = Class.new(Parent)
|
20
|
+
GrandChild = Class.new(Child)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "when an alternative syntax is given (0)" do
|
24
|
+
before do
|
25
|
+
Parent.cascade [:hair_color, "black"], :eye_color, [:piercings, false]
|
26
|
+
|
27
|
+
Child.hair_color = "blond"
|
28
|
+
Child.eye_color = "brown"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should give the same result" do
|
32
|
+
Parent.hair_color.must_equal "black"
|
33
|
+
Child.hair_color.must_equal "blond"
|
34
|
+
GrandChild.hair_color.must_equal "blond"
|
35
|
+
|
36
|
+
Parent.eye_color.must_equal nil
|
37
|
+
Child.eye_color.must_equal "brown"
|
38
|
+
GrandChild.eye_color.must_equal "brown"
|
39
|
+
|
40
|
+
Parent.piercings.must_equal false
|
41
|
+
Child.piercings.must_equal false
|
42
|
+
GrandChild.piercings.must_equal false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "when an alternate syntax is given (1)" do
|
47
|
+
before do
|
48
|
+
Parent.all! [:hair_color, "black"], :eye_color, [:piercings, false]
|
49
|
+
|
50
|
+
Child.hair_color = "blond"
|
51
|
+
Child.eye_color = "brown"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should give the same result" do
|
55
|
+
Parent.hair_color.must_equal "black"
|
56
|
+
Child.hair_color.must_equal "blond"
|
57
|
+
GrandChild.hair_color.must_equal "blond"
|
58
|
+
|
59
|
+
Parent.eye_color.must_equal nil
|
60
|
+
Child.eye_color.must_equal "brown"
|
61
|
+
GrandChild.eye_color.must_equal "brown"
|
62
|
+
|
63
|
+
Parent.piercings.must_equal false
|
64
|
+
Child.piercings.must_equal false
|
65
|
+
GrandChild.piercings.must_equal false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "when an alternative syntax is given (2)" do
|
70
|
+
before do
|
71
|
+
Parent.cascade :eye_color do
|
72
|
+
hair_color :begin => "black"
|
73
|
+
piercings :initial => false
|
74
|
+
end
|
75
|
+
|
76
|
+
Child.hair_color = "blond"
|
77
|
+
Child.eye_color = "brown"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should give the same result" do
|
81
|
+
Parent.hair_color.must_equal "black"
|
82
|
+
Child.hair_color.must_equal "blond"
|
83
|
+
GrandChild.hair_color.must_equal "blond"
|
84
|
+
|
85
|
+
Parent.eye_color.must_equal nil
|
86
|
+
Child.eye_color.must_equal "brown"
|
87
|
+
GrandChild.eye_color.must_equal "brown"
|
88
|
+
|
89
|
+
Parent.piercings.must_equal false
|
90
|
+
Child.piercings.must_equal false
|
91
|
+
GrandChild.piercings.must_equal false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "when an alternative syntax is given (3)" do
|
96
|
+
before do
|
97
|
+
Parent.all! :eye_color do
|
98
|
+
hair_color :start => "black"
|
99
|
+
piercings :default => false
|
100
|
+
end
|
101
|
+
|
102
|
+
Child.hair_color = "blond"
|
103
|
+
Child.eye_color = "brown"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should give the same result" do
|
107
|
+
Parent.hair_color.must_equal "black"
|
108
|
+
Child.hair_color.must_equal "blond"
|
109
|
+
GrandChild.hair_color.must_equal "blond"
|
110
|
+
|
111
|
+
Parent.eye_color.must_equal nil
|
112
|
+
Child.eye_color.must_equal "brown"
|
113
|
+
GrandChild.eye_color.must_equal "brown"
|
114
|
+
|
115
|
+
Parent.piercings.must_equal false
|
116
|
+
Child.piercings.must_equal false
|
117
|
+
GrandChild.piercings.must_equal false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "when an alternative syntax is given (3)" do
|
122
|
+
before do
|
123
|
+
Parent.all! :eye_color do
|
124
|
+
set_property :hair_color, :start => "black"
|
125
|
+
set_property :piercings, :initial => false
|
126
|
+
end
|
127
|
+
|
128
|
+
Child.hair_color = "blond"
|
129
|
+
Child.eye_color = "brown"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should give the same result" do
|
133
|
+
Parent.hair_color.must_equal "black"
|
134
|
+
Child.hair_color.must_equal "blond"
|
135
|
+
GrandChild.hair_color.must_equal "blond"
|
136
|
+
|
137
|
+
Parent.eye_color.must_equal nil
|
138
|
+
Child.eye_color.must_equal "brown"
|
139
|
+
GrandChild.eye_color.must_equal "brown"
|
140
|
+
|
141
|
+
Parent.piercings.must_equal false
|
142
|
+
Child.piercings.must_equal false
|
143
|
+
GrandChild.piercings.must_equal false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
begin
|
2
|
+
require_relative 'helper_spec'
|
3
|
+
rescue NameError
|
4
|
+
require File.expand_path('helper_spec', __FILE__)
|
5
|
+
end
|
6
|
+
|
7
|
+
# require 'minitest/autorun'
|
8
|
+
# require 'cascading_classes'
|
9
|
+
|
10
|
+
# class << MiniTest::Spec
|
11
|
+
# alias_method :all, :it
|
12
|
+
# end
|
13
|
+
|
14
|
+
describe "when a class extends CascadingClasses" do
|
15
|
+
|
16
|
+
# this is the same as:
|
17
|
+
# class Parent
|
18
|
+
# extend CascadingClasses
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# class Child < Parent; end
|
22
|
+
#
|
23
|
+
# class GrandChild < Child; end
|
24
|
+
before do
|
25
|
+
Parent = Class.new{extend CC}
|
26
|
+
Child = Class.new(Parent)
|
27
|
+
GrandChild = Class.new(Child)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "when a property is set from above" do
|
31
|
+
before do
|
32
|
+
Parent.cascade(:hair_color)
|
33
|
+
|
34
|
+
Parent.hair_color = :blond
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should cascade down to its descendents" do
|
38
|
+
Parent.hair_color.must_equal :blond
|
39
|
+
Child.hair_color.must_equal :blond
|
40
|
+
GrandChild.hair_color.must_equal :blond
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "when a property is set from below" do
|
45
|
+
before do
|
46
|
+
Parent.cascade do
|
47
|
+
arms :default => 2
|
48
|
+
legs :default => 2
|
49
|
+
end
|
50
|
+
|
51
|
+
Child.arms = 3
|
52
|
+
GrandChild.legs = 1
|
53
|
+
end
|
54
|
+
|
55
|
+
all "descendents are influenced and no acestors are affected" do
|
56
|
+
Parent.arms.must_equal 2
|
57
|
+
Child.arms.must_equal 3
|
58
|
+
GrandChild.arms.must_equal 3
|
59
|
+
|
60
|
+
Parent.legs.must_equal 2
|
61
|
+
Child.legs.must_equal 2
|
62
|
+
GrandChild.legs.must_equal 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "when a property has a default and has not been set by any descendent" do
|
67
|
+
before do
|
68
|
+
Parent.cascade [:eye_color, "red"]
|
69
|
+
end
|
70
|
+
|
71
|
+
all "descendents should reflect that default" do
|
72
|
+
Parent.eye_color.must_equal "red"
|
73
|
+
Child.eye_color.must_equal "red"
|
74
|
+
GrandChild.eye_color.must_equal "red"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "when a property has no default and has not been set by any ancestor" do
|
79
|
+
before do
|
80
|
+
Parent.cascade :has_house
|
81
|
+
end
|
82
|
+
|
83
|
+
all "descendents should reflect the same" do
|
84
|
+
Parent.has_house.must_equal nil
|
85
|
+
Child.has_house.must_equal nil
|
86
|
+
GrandChild.has_house.must_equal nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "when a child sets a property when its parent has set it too" do
|
91
|
+
before do
|
92
|
+
Parent.all! :trigger
|
93
|
+
|
94
|
+
Parent.trigger = "silent"
|
95
|
+
Child.trigger = "loud"
|
96
|
+
end
|
97
|
+
|
98
|
+
all "cascading is scoped off the youngest child with a set property" do
|
99
|
+
Parent.trigger.must_equal "silent"
|
100
|
+
Child.trigger.must_equal "loud"
|
101
|
+
GrandChild.trigger.must_equal "loud"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "when a parent has a property with a default set" do
|
106
|
+
before do
|
107
|
+
## Parent.cascade{ :time, :default => 32}
|
108
|
+
Parent.cascade [:time, 32]
|
109
|
+
end
|
110
|
+
|
111
|
+
it "won't matter if the property is first accessed by the child" do
|
112
|
+
Child.time
|
113
|
+
|
114
|
+
Child.time.must_equal 32
|
115
|
+
Parent.time.must_equal 32
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "when a parent has a property with a default set" do
|
120
|
+
before do
|
121
|
+
Parent.cascade [:time, 32]
|
122
|
+
end
|
123
|
+
|
124
|
+
it "won't matter if the property is first accessed by the parent" do
|
125
|
+
Parent.time
|
126
|
+
|
127
|
+
Child.time.must_equal 32
|
128
|
+
Parent.time.must_equal 32
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
begin
|
2
|
+
require_relative 'helper_spec'
|
3
|
+
rescue NameError
|
4
|
+
require File.expand_path('helper_spec', __FILE__)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe "when CascadingClasses is included by a class" do
|
8
|
+
|
9
|
+
|
10
|
+
# this is the same as:
|
11
|
+
# class A
|
12
|
+
# include CascadingClasses
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# class B < A; end
|
16
|
+
#
|
17
|
+
# class C < B; end
|
18
|
+
#
|
19
|
+
# a = A.new
|
20
|
+
# b = B.new
|
21
|
+
# c = C.new
|
22
|
+
before do
|
23
|
+
A = Class.new{include CC}
|
24
|
+
B = Class.new(A)
|
25
|
+
C = Class.new(B)
|
26
|
+
@a, @b, @c = A.new, B.new, C.new
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "when CascadingClasses is included in a module" do
|
30
|
+
before do
|
31
|
+
end
|
32
|
+
|
33
|
+
it "must pass all 'extended' test" do
|
34
|
+
# how do you run those tests??
|
35
|
+
file = File.join(File.expand_path(File.dirname(__FILE__)), 'extended_spec.rb')
|
36
|
+
result = `ruby #{file}`
|
37
|
+
result.must_match /(0) failures/
|
38
|
+
result.must_match /(0) errors/
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "when a property is set on a class" do
|
43
|
+
before do
|
44
|
+
A.all! :once, :twice # same as A.cascade :once, :twice
|
45
|
+
|
46
|
+
A.once = :thinking
|
47
|
+
B.twice = :speaking
|
48
|
+
end
|
49
|
+
|
50
|
+
all "cascading flows from that class downwards to all child classes and child objects" do
|
51
|
+
# :once
|
52
|
+
A.once.must_equal :thinking
|
53
|
+
@a.once.must_equal :thinking
|
54
|
+
B.once.must_equal :thinking
|
55
|
+
@b.once.must_equal :thinking
|
56
|
+
C.once.must_equal :thinking
|
57
|
+
@c.once.must_equal :thinking
|
58
|
+
|
59
|
+
# :twice
|
60
|
+
A.twice.must_equal nil
|
61
|
+
@a.twice.must_equal nil
|
62
|
+
B.twice.must_equal :speaking
|
63
|
+
@b.twice.must_equal :speaking
|
64
|
+
C.twice.must_equal :speaking
|
65
|
+
@c.twice.must_equal :speaking
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "when a property is set on an object" do
|
70
|
+
before do
|
71
|
+
A.all! :table
|
72
|
+
|
73
|
+
@a.table = "a large, rectangular object with spotty marks"
|
74
|
+
end
|
75
|
+
|
76
|
+
it "only affects that object" do
|
77
|
+
A.table.must_equal nil
|
78
|
+
@a.table.must_equal "a large, rectangular object with spotty marks"
|
79
|
+
B.table.must_equal nil
|
80
|
+
@b.table.must_equal nil
|
81
|
+
C.table.must_equal nil
|
82
|
+
@c.table.must_equal nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cascading_classes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jeremy Gables
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-03 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: jeremy.gables@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.md
|
21
|
+
- Rakefile
|
22
|
+
- lib/cascading_classes.rb
|
23
|
+
- lib/cascading_classes/cascading_classes.rb
|
24
|
+
- spec/extended_spec.rb
|
25
|
+
- spec/included_spec.rb
|
26
|
+
- spec/alternative_syntax_spec.rb
|
27
|
+
homepage: http://github.com/gables/cascading_classes
|
28
|
+
licenses: []
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 1.8.5
|
48
|
+
signing_key:
|
49
|
+
specification_version: 3
|
50
|
+
summary: Easily create properties whose values cascade down class trees
|
51
|
+
test_files:
|
52
|
+
- spec/extended_spec.rb
|
53
|
+
- spec/included_spec.rb
|
54
|
+
- spec/alternative_syntax_spec.rb
|