Module: CascadingClasses

Defined in:
lib/cascading_classes.rb,
lib/cascading_classes/dsl.rb,
lib/cascading_classes/types.rb,
lib/cascading_classes/parse_options.rb,
lib/cascading_classes/cascading_classes.rb

Overview

todo: support regexp’s natively??

Defined Under Namespace

Classes: BlankSlate, DSL

Class Method Summary collapse

Class Method Details

.apply(klass, instances_too) ⇒ Object

klass is the class that included/excluded CascadingClasses



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/cascading_classes.rb', line 63

def self.apply(klass, instances_too)

  # cascade method 
  klass.singleton_class.instance_eval do

    define_method :cascade do |*props, &block|
      illegal_names = CascadingClasses.illegal_names

      props = CascadingClasses.parse_options(instances_too, *props, &block)
      props.each{|prop, v| 
        v[:getters].each{|getter|
          raise NameError, "Illegal property getter: #{getter} for #{prop}" if
            illegal_names.include? getter }
      }

      props.each do |prop, settings|
        raise "Hell" unless Symbol === prop  # sanity
        getters, setters = settings[:getters], settings[:setters]
        getter, setter = getters.first, setters.first

        # set default value on top level parent
        me = settings[:ensure_type].call(settings[:default])
        instance_variable_set("@#{prop}", me)

        slf = self
        # property metadata saved in singleton class
        singleton_class.instance_eval do
          @properties ||= {}
          @properties.delete prop  # in case it's already there
          @properties[prop] = settings
          @properties[prop][:class] = slf
        end

        ## mod = CascadingClasses.cascade_intern(self, prop, settings)
        mod = CascadingClasses.cascade_intern(prop, settings)

        self.extend mod
        singleton_class.instance_eval do
          getters[1..-1].each{|g| alias_method g, getter} unless getter.nil?
          setters[1..-1].each{|s| alias_method s, setter} unless setter.nil?
        end

        if settings[:instances_too]
          send(:include, mod)
          instance_eval do
            getters[1..-1].each{|g| alias_method g, getter} unless getter.nil?
            setters[1..-1].each{|s| alias_method s, setter} unless setter.nil?
          end
        end
      end
    end  

    alias_method(:all!, :cascade) unless instance_methods.include?(:all!)
  end

  klass_methods = Module.new do

    # whether property was originally defined on self
    # uses the fact that 'properties' hash can be defined multiple times
    #   for the same property on the class tree
    # name: property name
    # instance: (boolean) whether callee is an instance
    define_method :top_level_parent_for? do |name, instance=false|
      return false if instance
      name = name.to_sym
      props = singleton_class.properties
      raise "neither #{self} nor its ancestors have #{name}" unless
        props and props[name]
      props2 = superclass.singleton_class.properties rescue nil
      (props2 and props2[name]) ? false : true
    end

    # returns array of parents starting (excludes self)
    #   for property:'name'
    ### will include self if self is a class
    # never includes self, even if a class
    # name: property name
    # instance: (boolean) whether 'callee' is an instance
    # always called by a class, not an instance
    define_method :parents_for do |name, instance=false|
      return [] if top_level_parent_for?(name, instance) 
      s = instance ? self : self.superclass
      res = [s]

      until s.top_level_parent_for?(name)
        raise "Hell" if s.nil?
        s = s.superclass
        res << s
      end
      res
    end

    # differs from 'parents_for' in that it is not
    # property specific. a property defined on  a
    # descendent of the top-most class will only
    # go as high as that descendent in the 'parents_for'.
    # hence any call to 'parents_for' will be a sub array
    # of a call to 'ancestor_chain'
    #
    # never includes self
    define_method :ancestor_chain do |instance=false|
      top_parent = oldest_cascading_class
      return [] unless top_parent  # todo: raise "Hell" instead
      ## return [self] if self == top_parent
      return [] if self == top_parent
      res = []
      up = self
      while up != top_parent
        res << up unless up == self and instance == false
        up = up.superclass
      end
      res << top_parent
      res
    end

    # slightly hackish
    # todo: propose adding marker variable to top-level class
    define_method :oldest_cascading_class do 
      res = singleton_class.instance_variable_get("@properties").nil? ? nil : self
      tmp = self
      while tmp = tmp.superclass
        if s = tmp.singleton_class.instance_variable_get("@properties")
          res = tmp if Hash === s
        end
      end
      res
    end

    # calculates number of generations to ancestor
    #  *eval*  todo: resolve
    # note: a class is considered one generations away from its instances
    # todo: explain why not using self.ancestors 
    # ancestor: class name to take generations from
    # instance: (boolean) whether 'callee' is an instance
    # always called by a class, not an instance
    define_method :generations_from do |ancestor, instance=false|
      ancestor = eval(ancestor.to_s) if Symbol === ancestor  # *eval*
      s = self
      num = instance ? 1 : 0

      while s != ancestor 
        s = s.superclass
        raise "NoAncestor" if s.nil?
        num += 1
      end
      num
    end

    # todo: instances
    # collects properties and their values
    # blanks: whether to include values that are blank
    define_method :to_hash do |blanks=true|
      s = self.class == Class ? self : self.class
      ## props = s.singleton_class.properties rescue {}
      ## return props if props.empty?
      props = s.singleton_class.properties
      getters = Hash[ props.map{|name, v| [name, v[:getters].first]} ]
          
      res = if blanks
        props.map{|name, v| [name, s.send(getters[name])] }
      else
        prop_names = props.keys.delete_if{|name| send("#{name}_is_blank?") }  
        prop_names.map{|name| [name, s.send(getters[name])] }
      end
      Hash[res]
    end

    # closest ancestor with a non-blank property
    # returns nil if there aren't any
    # name: property name
    # instance: (boolean) whether 'callee' is an instance
    # always called by a class, not an instance
    define_method :youngest_valid_ancestor_for do |name, instance=false|
      parents = parents_for(name, instance)
      return nil if parents.empty?
      parents.each{|ancestor| return ancestor unless
        ancestor.send("#{name}_is_blank?") }
      nil
    end

=begin
    define_method :youngest_valid_ancestor_for_OLD do |name, instance=false|
      parents = parents_for(name, instance)
      return self if parents.empty?
      parents.each{|ancestor|
        return ancestor unless ancestor.send("#{name}_is_blank?") }
      self
    end
=end

    # name: property name
    # instance: (boolean) whether 'callee' is an instance
    # always called by a class, not an instance
    define_method :oldest_valid_ancestor_for do |name, instance=false|
      ## s = self.class == Class ? self : self.class
      ## props = s.singleton_class.properties
      props = singleton_class.properties
      raise "No ancestor of #{self} has #{name}" unless
        props and props[name.to_sym]

      s = self
      begin
        tmp = s.superclass
        while props2 = tmp.singleton_class.properties and
              props2[name.to_sym]
          s = tmp
          tmp = s.superclass                
        end
      rescue NoMethodError          
      end
      s
    end

  end
  klass.extend klass_methods
  ## klass.send(:include, klass_and_inst_methods) if instances_too

  klass.singleton_class.instance_eval do
    @properties ||= {}

    singleton_class.instance_eval do
      define_method :properties do
        res = instance_variable_get("@properties") || {}
        return res if self.superclass == Object.singleton_class
        up = superclass.send(:properties) rescue {}
        up.merge(res)
      end

      define_method :children do
        @children ||= []
      end
    end

    define_method :inherited do |subklass|
      c = self.singleton_class.send(:children)
      c.delete subklass # if present
      c << subklass
    end
  end
end

.basic_typesObject



10
11
12
13
# File 'lib/cascading_classes/types.rb', line 10

def self.basic_types
  [:String, :Fixnum, :Float, :bool, :boolean, :Symbol, :Hash, :Array,
   :Set, :Proc, :FalseClass, :TrueClass]
end

.block_inst_exec=(val) ⇒ Object



58
59
60
# File 'lib/cascading_classes.rb', line 58

def self.block_inst_exec=(val)
  @block_inst_exec = !!val
end

.block_inst_exec?Boolean

global default: whether blocks are yielded to or instance_exec’d global default defaults to true

Returns:

  • (Boolean)


54
55
56
# File 'lib/cascading_classes.rb', line 54

def self.block_inst_exec?
  @block_inst_exec.nil? ? @block_inst_exec = true : @block_inst_exec
end

.cascade_intern(property, settings) ⇒ Object

def self.cascade_intern(klass, property, settings)



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/cascading_classes/cascading_classes.rb', line 73

def self.cascade_intern(property, settings)
  property = property.to_sym
  Module.new do

    getter, setter = settings[:getters].first, settings[:setters].first
    raise "Hell" unless getter

    # opts:
    #   proc_context:      class to evaluate any proc in
    #   block_inst_exec:   whether to call block (false) or
    #                      to send block to instance_exec (true)
    #   proc_inst_exec:    whether to call proc (false) or
    #                      to convert to a block and send to instance_exec (true)
    #   inherit:           whether property (if blank) should inherit its value from above
    #   --->top_most_parent_overall vs top_most_parent_property
    define_method getter do |arg1=:undef, arg2=:undef, opts={}, &block|
      is_instance = (self.class == Class) ? false : true
      klass = is_instance ? self.class : self

      # todo: allow an option to decide which chain to use
      chain = klass.parents_for(property, is_instance)
      ## chain = klass.ancestor_chain  # incorrect value for A.new.prop{|val, parents| parents}

      if send("#{property}_is_unset?") and
           not CascadingClasses.undefined_type?(settings[:type])
        instance_variable_set "@#{property}", settings[:new].call
      end

      # parse options
      r = CascadingClasses.cascade_parse_options(arg1, arg2, opts, settings)
      proc_inst_exec, block_inst_exec = r[:proc_inst_exec], r[:block_inst_exec]
      inherit_age = r[:inherit_age] || -1 

      if r[:default]
        val, is_blank = settings[:default], false
      else
        val = instance_variable_get "@#{property}"
        is_blank = send("#{property}_is_blank?")
      end
      raise "Hell" if val and CascadingClasses.undefined_type?(settings[:type]) # sanity
      return nil if CascadingClasses.undefined_type?(settings[:type]) # corner case

      ## cb = settings[:ensure_type]  # callback
      cb = (settings[:type] == :Proc) ? Proc.new{|v| v} : settings[:ensure_type]
      if settings[:after_get]
        cb = Proc.new{|v| cb.call(settings[:after_get].call(v))}
      end

      first_valid_ancestor = is_blank ?
        klass.youngest_valid_ancestor_for(property, is_instance) : self

      # todo: double-check
      unless first_valid_ancestor 
        # there are no valid ancestors (from self) for property
        raise "Hell" unless
          klass.top_level_parent_for?(property, is_instance) or is_blank

        me = cb.call(val)
        return block ? (block_inst_exec ? instance_exec(self, chain, &block) :
                        block.call(me, chain)) : me
      end

      ancestor_age = (self == first_valid_ancestor) ? 0 :
        klass.generations_from(first_valid_ancestor, is_instance)
      inherit_age = (inherit_age < 0) ? ancestor_age : inherit_age

      if is_blank
        if ancestor_age > 0 and inherit_age >= ancestor_age
          if settings[:type] == :Proc and not opts.include?(:proc_context)
            opts.merge!({:proc_context => self})
          end

          # fix: hackish
          up_val = first_valid_ancestor.instance_variable_get "@#{property}"
          val = (settings[:type] == :Proc or Proc === up_val) ? 
            up_val : first_valid_ancestor.send(getter, :undef, :undef, opts)
        end          
      end

      if is_blank and inherit_age.zero? and settings[:type] == :Proc
        val = settings[:default]
      end
   
      if Proc === val 
        val = if opts[:proc_context]
          opts[:proc_context].instance_exec(self, chain, &val)
        else
          opts[:proc_inst_exec] ? instance_exec(self, chain, &val) : 
            val.call(self, chain)
        end
      end

      me = cb.call(val)
      block ? (block_inst_exec ? instance_exec(me, chain, &block) :
               block.call(me, chain)) : me
    end

    if setter
      define_method setter do |v|
        raise "Hell" if settings[:type] == :NilClass  # sanity
        if CascadingClasses.undefined_type?(settings[:type])   # or
          ## settings[:type] == :NilClass
          if v and v.class.to_s.to_sym != settings[:type]
            type = settings[:type] = v.class.to_s.to_sym           
            unless settings[:options_given].include?(:blank)
              settings[:blank] = CascadingClasses.default_blank(type)
            end
            unless settings[:options_given].include?(:new)
              settings[:new] = CascadingClasses.default_new(type)
            end
            unless settings[:options_given].include?(:ensure_type)
              settings[:ensure_type] =
                CascadingClasses.default_ensure_type type,
                                                     settings[:blank],
                                                     settings[:new]
            end
            unless settings[:options_given].include?(:inherit)
              settings[:inherit] = CascadingClasses.is_container?(type) ? false : true
            end
          end
        end

        v = settings[:ensure_type].call(v) unless Proc === v
        instance_variable_set("@#{property}", v)
      end

    end

    define_method "#{property}_is_unset?" do
      not instance_variables.include? "@#{property}".to_sym
    end
 
    define_method "#{property}_is_blank?" do
      return true if self.send "#{property}_is_unset?"
      val = instance_variable_get "@#{property}"
      Proc === val ? false : settings[:blank].call(val)
    end
    
  end
end

.cascade_parse_options(arg1, arg2, opts, settings) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/cascading_classes/cascading_classes.rb', line 9

def self.cascade_parse_options(arg1, arg2, opts, settings)
  r = {}

  # ':default' given? -> whether to ignore own value and
  # return top-level 'default' set in 'cascade' call
  r[:default] = 
    if arg1 == :default and (arg2 or arg2 == :undef)
      true
    elsif Hash === arg1 and arg1.include?(:default)
      !!arg1[:default]
    else
      false
    end
  return r if r[:default]

  r[:proc_inst_exec] = opts.include?(:proc_inst_exec) ?
    !!opts[:proc_inst_exec] : settings[:proc_inst_exec]
  r[:block_inst_exec] = opts.include?(:block_inst_exec) ?
    !!opts[:block_inst_exec] : settings[:block_inst_exec]

  is_container = CascadingClasses.is_container?(settings[:type])

  # inherit_age unless it's given manually
  r[:inherit_age] = 
    if settings[:inherit] == :undef 
      raise "Hell" unless CascadingClasses.undefined_type?(settings[:type])  # sanity
      -1
    else
      settings[:inherit]
    end

=begin      
  r[:inherit_age] = settings[:inherit]
  if r[:inherit_age] == :undef and
      not CascadingClasses.undefined_type?(settings[:type])
    # only happens in corner case of unknown type 
    r[:inherit_age] = is_container ? 0 : -1
  end
=end    

  r[:inherit_age] = 
    if Hash === arg1 and arg1.include?(:inherit)
      arg1[:inherit]
    elsif arg1 == :inherit
      arg2 == :undef ? -1 : arg2
    elsif opts.include?(:inherit)
      opts[:inherit]
    else
      (arg1 == :undef) ? r[:inherit_age] : arg1
    end
 
  r[:inherit_age] = 
    case r[:inherit_age]
    when Fixnum then r[:inherit_age]
    when true then -1
    when false, nil then 0
    else
      raise "inherit_age: #{r[:inherit_age]} must be one of true, false, nil, <fixnum>"
    end

  r
end

.class_methodsObject



30
31
32
# File 'lib/cascading_classes.rb', line 30

def self.class_methods
  [:to_hash, :nearest_parent_for, :top_level_parent_for?]
end

.const_missing(name) ⇒ Object

note, VERSION will be undetected unless called directly



19
20
21
22
23
24
# File 'lib/cascading_classes.rb', line 19

def self.const_missing(name)
  case name
  when :VERSION then "0.6.1"
  else super
  end
end

.default_blank(klass) ⇒ Object

returns a proc that takes an argument, evaluating

whether it is blank


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/cascading_classes/types.rb', line 55

def self.default_blank(klass)
  ## raise "Hell" unless basic_type.include klass  # sanity

=begin
  if is_container?(klass)
    return Proc.new{|v| v.respond_to?(:size) ? v.size.zero? : true}  # *eval*
  end
=end
  return Proc.new{|v| v.size.zero?} if is_container?(klass)  # *eval*

  klass = klass.to_s.to_sym

  case klass
  ## when :Proc, :Object then Proc.new{|v| v.nil? }
  when :Proc then Proc.new{|v| v.nil? }
  when :String then Proc.new{|v| v == ''}
  when :Fixnum, :Float then Proc.new{|v| v.nil?}
    ## Proc.new{|v| v.zero? }
  when :Symbol then Proc.new{|v| v.nil? or v.size.zero? }
  when :FalseClass, :TrueClass then Proc.new{|v| v.nil? }
  else
    self.undefined_type?(klass) ? Proc.new{|v| v.nil? } :
      Proc.new{|v| !!v}
  end
end

.default_ensure_type(klass, is_blank, new_proc) ⇒ Object

naive transformation to be applied for each type to ensure

that it stays that type

of course it would be better (wouldn’t it??) to react

to an object rather than a 'type'
but one of the features we support is to specify the
type before any default data has been given

procs take any value and should attempt to convert to specified type is_blank: proc that determines whether a value of that type is blank new_proc: proc that creates new objects of that type



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/cascading_classes/types.rb', line 116

def self.default_ensure_type(klass, is_blank, new_proc)
  ## raise "Hell" unless basic_type.include klass  # sanity
  klass = klass.to_s.to_sym
  pre = lambda{|v|
    is_blank.call(v) ? v : (v || new_proc.call) }

  case klass
  when :Hash
    lambda do |v|
      return v if is_blank.call(v)
      v = new_proc.call if v.nil?
      return v if CascadingClasses.is_of_type?(v, klass)
      v = v.to_hash if v.respond_to?(:to_hash)
      Hash === v ? v : {v => nil}
    end
  when :Array
    lambda do |v|
      return v if is_blank.call(v)
      v = new_proc.call if v.nil?
      return v if CascadingClasses.is_of_type?(v, klass)
      v = v.to_a if v.respond_to?(:to_a)
      Array === v ? v : [*v]
    end
  when :Set
    lambda do |v|
      return v if is_blank.call(v)
      v = new_proc.call if v.nil?
      return v if CascadingClasses.is_of_type?(v, klass)
      if v.respond_to? :to_set
        v.to_set
      elsif v.respond_to? :to_a
        Set.new(v.to_a)
      else
        Set.new([*v])
      end
    end
  ## when :Proc then Proc.new{|v| v}
  when :Proc
    Proc.new do |v|
      if v.nil? then nil
      elsif Proc === v then v
      else Proc.new{ v }
      end
    end
  when :Fixnum
    lambda{|v|
      return v if is_blank.call(v)
      v.to_i
    }
  when :Float
    lambda{|v|
      return v if is_blank.call(v)
      v.to_f
    }
  when :String
    lambda{|v|
      return v if is_blank.call(v)
      v.to_s
    }
  when :Symbol
    lambda{|v|
      return v if is_blank.call(v)
      v.respond_to?(:to_sym) ? v.to_sym : "#{v}".to_sym
    }
  when :FalseClass, :TrueClass
    lambda{|v|
      return v if is_blank.call(v)
      v.nil? ? nil : !!(v)
    }
  when :NilClass then Proc.new{|v| nil}
  ## when :Object then Proc.new{|v| v}   # unknown type
    ## Proc.new{|v| raise "hell"}  # sanity
  else
    if self.undefined_type?(klass) then Proc.new{|v| v}
    else
      lambda do |v|
        return v if is_blank.call(v)
        v = new_proc.call if v.nil?
        return v if CascadingClasses.is_of_type?(v, klass) 
        raise "error: don't know how to convert #{v} to object of type #{klass}"
      end
    end
  end
end

.default_new(klass) ⇒ Object

subclass’ initialition of own version of property



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/cascading_classes/types.rb', line 82

def self.default_new(klass)
  ## raise "Hell" unless basic_type.include klass  # sanity
  case klass
  when :String then Proc.new{ String.new }
  when :Fixnum then Proc.new{ nil }
    ## Proc.new{ 0 }
  when :Float then Proc.new{ nil }
    ## Proc.new{ 0.0 }
  when :Hash then Proc.new{ Hash.new }
  when :Array then Proc.new{ Array.new }
  when :Proc, :Symbol then Proc.new{ nil }
  when :FalseClass, :TrueClass, :NilClass then Proc.new{ nil }
  else
    if self.undefined_type?(klass) then Proc.new{ nil }
    else
      s = (klass.class == Class) ? klass : unsymbolize(klass)  # *eval*
      if s.class == Class and s.respond_to?(:new)
        Proc.new{ s.new }
      else
        raise "don't know how to create new instance for #{klass}"
      end
    end
  end
end

.extended(klass) ⇒ Object



308
309
310
# File 'lib/cascading_classes.rb', line 308

def self.extended(klass)
  apply(klass, false)
end

.get_type(default = nil, ensure_type = nil, ensure_val = nil) ⇒ Object

typically you will be given just a default value, from that

we need to determine the type: default.class.to_s.to_sym
in this case ensure_type will be nil and ensure_val will be ignored

but a type can also be specified explicitly:

A.cascade do
  name :hash => true
end

in this case ensure_type will be :Hash and ensure_val will be true



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/cascading_classes/types.rb', line 32

def self.get_type(default=nil, ensure_type=nil, ensure_val=nil)
  raise "type: #{ensure_type} must be set to true or false, not #{ensure_val}" unless
    ensure_val == true or ensure_val == false if ensure_type

  type = default.class.to_s.to_sym
  type = self.undefined_type if type == :NilClass

  if ensure_type
    if ensure_val then ensure_type
    ## elsif ensure_type == type then :Object
    elsif ensure_type == type then self.undefined_type
    else type
    end
  else type
  end
end

.illegal_namesObject

only applies to desired getter and setter names, not the property itself todo: allow one of [:cascade, :all!] to be overwritten todo: add :unset_property



37
38
39
40
# File 'lib/cascading_classes.rb', line 37

def self.illegal_names
  [:cascade, :all!, :set_property, :instance_eval, :instance_exec,
   :class_eval].concat(class_methods)
end

.included(klass) ⇒ Object

self.apply



304
305
306
# File 'lib/cascading_classes.rb', line 304

def self.included(klass)
  apply(klass, true)
end

.is_array_like?(obj, blk = nil) ⇒ Boolean

Returns:

  • (Boolean)


219
220
221
222
223
224
225
226
227
# File 'lib/cascading_classes/types.rb', line 219

def self.is_array_like?(obj, blk=nil)
  obj = unsymbolize(obj) if Symbol === obj
  return blk.call(obj) if blk
  s = (obj.class == Class) ? obj : obj.class
  return true if s.ancestors.include?(Array)
  meths = s.instance_methods
  meths.include?(:index) and meths.include?(:at) and
    meths.include?(:include?)
end

.is_container?(obj, blk = nil) ⇒ Boolean

Returns:

  • (Boolean)


201
202
203
204
205
206
207
# File 'lib/cascading_classes/types.rb', line 201

def self.is_container?(obj, blk=nil)
  obj = unsymbolize(obj) if Symbol === obj
  return blk.call(obj) if blk
  s = (obj.class == Class) ? obj : obj.class
  s.ancestors.include?(Enumerable) or
    s.instance_methods.include?(:each)
end

.is_hash_like?(obj, blk = nil) ⇒ Boolean

Returns:

  • (Boolean)


209
210
211
212
213
214
215
216
217
# File 'lib/cascading_classes/types.rb', line 209

def self.is_hash_like?(obj, blk=nil)
  obj = unsymbolize(obj) if Symbol === obj
  return blk.call(obj) if blk
  s = (obj.class == Class) ? obj : obj.class
  return true if s.ancestors.include?(Hash)
  meths = s.instance_methods
  meths.include?(:keys) and meths.include?(:include?) and
    (meths.include?(:[]) or meths.include?(:fetch))
end

.is_of_type?(obj, klass) ⇒ Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/cascading_classes/types.rb', line 49

def self.is_of_type?(obj, klass)
  obj.class.to_s.to_sym == klass
end

.parse_options(apply_to_instances, *properties, &block) ⇒ Object

returns hash: {…, attr => [default, inst_too], .. } todo: consider removing support for custom getters/setters in array syntax



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/cascading_classes/parse_options.rb', line 7

def self.parse_options(apply_to_instances, *properties, &block)
  err = "syntax error: list properties or supply block, but not both" 
  raise err if properties.size > 0 and block_given?

  res = {}
  properties.each do |prop|
    name, default, inst_too = prop, nil, apply_to_instances
    if Array === prop 
      case prop.size
      when 0 then next
      when 1 then name = prop[0]
      when 2 then name, default = prop[0..1]
      else
        name, default, inst_too = prop[0..2]
      end
    end
    name = name.to_sym
    ## type = default.nil? ? :Object : default.class.name.to_sym
    type = default.nil? ? CascadingClasses.undefined_type() : default.class.name.to_sym

    new_proc = CascadingClasses.default_new(type)    # proc creates new obj
    is_blank = CascadingClasses.default_blank(type)  # proc verifies blank values

    res[name] = {
      :default => default, :inst_too => !!inst_too,
      :getters => [name], :setters => ["#{name}=".to_sym],
      :type => type, :after_get => nil,
      :blank => is_blank,
      :proc_inst_exec => CascadingClasses.proc_inst_exec?,
      :block_inst_exec => CascadingClasses.block_inst_exec?,
      :new => new_proc,
      :inherit => CascadingClasses.undefined_type?(type) ?
        :undef : (CascadingClasses.is_container?(type) ? false : true),
      :ensure_type => CascadingClasses.default_ensure_type(type, is_blank, new_proc),
      :options_given => []
    }
  end

  if block_given?
    a = CascadingClasses::DSL.new(apply_to_instances, &block)

    names = a.instance_eval{ @names }

    pluck = proc{|v| a.instance_eval("@#{v}") }

    vars = [:default, :instances_too, :getters, :setters, :type, :after_get, :blank,
            :proc_inst_exec, :block_inst_exec, :new, :inherit, :ensure_type, 
            :options_given]

    names.each{|name| res[name] = Hash[ vars.map{|v| [v, pluck.call(v)[name]] } ] }
  end

  # validation
  res.each do |name, settings|

=begin  -> why must new values be blank? ans: b/c if they don't
    new_var = settings[:new].call
    raise "proc to create new #{settings[:type]} is #{new_var.inspect}, not blank" unless
      settings[:blank].call(new_var)
=end
  end

  res
end

.proc_inst_exec=(val) ⇒ Object



48
49
50
# File 'lib/cascading_classes.rb', line 48

def self.proc_inst_exec=(val)
  @proc_inst_exec = !!val
end

.proc_inst_exec?Boolean

global default: whether proc values are evaluated or instance_exec’d global default defaults to false

Returns:

  • (Boolean)


44
45
46
# File 'lib/cascading_classes.rb', line 44

def self.proc_inst_exec?
  @proc_inst_exec.nil? ? @proc_inst_exec = false : @proc_inst_exec
end

.respond_to?(const) ⇒ Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/cascading_classes.rb', line 26

def self.respond_to?(const)
  (const.to_s == 'VERSION') ? true : super
end

.undefined_typeObject



2
3
4
# File 'lib/cascading_classes/types.rb', line 2

def self.undefined_type
  :undefined_type
end

.undefined_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


6
7
8
# File 'lib/cascading_classes/types.rb', line 6

def self.undefined_type?(type)
  type == self.undefined_type
end

.unsymbolize(obj) ⇒ Object

turns :Hash into Hash, :Array into Array, …



16
17
18
19
# File 'lib/cascading_classes/types.rb', line 16

def self.unsymbolize(obj)
  res = eval(obj.to_s) rescue nil
  (res.class == Class) ? res : obj
end