lib/datashift/method_detail.rb in datashift-0.0.1 vs lib/datashift/method_detail.rb in datashift-0.0.2

- old
+ new

@@ -1,237 +1,237 @@ -# Copyright:: (c) Autotelik Media Ltd 2011 -# Author :: Tom Statter -# Date :: Aug 2010 -# License:: MIT -# -# Details:: This class provides info and access to the individual population methods -# on an AR model. Populated by, and coupled with MethodMapper, -# which does the model interrogation work and stores sets of MethodDetails. -# -# Enables 'loaders' to iterate over the MethodMapper results set, -# and assign values to AR object, without knowing anything about that receiving object. -# -require 'to_b' - -module DataShift - - class MethodDetail - - def self.type_enum - @type_enum ||= Set[:assignment, :belongs_to, :has_one, :has_many] - @type_enum - end - - # When looking up an association, try each of these in turn till a match - # i.e find_by_name .. find_by_title and so on, lastly try the raw id - @@insistent_find_by_list ||= [:name, :title, :id] - - # Name is the raw, client supplied name - attr_reader :name, :col_type, :current_value - - attr_reader :operator, :operator_type - - # Store the raw (client supplied) name against the active record klass(model), operator and types - # col_types can typically be derived from klass.columns - set of ActiveRecord::ConnectionAdapters::Column - - def initialize(client_name, klass, operator, type, col_types = {} ) - @klass, @name = klass, client_name - - if( MethodDetail::type_enum.member?(type.to_sym) ) - @operator_type = type - else - raise "Bad operator Type #{type} passed to Method Detail" - end - - @operator = operator - - # Note : Not all assignments will currently have a column type, for example - # those that are derived from a delegate_belongs_to - if(col_types.empty?) - @col_type = klass.columns.find{ |col| col.name == operator } - else - @col_type = col_types[operator] - end - end - - - # Return the actual operator's name for supplied method type - # where type one of :assignment, :has_one, :belongs_to, :has_many etc - def operator_for( type ) - return operator if(@operator_type == type) - nil - end - - def operator?(name) - operator == name - end - - # Return the operator's expected class name, if can be derived, else nil - def operator_class_name() - @operator_class_name ||= if(operator_for(:has_many) || operator_for(:belongs_to) || operator_for(:has_one)) - begin - Kernel.const_get(operator.classify) - operator.classify - rescue; ""; end - - elsif(@col_type) - @col_type.type.to_s.classify - else - "" - end - - @operator_class_name - end - - # Return the operator's expected class, if can be derived, else nil - def operator_class() - @operator_class ||= if(operator_for(:has_many) || operator_for(:belongs_to) || operator_for(:has_one)) - begin - Kernel.const_get(operator.classify) - rescue; ""; end - - elsif(@col_type) - begin - Kernel.const_get(@col_type.type.to_s.classify) - rescue; nil; end - else - nil - end - - @operator_class - end - - - def assign(record, value ) - - @current_value = value - - puts "WARNING nil value supplied for Column [#{@name}]" if(@current_value.nil?) - - if( operator_for(:belongs_to) ) - - #puts "DEBUG : BELONGS_TO : #{@name} : #{operator} - Lookup #{@current_value} in DB" - insistent_belongs_to(record, @current_value) - - elsif( operator_for(:has_many) ) - - #puts "DEBUG : HAS_MANY : #{@name} : #{operator}(#{operator_class}) - Lookup #{@current_value} in DB" - if(value.is_a?(Array) || value.is_a?(operator_class)) - record.send(operator) << value - else - puts "ERROR #{value.class} - Not expected type for has_many #{operator} - cannot assign" - # TODO - Not expected type - maybe try to look it up somehow ?" - #insistent_has_many(record, @current_value) - end - - elsif( operator_for(:has_one) ) - - #puts "DEBUG : HAS_MANY : #{@name} : #{operator}(#{operator_class}) - Lookup #{@current_value} in DB" - if(value.is_a?(operator_class)) - record.send(operator + '=', value) - else - puts "ERROR #{value.class} - Not expected type for has_one #{operator} - cannot assign" - # TODO - Not expected type - maybe try to look it up somehow ?" - #insistent_has_many(record, @current_value) - end - - elsif( operator_for(:assignment) && @col_type ) - #puts "DEBUG : COl TYPE defined for #{@name} : #{@assignment} => #{@current_value} #{@col_type.type}" - #puts "DEBUG : COl TYPE CAST: #{@current_value} => #{@col_type.type_cast( @current_value ).inspect}" - record.send( operator + '=' , @col_type.type_cast( @current_value ) ) - - #puts "DEBUG : MethodDetails Assignment RESULT: #{record.send(operator)}" - - elsif( operator_for(:assignment) ) - #puts "DEBUG : Brute force assignment of value #{@current_value} supplied for Column [#{@name}]" - # brute force case for assignments without a column type (which enables us to do correct type_cast) - # so in this case, attempt straightforward assignment then if that fails, basic ops such as to_s, to_i, to_f etc - insistent_assignment(record, @current_value) - else - puts "WARNING: No operator found for assignment on #{self.inspect} for Column [#{@name}]" - end - end - - def pp - "#{@name} => #{operator}" - end - - - def self.insistent_method_list - @insistent_method_list ||= [:to_s, :to_i, :to_f, :to_b] - @insistent_method_list - end - - private - - # Attempt to find the associated object via id, name, title .... - def insistent_belongs_to( record, value ) - - if( value.class == operator_class) - record.send(operator) << value - else - - @@insistent_find_by_list.each do |x| - begin - next unless operator_class.respond_to?( "find_by_#{x}" ) - item = operator_class.send( "find_by_#{x}", value) - if(item) - record.send(operator + '=', item) - break - end - rescue => e - puts "ERROR: #{e.inspect}" - if(x == MethodDetail::insistent_method_list.last) - raise "I'm sorry I have failed to assign [#{value}] to #{@assignment}" unless value.nil? - end - end - end - end - end - - # Attempt to find the associated object via id, name, title .... - def insistent_has_many( record, value ) - - if( value.class == operator_class) - record.send(operator) << value - else - @@insistent_find_by_list.each do |x| - begin - item = operator_class.send( "find_by_#{x}", value) - if(item) - record.send(operator) << item - break - end - rescue => e - puts "ERROR: #{e.inspect}" - if(x == MethodDetail::insistent_method_list.last) - raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil? - end - end - end - end - end - - def insistent_assignment( record, value ) - #puts "DEBUG: RECORD CLASS #{record.class}" - op = operator + '=' - - begin - record.send(op, value) - rescue => e - MethodDetail::insistent_method_list.each do |f| - begin - record.send(op, value.send( f) ) - break - rescue => e - #puts "DEBUG: insistent_assignment: #{e.inspect}" - if f == MethodDetail::insistent_method_list.last - puts "I'm sorry I have failed to assign [#{value}] to #{operator}" - raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil? - end - end - end - end - end - end - +# Copyright:: (c) Autotelik Media Ltd 2011 +# Author :: Tom Statter +# Date :: Aug 2010 +# License:: MIT +# +# Details:: This class provides info and access to the individual population methods +# on an AR model. Populated by, and coupled with MethodMapper, +# which does the model interrogation work and stores sets of MethodDetails. +# +# Enables 'loaders' to iterate over the MethodMapper results set, +# and assign values to AR object, without knowing anything about that receiving object. +# +require 'to_b' + +module DataShift + + class MethodDetail + + def self.type_enum + @type_enum ||= Set[:assignment, :belongs_to, :has_one, :has_many] + @type_enum + end + + # When looking up an association, try each of these in turn till a match + # i.e find_by_name .. find_by_title and so on, lastly try the raw id + @@insistent_find_by_list ||= [:name, :title, :id] + + # Name is the raw, client supplied name + attr_reader :name, :col_type, :current_value + + attr_reader :operator, :operator_type + + # Store the raw (client supplied) name against the active record klass(model), operator and types + # col_types can typically be derived from klass.columns - set of ActiveRecord::ConnectionAdapters::Column + + def initialize(client_name, klass, operator, type, col_types = {} ) + @klass, @name = klass, client_name + + if( MethodDetail::type_enum.member?(type.to_sym) ) + @operator_type = type + else + raise "Bad operator Type #{type} passed to Method Detail" + end + + @operator = operator + + # Note : Not all assignments will currently have a column type, for example + # those that are derived from a delegate_belongs_to + if(col_types.empty?) + @col_type = klass.columns.find{ |col| col.name == operator } + else + @col_type = col_types[operator] + end + end + + + # Return the actual operator's name for supplied method type + # where type one of :assignment, :has_one, :belongs_to, :has_many etc + def operator_for( type ) + return operator if(@operator_type == type) + nil + end + + def operator?(name) + operator == name + end + + # Return the operator's expected class name, if can be derived, else nil + def operator_class_name() + @operator_class_name ||= if(operator_for(:has_many) || operator_for(:belongs_to) || operator_for(:has_one)) + begin + Kernel.const_get(operator.classify) + operator.classify + rescue; ""; end + + elsif(@col_type) + @col_type.type.to_s.classify + else + "" + end + + @operator_class_name + end + + # Return the operator's expected class, if can be derived, else nil + def operator_class() + @operator_class ||= if(operator_for(:has_many) || operator_for(:belongs_to) || operator_for(:has_one)) + begin + Kernel.const_get(operator.classify) + rescue; ""; end + + elsif(@col_type) + begin + Kernel.const_get(@col_type.type.to_s.classify) + rescue; nil; end + else + nil + end + + @operator_class + end + + + def assign(record, value ) + + @current_value = value + + # logger.info("WARNING nil value supplied for Column [#{@name}]") if(@current_value.nil?) + + if( operator_for(:belongs_to) ) + + #puts "DEBUG : BELONGS_TO : #{@name} : #{operator} - Lookup #{@current_value} in DB" + insistent_belongs_to(record, @current_value) + + elsif( operator_for(:has_many) ) + + #puts "DEBUG : HAS_MANY : #{@name} : #{operator}(#{operator_class}) - Lookup #{@current_value} in DB" + if(value.is_a?(Array) || value.is_a?(operator_class)) + record.send(operator) << value + else + puts "ERROR #{value.class} - Not expected type for has_many #{operator} - cannot assign" + # TODO - Not expected type - maybe try to look it up somehow ?" + #insistent_has_many(record, @current_value) + end + + elsif( operator_for(:has_one) ) + + #puts "DEBUG : HAS_MANY : #{@name} : #{operator}(#{operator_class}) - Lookup #{@current_value} in DB" + if(value.is_a?(operator_class)) + record.send(operator + '=', value) + else + puts "ERROR #{value.class} - Not expected type for has_one #{operator} - cannot assign" + # TODO - Not expected type - maybe try to look it up somehow ?" + #insistent_has_many(record, @current_value) + end + + elsif( operator_for(:assignment) && @col_type ) + #puts "DEBUG : COl TYPE defined for #{@name} : #{@assignment} => #{@current_value} #{@col_type.type}" + #puts "DEBUG : COl TYPE CAST: #{@current_value} => #{@col_type.type_cast( @current_value ).inspect}" + record.send( operator + '=' , @col_type.type_cast( @current_value ) ) + + #puts "DEBUG : MethodDetails Assignment RESULT: #{record.send(operator)}" + + elsif( operator_for(:assignment) ) + #puts "DEBUG : Brute force assignment of value #{@current_value} supplied for Column [#{@name}]" + # brute force case for assignments without a column type (which enables us to do correct type_cast) + # so in this case, attempt straightforward assignment then if that fails, basic ops such as to_s, to_i, to_f etc + insistent_assignment(record, @current_value) + else + puts "WARNING: No operator found for assignment on #{self.inspect} for Column [#{@name}]" + end + end + + def pp + "#{@name} => #{operator}" + end + + + def self.insistent_method_list + @insistent_method_list ||= [:to_s, :to_i, :to_f, :to_b] + @insistent_method_list + end + + private + + # Attempt to find the associated object via id, name, title .... + def insistent_belongs_to( record, value ) + + if( value.class == operator_class) + record.send(operator) << value + else + + @@insistent_find_by_list.each do |x| + begin + next unless operator_class.respond_to?( "find_by_#{x}" ) + item = operator_class.send( "find_by_#{x}", value) + if(item) + record.send(operator + '=', item) + break + end + rescue => e + puts "ERROR: #{e.inspect}" + if(x == MethodDetail::insistent_method_list.last) + raise "I'm sorry I have failed to assign [#{value}] to #{@assignment}" unless value.nil? + end + end + end + end + end + + # Attempt to find the associated object via id, name, title .... + def insistent_has_many( record, value ) + + if( value.class == operator_class) + record.send(operator) << value + else + @@insistent_find_by_list.each do |x| + begin + item = operator_class.send( "find_by_#{x}", value) + if(item) + record.send(operator) << item + break + end + rescue => e + puts "ERROR: #{e.inspect}" + if(x == MethodDetail::insistent_method_list.last) + raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil? + end + end + end + end + end + + def insistent_assignment( record, value ) + #puts "DEBUG: RECORD CLASS #{record.class}" + op = operator + '=' + + begin + record.send(op, value) + rescue => e + MethodDetail::insistent_method_list.each do |f| + begin + record.send(op, value.send( f) ) + break + rescue => e + #puts "DEBUG: insistent_assignment: #{e.inspect}" + if f == MethodDetail::insistent_method_list.last + puts "I'm sorry I have failed to assign [#{value}] to #{operator}" + raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil? + end + end + end + end + end + end + end \ No newline at end of file