Class: Doodle::DoodleInfo

Inherits:
Object
  • Object
show all
Defined in:
lib/doodle.rb

Overview

place to stash bookkeeping info

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(object) ⇒ DoodleInfo

Returns a new instance of DoodleInfo.



193
194
195
196
197
198
199
200
201
202
# File 'lib/doodle.rb', line 193

def initialize(object)
  @this = object
  @local_attributes = OrderedHash.new
  @local_validations = []
  @validation_on = true
  @local_conversions = {}
  @arg_order = []
  @errors = []
  @parent = nil
end

Instance Attribute Details

#arg_orderObject

Returns the value of attribute arg_order.



189
190
191
# File 'lib/doodle.rb', line 189

def arg_order
  @arg_order
end

#errorsObject

Returns the value of attribute errors.



190
191
192
# File 'lib/doodle.rb', line 190

def errors
  @errors
end

#local_attributesObject

Returns the value of attribute local_attributes.



185
186
187
# File 'lib/doodle.rb', line 185

def local_attributes
  @local_attributes
end

#local_conversionsObject

Returns the value of attribute local_conversions.



187
188
189
# File 'lib/doodle.rb', line 187

def local_conversions
  @local_conversions
end

#local_validationsObject

Returns the value of attribute local_validations.



186
187
188
# File 'lib/doodle.rb', line 186

def local_validations
  @local_validations
end

#parentObject

Returns the value of attribute parent.



191
192
193
# File 'lib/doodle.rb', line 191

def parent
  @parent
end

#validation_onObject

Returns the value of attribute validation_on.



188
189
190
# File 'lib/doodle.rb', line 188

def validation_on
  @validation_on
end

Instance Method Details

#attributes(tf = true) ⇒ Object

returns array of Attributes

  • if tf == true, returns all inherited attributes

  • if tf == false, returns only those attributes defined in the current object/class



261
262
263
264
265
266
267
268
# File 'lib/doodle.rb', line 261

def attributes(tf = true)
  results = handle_inherited_hash(tf, :local_attributes)
  # if an instance, include the singleton_class attributes
  if !@this.kind_of?(Class) && @this.singleton_class.doodle.respond_to?(:attributes)
    results = results.merge(@this.singleton_class.doodle.attributes)
  end
  results
end

#class_attributesObject

return class level attributes



271
272
273
274
275
276
277
278
279
280
281
# File 'lib/doodle.rb', line 271

def class_attributes
  attrs = OrderedHash.new
  if @this.kind_of?(Class)
    attrs = collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
      hash.merge(OrderedHash[*item])
    }.merge(@this.singleton_class.doodle.respond_to?(:attributes) ? @this.singleton_class.doodle.attributes : { })
    attrs
  else
    @this.class.doodle.class_attributes
  end
end

#collect_inherited(message) ⇒ Object

send message to all doodle_parents and collect results



236
237
238
239
240
241
242
243
244
245
246
# File 'lib/doodle.rb', line 236

def collect_inherited(message)
  result = []
  parents.each do |klass|
    if klass.respond_to?(:doodle) && klass.doodle.respond_to?(message)
      result.unshift(*klass.doodle.__send__(message))
    else
      break
    end
  end
  result
end

#conversions(tf = true) ⇒ Object

returns hash of conversions

  • if tf == true, returns all inherited conversions

  • if tf == false, returns only those conversions defined in the current object/class



310
311
312
# File 'lib/doodle.rb', line 310

def conversions(tf = true)
  handle_inherited_hash(tf, :local_conversions)
end

#defer_validation(&block) ⇒ Object

turn off validation, execute block, then set validation to same state as it was before defer_validation was called - can be nested fixme: move



348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/doodle.rb', line 348

def defer_validation(&block)
  old_validation = self.validation_on
  self.validation_on = false
  v = nil
  begin
    v = @this.instance_eval(&block)
  ensure
    self.validation_on = old_validation
  end
  @this.validate!(false)
  v
end

#handle_error(name, *args) ⇒ Object

handle errors either by collecting in :errors or raising an exception



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

def handle_error(name, *args)
  # don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
  if !errors.include?([name, *args])
    errors << [name, *args]
  end
  if Doodle.raise_exception_on_error
    raise(*args)
  end
end

#handle_inherited_hash(tf, method) ⇒ Object



248
249
250
251
252
253
254
255
256
# File 'lib/doodle.rb', line 248

def handle_inherited_hash(tf, method)
  if tf
    collect_inherited(method).inject(OrderedHash.new){ |hash, item|
      hash.merge(OrderedHash[*item])
    }.merge(@this.doodle.__send__(method))
  else
    @this.doodle.__send__(method)
  end
end

#initial_values(tf = true) ⇒ Object

fixme: move



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/doodle.rb', line 315

def initial_values(tf = true)
  attributes(tf).select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)|
    #p [:initial_values, a.name]
    hash[n] = case a.init
              when NilClass, TrueClass, FalseClass, Fixnum, Float, Bignum
                # uncloneable values
                #p [:initial_values, :special, a.name, a.init]
                a.init
              when DeferredBlock
                #p [:initial_values, self, DeferredBlock, a.name]
                begin
                  @this.instance_eval(&a.init.block)
                rescue Object => e
                  #p [:exception_in_deferred_block, e]
                  raise
                end
              else
                #p [:initial_values, :clone, a.name]
                begin
                  a.init.clone
                rescue Exception => e
                  warn "tried to clone #{a.init.class} in :init option"
                  #p [:initial_values, :exception, a.name, e]
                  a.init
                end
              end
    hash
  }
end

#initialize_from_hash(*args) ⇒ Object

helper function to initialize from hash - this is safe to use after initialization (validate! is called if this method is called after initialization)



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/doodle.rb', line 364

def initialize_from_hash(*args)
  #!p [:doodle_initialize_from_hash, :args, *args]
  defer_validation do
    # hash initializer
    # separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args 
    key_values, args = args.partition{ |x| x.kind_of?(Hash)}
    #DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args] }
    #!p [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args]

    # set up initial values with ~clones~ of specified values (so not shared between instances)
    #init_values = initial_values
    #!p [:init_values, init_values]
    
    # match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
    #arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
    arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
    #!p [self.class, :doodle_initialize_from_hash, :arg_keywords, arg_keywords]

    # merge all hash args into one
    key_values = key_values.inject(arg_keywords) { |hash, item|
      #!p [self.class, :doodle_initialize_from_hash, :merge, hash, item]
      hash.merge(item)
    }
    #!p [self.class, :doodle_initialize_from_hash, :key_values2, key_values]

    # convert keys to symbols (note not recursively - only first level == doodle keywords)
    Doodle::Utils.symbolize_keys!(key_values)
    #DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values2, key_values, :args2, args] }
    #!p [self.class, :doodle_initialize_from_hash, :key_values3, key_values]
    
    # create attributes
    key_values.keys.each do |key|
      #DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]] }
      #p [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]]
      if respond_to?(key)
        __send__(key, key_values[key])
      else
        # raise error if not defined
        __doodle__.handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' #{key_values[key].inspect}", (caller)
      end
    end
    # do init_values after user supplied values so init blocks can depend on user supplied values
    #p [:getting_init_values, instance_variables]
    __doodle__.initial_values.each do |key, value|
      if !key_values.key?(key) && respond_to?(key)
        __send__(key, value)
      end
    end
  end
end

#inspectObject

hide from inspect



204
205
206
# File 'lib/doodle.rb', line 204

def inspect
  ''
end

#lookup_attribute(name) ⇒ Object



297
298
299
300
301
302
303
304
305
# File 'lib/doodle.rb', line 297

def lookup_attribute(name)
  # (look at singleton attributes first)
  # fixme[this smells like a hack to me]
  if @this.class == Class
    class_attributes[name]
  else
    attributes[name]
  end
end

#parentsObject

provide an alternative inheritance chain that works for singleton classes as well as modules, classes and instances



221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/doodle.rb', line 221

def parents
  anc = if @this.respond_to?(:ancestors)
          if @this.ancestors.include?(@this)
            @this.ancestors[1..-1]
          else
            # singletons have no doodle_parents (they're orphans)
            []
          end
        else
          @this.class.ancestors
        end
  anc.select{|x| x.kind_of?(Class)}
end

#validations(tf = true) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/doodle.rb', line 283

def validations(tf = true)
  if tf
    # note: validations are handled differently to attributes and
    # conversions because ~all~ validations apply (so are stored
    # as an array), whereas attributes and conversions are keyed
    # by name and kind respectively, so only the most recent
    # applies
    
    local_validations + collect_inherited(:local_validations)
  else
    local_validations
  end
end