cascading_classes 0.3.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +959 -140
- data/Rakefile +9 -2
- data/lib/cascading_classes.rb +254 -46
- data/lib/cascading_classes/cascading_classes.rb +194 -40
- data/lib/cascading_classes/dsl.rb +137 -59
- data/lib/cascading_classes/parse_options.rb +71 -0
- data/lib/cascading_classes/types.rb +229 -0
- data/spec/basics/basic_spec.rb +230 -0
- data/spec/basics/block_spec.rb +52 -0
- data/spec/basics/container_spec.rb +201 -0
- data/spec/basics/inherit_spec.rb +339 -0
- data/spec/basics/proc_spec.rb +343 -0
- data/spec/class_helper_methods/parents_for_spec.rb +36 -0
- data/spec/custom_classes/hash_like_spec.rb +144 -0
- data/spec/helper_spec.rb +34 -2
- data/spec/instances/basics.rb +239 -0
- data/spec/preset_classes/array_spec.rb +214 -0
- data/spec/preset_classes/hash_spec.rb +210 -0
- data/spec/preset_classes/strings.rb +190 -0
- data/spec/preset_classes/undefined.rb +56 -0
- data/spec/usage/block_spec.rb +59 -0
- data/spec/usage/proc_spec.rb +60 -0
- data/todo +6 -0
- metadata +35 -14
- data/spec/alternative_syntax_spec.rb +0 -173
- data/spec/extended_spec.rb +0 -315
- data/spec/included_spec.rb +0 -103
data/Rakefile
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rake/testtask'
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
desc "Default Task"
|
5
|
+
task :default => :test_all
|
6
|
+
|
7
|
+
desc "Run all tests"
|
8
|
+
task :test_all => [:test_units]
|
9
|
+
|
10
|
+
Rake::TestTask.new("test_units") do |t|
|
11
|
+
t.pattern = "spec/**/*_spec.rb"
|
12
|
+
## t.test_files = FileList['spec/*
|
6
13
|
end
|
7
14
|
|
data/lib/cascading_classes.rb
CHANGED
@@ -1,97 +1,305 @@
|
|
1
1
|
require 'cascading_classes/cascading_classes'
|
2
|
+
require 'cascading_classes/parse_options'
|
2
3
|
|
3
4
|
CC = CascadingClasses unless defined? CC
|
4
5
|
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
6
|
+
# todo: add :unset_property functionality
|
7
|
+
# todo: allow types to cascade down singleton_class
|
8
|
+
# ie: each subclass could potentially have a different type
|
9
|
+
# todo: make parents_for() redundant by keeping track of each child's parents
|
10
|
+
# todo: Proc properties: (proc_spec.rb) B redefines mood/color procs, C.color(:inherit => false)
|
11
|
+
# is this supposed to call C.mood(:inherit => false) too?
|
8
12
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# cascade :one, :two, :three, ...
|
12
|
-
# end
|
13
|
-
# Foo.one = 12
|
14
|
-
# Foo.new.one # => 12
|
15
|
-
# Bar = Class.new(Foo).one # => 12
|
16
|
-
# Bar.new.one # => 12
|
13
|
+
# blurb:
|
14
|
+
# module not meant to be both included and extended
|
17
15
|
|
18
16
|
module CascadingClasses
|
19
17
|
|
20
18
|
# note, VERSION will be undetected unless called directly
|
21
19
|
def self.const_missing(name)
|
22
20
|
case name
|
23
|
-
when :VERSION then "0.
|
21
|
+
when :VERSION then "0.4.0"
|
24
22
|
else super
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
28
|
-
def respond_to?(const)
|
26
|
+
def self.respond_to?(const)
|
29
27
|
(const.to_s == 'VERSION') ? true : super
|
30
28
|
end
|
31
|
-
module_function :respond_to?
|
32
29
|
|
33
|
-
|
30
|
+
def self.class_methods
|
31
|
+
[:to_hash, :nearest_parent_for, :top_level_parent_for?]
|
32
|
+
end
|
33
|
+
|
34
|
+
# only applies to desired getter and setter names, not the property itself
|
34
35
|
# todo: allow one of [:cascade, :all!] to be overwritten
|
36
|
+
# todo: add :unset_property
|
35
37
|
def self.illegal_names
|
36
38
|
[:cascade, :all!, :set_property, :instance_eval, :instance_exec,
|
37
|
-
:class_eval]
|
39
|
+
:class_eval].concat(class_methods)
|
40
|
+
end
|
41
|
+
|
42
|
+
# global default: whether proc values are evaluated or instance_exec'd
|
43
|
+
# global default defaults to false
|
44
|
+
def self.proc_inst_exec?
|
45
|
+
@proc_inst_exec.nil? ? @proc_inst_exec = false : @proc_inst_exec
|
38
46
|
end
|
39
47
|
|
48
|
+
def self.proc_inst_exec=(val)
|
49
|
+
@proc_inst_exec = !!val
|
50
|
+
end
|
51
|
+
|
52
|
+
# global default: whether blocks are yielded to or instance_exec'd
|
53
|
+
# global default defaults to true
|
54
|
+
def self.block_inst_exec?
|
55
|
+
@block_inst_exec.nil? ? @block_inst_exec = true : @block_inst_exec
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.block_inst_exec=(val)
|
59
|
+
@block_inst_exec = !!val
|
60
|
+
end
|
61
|
+
|
62
|
+
# klass is the class that included/excluded CascadingClasses
|
40
63
|
def self.apply(klass, instances_too)
|
64
|
+
|
65
|
+
# cascade method
|
41
66
|
klass.singleton_class.instance_eval do
|
42
67
|
|
43
68
|
define_method :cascade do |*props, &block|
|
44
69
|
illegal_names = CascadingClasses.illegal_names
|
45
70
|
|
46
|
-
props = CascadingClasses.parse_options(
|
71
|
+
props = CascadingClasses.parse_options(instances_too, *props, &block)
|
47
72
|
props.each{|prop, v|
|
48
|
-
|
49
|
-
getters.each{|getter|
|
73
|
+
v[:getters].each{|getter|
|
50
74
|
raise NameError, "Illegal property getter: #{getter} for #{prop}" if
|
51
75
|
illegal_names.include? getter }
|
52
76
|
}
|
53
77
|
|
54
|
-
props.each do |prop,
|
55
|
-
|
78
|
+
props.each do |prop, settings|
|
79
|
+
raise "Hell" unless Symbol === prop # sanity
|
80
|
+
getters, setters = settings[:getters], settings[:setters]
|
81
|
+
getter, setter = getters.first, setters.first
|
56
82
|
|
57
|
-
|
58
|
-
|
59
|
-
|
83
|
+
# set default value on top level parent
|
84
|
+
me = settings[:ensure_type].call(settings[:default])
|
85
|
+
instance_variable_set("@#{prop}", me)
|
86
|
+
|
87
|
+
slf = self
|
88
|
+
# property metadata saved in singleton class
|
89
|
+
singleton_class.instance_eval do
|
90
|
+
@properties ||= {}
|
91
|
+
@properties.delete prop # in case it's already there
|
92
|
+
@properties[prop] = settings
|
93
|
+
@properties[prop][:class] = slf
|
60
94
|
end
|
61
95
|
|
62
|
-
|
96
|
+
## mod = CascadingClasses.cascade_intern(self, prop, settings)
|
97
|
+
mod = CascadingClasses.cascade_intern(prop, settings)
|
63
98
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
getters[0].nil?
|
69
|
-
setters[1..-1].each{|setter| alias_method setter, setters[0]} unless
|
70
|
-
setters[0].nil?
|
99
|
+
self.extend mod
|
100
|
+
singleton_class.instance_eval do
|
101
|
+
getters[1..-1].each{|g| alias_method g, getter} unless getter.nil?
|
102
|
+
setters[1..-1].each{|s| alias_method s, setter} unless setter.nil?
|
71
103
|
end
|
72
104
|
|
73
|
-
if
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
klass.send(:include, mod)
|
79
|
-
klass.instance_eval do
|
80
|
-
getters[1..-1].each{|getter| alias_method getter, getters[0]} unless
|
81
|
-
getters[0].nil?
|
82
|
-
setters[1..-1].each{|setter| alias_method setter, setters[0]} unless
|
83
|
-
setters[0].nil?
|
105
|
+
if settings[:instances_too]
|
106
|
+
send(:include, mod)
|
107
|
+
instance_eval do
|
108
|
+
getters[1..-1].each{|g| alias_method g, getter} unless getter.nil?
|
109
|
+
setters[1..-1].each{|s| alias_method s, setter} unless setter.nil?
|
84
110
|
end
|
85
111
|
end
|
86
112
|
end
|
113
|
+
end
|
114
|
+
|
115
|
+
alias_method(:all!, :cascade) unless instance_methods.include?(:all!)
|
116
|
+
end
|
117
|
+
|
118
|
+
klass_methods = Module.new do
|
119
|
+
|
120
|
+
# whether property was originally defined on self
|
121
|
+
# uses the fact that 'properties' hash can be defined multiple times
|
122
|
+
# for the same property on the class tree
|
123
|
+
# name: property name
|
124
|
+
# instance: (boolean) whether callee is an instance
|
125
|
+
define_method :top_level_parent_for? do |name, instance=false|
|
126
|
+
return false if instance
|
127
|
+
name = name.to_sym
|
128
|
+
props = singleton_class.properties
|
129
|
+
raise "neither #{self} nor its ancestors have #{name}" unless
|
130
|
+
props and props[name]
|
131
|
+
props2 = superclass.singleton_class.properties rescue nil
|
132
|
+
(props2 and props2[name]) ? false : true
|
133
|
+
end
|
134
|
+
|
135
|
+
# returns array of parents starting (excludes self)
|
136
|
+
# for property:'name'
|
137
|
+
### will include self if self is a class
|
138
|
+
# never includes self, even if a class
|
139
|
+
# name: property name
|
140
|
+
# instance: (boolean) whether 'callee' is an instance
|
141
|
+
# always called by a class, not an instance
|
142
|
+
define_method :parents_for do |name, instance=false|
|
143
|
+
return [] if top_level_parent_for?(name, instance)
|
144
|
+
s = instance ? self : self.superclass
|
145
|
+
res = [s]
|
146
|
+
|
147
|
+
until s.top_level_parent_for?(name)
|
148
|
+
raise "Hell" if s.nil?
|
149
|
+
s = s.superclass
|
150
|
+
res << s
|
151
|
+
end
|
152
|
+
res
|
153
|
+
end
|
154
|
+
|
155
|
+
# differs from 'parents_for' in that it is not
|
156
|
+
# property specific. a property defined on a
|
157
|
+
# descendent of the top-most class will only
|
158
|
+
# go as high as that descendent in the 'parents_for'.
|
159
|
+
# hence any call to 'parents_for' will be a sub array
|
160
|
+
# of a call to 'ancestor_chain'
|
161
|
+
#
|
162
|
+
# never includes self
|
163
|
+
define_method :ancestor_chain do |instance=false|
|
164
|
+
top_parent = oldest_cascading_class
|
165
|
+
return [] unless top_parent # todo: raise "Hell" instead
|
166
|
+
## return [self] if self == top_parent
|
167
|
+
return [] if self == top_parent
|
168
|
+
res = []
|
169
|
+
up = self
|
170
|
+
while up != top_parent
|
171
|
+
res << up unless up == self and instance == false
|
172
|
+
up = up.superclass
|
173
|
+
end
|
174
|
+
res << top_parent
|
175
|
+
res
|
176
|
+
end
|
177
|
+
|
178
|
+
# slightly hackish
|
179
|
+
# todo: propose adding marker variable to top-level class
|
180
|
+
define_method :oldest_cascading_class do
|
181
|
+
res = singleton_class.instance_variable_get("@properties").nil? ? nil : self
|
182
|
+
tmp = self
|
183
|
+
while tmp = tmp.superclass
|
184
|
+
if s = tmp.singleton_class.instance_variable_get("@properties")
|
185
|
+
res = tmp if Hash === s
|
186
|
+
end
|
187
|
+
end
|
188
|
+
res
|
189
|
+
end
|
190
|
+
|
191
|
+
# calculates number of generations to ancestor
|
192
|
+
# *eval* todo: resolve
|
193
|
+
# note: a class is considered one generations away from its instances
|
194
|
+
# todo: explain why not using self.ancestors
|
195
|
+
# ancestor: class name to take generations from
|
196
|
+
# instance: (boolean) whether 'callee' is an instance
|
197
|
+
# always called by a class, not an instance
|
198
|
+
define_method :generations_from do |ancestor, instance=false|
|
199
|
+
ancestor = eval(ancestor.to_s) if Symbol === ancestor # *eval*
|
200
|
+
s = self
|
201
|
+
num = instance ? 1 : 0
|
202
|
+
|
203
|
+
while s != ancestor
|
204
|
+
s = s.superclass
|
205
|
+
raise "NoAncestor" if s.nil?
|
206
|
+
num += 1
|
207
|
+
end
|
208
|
+
num
|
209
|
+
end
|
210
|
+
|
211
|
+
# todo: instances
|
212
|
+
# collects properties and their values
|
213
|
+
# blanks: whether to include values that are blank
|
214
|
+
define_method :to_hash do |blanks=true|
|
215
|
+
s = self.class == Class ? self : self.class
|
216
|
+
## props = s.singleton_class.properties rescue {}
|
217
|
+
## return props if props.empty?
|
218
|
+
props = s.singleton_class.properties
|
219
|
+
getters = Hash[ props.map{|name, v| [name, v[:getters].first]} ]
|
220
|
+
|
221
|
+
res = if blanks
|
222
|
+
props.map{|name, v| [name, s.send(getters[name])] }
|
223
|
+
else
|
224
|
+
prop_names = props.keys.delete_if{|name| send("#{name}_is_blank?") }
|
225
|
+
prop_names.map{|name| [name, s.send(getters[name])] }
|
226
|
+
end
|
227
|
+
Hash[res]
|
228
|
+
end
|
229
|
+
|
230
|
+
# closest ancestor with a non-blank property
|
231
|
+
# returns nil if there aren't any
|
232
|
+
# name: property name
|
233
|
+
# instance: (boolean) whether 'callee' is an instance
|
234
|
+
# always called by a class, not an instance
|
235
|
+
define_method :youngest_valid_ancestor_for do |name, instance=false|
|
236
|
+
parents = parents_for(name, instance)
|
237
|
+
return nil if parents.empty?
|
238
|
+
parents.each{|ancestor| return ancestor unless
|
239
|
+
ancestor.send("#{name}_is_blank?") }
|
240
|
+
nil
|
241
|
+
end
|
242
|
+
|
243
|
+
=begin
|
244
|
+
define_method :youngest_valid_ancestor_for_OLD do |name, instance=false|
|
245
|
+
parents = parents_for(name, instance)
|
246
|
+
return self if parents.empty?
|
247
|
+
parents.each{|ancestor|
|
248
|
+
return ancestor unless ancestor.send("#{name}_is_blank?") }
|
249
|
+
self
|
250
|
+
end
|
251
|
+
=end
|
252
|
+
|
253
|
+
# name: property name
|
254
|
+
# instance: (boolean) whether 'callee' is an instance
|
255
|
+
# always called by a class, not an instance
|
256
|
+
define_method :oldest_valid_ancestor_for do |name, instance=false|
|
257
|
+
## s = self.class == Class ? self : self.class
|
258
|
+
## props = s.singleton_class.properties
|
259
|
+
props = singleton_class.properties
|
260
|
+
raise "No ancestor of #{self} has #{name}" unless
|
261
|
+
props and props[name.to_sym]
|
262
|
+
|
263
|
+
s = self
|
264
|
+
begin
|
265
|
+
tmp = s.superclass
|
266
|
+
while props2 = tmp.singleton_class.properties and
|
267
|
+
props2[name.to_sym]
|
268
|
+
s = tmp
|
269
|
+
tmp = s.superclass
|
270
|
+
end
|
271
|
+
rescue NoMethodError
|
272
|
+
end
|
273
|
+
s
|
87
274
|
end
|
88
275
|
|
89
|
-
|
90
|
-
|
276
|
+
end
|
277
|
+
klass.extend klass_methods
|
278
|
+
## klass.send(:include, klass_and_inst_methods) if instances_too
|
279
|
+
|
280
|
+
klass.singleton_class.instance_eval do
|
281
|
+
@properties ||= {}
|
282
|
+
|
283
|
+
singleton_class.instance_eval do
|
284
|
+
define_method :properties do
|
285
|
+
res = instance_variable_get("@properties") || {}
|
286
|
+
return res if self.superclass == Object.singleton_class
|
287
|
+
up = superclass.send(:properties) rescue {}
|
288
|
+
up.merge(res)
|
289
|
+
end
|
290
|
+
|
291
|
+
define_method :children do
|
292
|
+
@children ||= []
|
293
|
+
end
|
91
294
|
end
|
92
295
|
|
296
|
+
define_method :inherited do |subklass|
|
297
|
+
c = self.singleton_class.send(:children)
|
298
|
+
c.delete subklass # if present
|
299
|
+
c << subklass
|
300
|
+
end
|
93
301
|
end
|
94
|
-
end
|
302
|
+
end # self.apply
|
95
303
|
|
96
304
|
def self.included(klass)
|
97
305
|
apply(klass, true)
|
@@ -1,60 +1,214 @@
|
|
1
1
|
require 'cascading_classes/dsl'
|
2
|
+
require 'cascading_classes/types'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
# todo: support regexp's natively??
|
2
6
|
|
3
7
|
module CascadingClasses
|
4
8
|
|
5
|
-
def self.
|
6
|
-
|
9
|
+
def self.cascade_parse_options(arg1, arg2, opts, settings)
|
10
|
+
r = {}
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
# ':default' given? -> whether to ignore own value and
|
13
|
+
# return top-level 'default' set in 'cascade' call
|
14
|
+
r[:default] =
|
15
|
+
if arg1 == :default and (arg2 or arg2 == :undef)
|
16
|
+
true
|
17
|
+
elsif Hash === arg1 and arg1.include?(:default)
|
18
|
+
!!arg1[:default]
|
19
|
+
else
|
20
|
+
false
|
15
21
|
end
|
22
|
+
return r if r[:default]
|
16
23
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
r[:proc_inst_exec] = opts.include?(:proc_inst_exec) ?
|
25
|
+
!!opts[:proc_inst_exec] : settings[:proc_inst_exec]
|
26
|
+
r[:block_inst_exec] = opts.include?(:block_inst_exec) ?
|
27
|
+
!!opts[:block_inst_exec] : settings[:block_inst_exec]
|
28
|
+
|
29
|
+
is_container = CascadingClasses.is_container?(settings[:type])
|
30
|
+
|
31
|
+
# inherit_age unless it's given manually
|
32
|
+
r[:inherit_age] =
|
33
|
+
if settings[:inherit] == :undef
|
34
|
+
raise "Hell" unless CascadingClasses.undefined_type?(settings[:type]) # sanity
|
35
|
+
-1
|
36
|
+
else
|
37
|
+
settings[:inherit]
|
21
38
|
end
|
22
39
|
|
40
|
+
=begin
|
41
|
+
r[:inherit_age] = settings[:inherit]
|
42
|
+
if r[:inherit_age] == :undef and
|
43
|
+
not CascadingClasses.undefined_type?(settings[:type])
|
44
|
+
# only happens in corner case of unknown type
|
45
|
+
r[:inherit_age] = is_container ? 0 : -1
|
23
46
|
end
|
47
|
+
=end
|
48
|
+
|
49
|
+
r[:inherit_age] =
|
50
|
+
if Hash === arg1 and arg1.include?(:inherit)
|
51
|
+
arg1[:inherit]
|
52
|
+
elsif arg1 == :inherit
|
53
|
+
arg2 == :undef ? -1 : arg2
|
54
|
+
elsif opts.include?(:inherit)
|
55
|
+
opts[:inherit]
|
56
|
+
else
|
57
|
+
(arg1 == :undef) ? r[:inherit_age] : arg1
|
58
|
+
end
|
59
|
+
|
60
|
+
r[:inherit_age] =
|
61
|
+
case r[:inherit_age]
|
62
|
+
when Fixnum then r[:inherit_age]
|
63
|
+
when true then -1
|
64
|
+
when false, nil then 0
|
65
|
+
else
|
66
|
+
raise "inherit_age: #{r[:inherit_age]} must be one of true, false, nil, <fixnum>"
|
67
|
+
end
|
68
|
+
|
69
|
+
r
|
24
70
|
end
|
25
71
|
|
26
|
-
|
27
|
-
def self.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
72
|
+
## def self.cascade_intern(klass, property, settings)
|
73
|
+
def self.cascade_intern(property, settings)
|
74
|
+
property = property.to_sym
|
75
|
+
Module.new do
|
76
|
+
|
77
|
+
getter, setter = settings[:getters].first, settings[:setters].first
|
78
|
+
raise "Hell" unless getter
|
79
|
+
|
80
|
+
# opts:
|
81
|
+
# proc_context: class to evaluate any proc in
|
82
|
+
# block_inst_exec: whether to call block (false) or
|
83
|
+
# to send block to instance_exec (true)
|
84
|
+
# proc_inst_exec: whether to call proc (false) or
|
85
|
+
# to convert to a block and send to instance_exec (true)
|
86
|
+
# inherit: whether property (if blank) should inherit its value from above
|
87
|
+
# --->top_most_parent_overall vs top_most_parent_property
|
88
|
+
define_method getter do |arg1=:undef, arg2=:undef, opts={}, &block|
|
89
|
+
is_instance = (self.class == Class) ? false : true
|
90
|
+
klass = is_instance ? self.class : self
|
91
|
+
|
92
|
+
# todo: allow an option to decide which chain to use
|
93
|
+
chain = klass.parents_for(property, is_instance)
|
94
|
+
## chain = klass.ancestor_chain # incorrect value for A.new.prop{|val, parents| parents}
|
95
|
+
|
96
|
+
if send("#{property}_is_unset?") and
|
97
|
+
not CascadingClasses.undefined_type?(settings[:type])
|
98
|
+
instance_variable_set "@#{property}", settings[:new].call
|
99
|
+
end
|
100
|
+
|
101
|
+
# parse options
|
102
|
+
r = CascadingClasses.cascade_parse_options(arg1, arg2, opts, settings)
|
103
|
+
proc_inst_exec, block_inst_exec = r[:proc_inst_exec], r[:block_inst_exec]
|
104
|
+
inherit_age = r[:inherit_age] || -1
|
105
|
+
|
106
|
+
if r[:default]
|
107
|
+
val, is_blank = settings[:default], false
|
37
108
|
else
|
38
|
-
|
39
|
-
|
40
|
-
|
109
|
+
val = instance_variable_get "@#{property}"
|
110
|
+
is_blank = send("#{property}_is_blank?")
|
111
|
+
end
|
112
|
+
raise "Hell" if val and CascadingClasses.undefined_type?(settings[:type]) # sanity
|
113
|
+
return nil if CascadingClasses.undefined_type?(settings[:type]) # corner case
|
114
|
+
|
115
|
+
## cb = settings[:ensure_type] # callback
|
116
|
+
cb = (settings[:type] == :Proc) ? Proc.new{|v| v} : settings[:ensure_type]
|
117
|
+
if settings[:after_get]
|
118
|
+
cb = Proc.new{|v| cb.call(settings[:after_get].call(v))}
|
41
119
|
end
|
120
|
+
|
121
|
+
first_valid_ancestor = is_blank ?
|
122
|
+
klass.youngest_valid_ancestor_for(property, is_instance) : self
|
123
|
+
|
124
|
+
# todo: double-check
|
125
|
+
unless first_valid_ancestor
|
126
|
+
# there are no valid ancestors (from self) for property
|
127
|
+
raise "Hell" unless
|
128
|
+
klass.top_level_parent_for?(property, is_instance) or is_blank
|
129
|
+
|
130
|
+
me = cb.call(val)
|
131
|
+
return block ? (block_inst_exec ? instance_exec(self, chain, &block) :
|
132
|
+
block.call(me, chain)) : me
|
133
|
+
end
|
134
|
+
|
135
|
+
ancestor_age = (self == first_valid_ancestor) ? 0 :
|
136
|
+
klass.generations_from(first_valid_ancestor, is_instance)
|
137
|
+
inherit_age = (inherit_age < 0) ? ancestor_age : inherit_age
|
138
|
+
|
139
|
+
if is_blank
|
140
|
+
if ancestor_age > 0 and inherit_age >= ancestor_age
|
141
|
+
if settings[:type] == :Proc and not opts.include?(:proc_context)
|
142
|
+
opts.merge!({:proc_context => self})
|
143
|
+
end
|
144
|
+
|
145
|
+
# fix: hackish
|
146
|
+
up_val = first_valid_ancestor.instance_variable_get "@#{property}"
|
147
|
+
val = (settings[:type] == :Proc or Proc === up_val) ?
|
148
|
+
up_val : first_valid_ancestor.send(getter, :undef, :undef, opts)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
if is_blank and inherit_age.zero? and settings[:type] == :Proc
|
153
|
+
val = settings[:default]
|
154
|
+
end
|
155
|
+
|
156
|
+
if Proc === val
|
157
|
+
val = if opts[:proc_context]
|
158
|
+
opts[:proc_context].instance_exec(self, chain, &val)
|
159
|
+
else
|
160
|
+
opts[:proc_inst_exec] ? instance_exec(self, chain, &val) :
|
161
|
+
val.call(self, chain)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
me = cb.call(val)
|
166
|
+
block ? (block_inst_exec ? instance_exec(me, chain, &block) :
|
167
|
+
block.call(me, chain)) : me
|
42
168
|
end
|
43
|
-
getters = [name.to_sym] if getters.empty?
|
44
|
-
setters = ["#{name}=".to_sym] if setters.empty?
|
45
|
-
inst_too = !!inst_too
|
46
|
-
res[name.to_sym] = [default, inst_too, getters, setters]
|
47
|
-
end
|
48
169
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
170
|
+
if setter
|
171
|
+
define_method setter do |v|
|
172
|
+
raise "Hell" if settings[:type] == :NilClass # sanity
|
173
|
+
if CascadingClasses.undefined_type?(settings[:type]) # or
|
174
|
+
## settings[:type] == :NilClass
|
175
|
+
if v and v.class.to_s.to_sym != settings[:type]
|
176
|
+
type = settings[:type] = v.class.to_s.to_sym
|
177
|
+
unless settings[:options_given].include?(:blank)
|
178
|
+
settings[:blank] = CascadingClasses.default_blank(type)
|
179
|
+
end
|
180
|
+
unless settings[:options_given].include?(:new)
|
181
|
+
settings[:new] = CascadingClasses.default_new(type)
|
182
|
+
end
|
183
|
+
unless settings[:options_given].include?(:ensure_type)
|
184
|
+
settings[:ensure_type] =
|
185
|
+
CascadingClasses.default_ensure_type type,
|
186
|
+
settings[:blank],
|
187
|
+
settings[:new]
|
188
|
+
end
|
189
|
+
unless settings[:options_given].include?(:inherit)
|
190
|
+
settings[:inherit] = CascadingClasses.is_container?(type) ? false : true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
v = settings[:ensure_type].call(v) unless Proc === v
|
196
|
+
instance_variable_set("@#{property}", v)
|
197
|
+
end
|
56
198
|
|
57
|
-
|
199
|
+
end
|
200
|
+
|
201
|
+
define_method "#{property}_is_unset?" do
|
202
|
+
not instance_variables.include? "@#{property}".to_sym
|
203
|
+
end
|
204
|
+
|
205
|
+
define_method "#{property}_is_blank?" do
|
206
|
+
return true if self.send "#{property}_is_unset?"
|
207
|
+
val = instance_variable_get "@#{property}"
|
208
|
+
Proc === val ? false : settings[:blank].call(val)
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
58
212
|
end
|
59
213
|
|
60
214
|
end # module CascadingClasses
|