cascading_classes 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|