cascading_classes 0.3.0 → 0.6.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 +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
|