lib/loaders/loader_base.rb in datashift-0.7.0 vs lib/loaders/loader_base.rb in datashift-0.8.0

- old
+ new

@@ -83,25 +83,37 @@ def self.set_multi_assoc_delim(x) @multi_assoc_delim = x; end + # Setup loading + # + # Options to drive building the method dictionary for a class, enabling headers to be mapped to operators on that class. + # # Options - # :instance_methods => true + # :load [default = true] : Load the method dictionary for object_class + # + # :reload : Force load of the method dictionary for object_class even if already loaded + # :instance_methods : Include setter type instance methods for assignment as well as AR columns + def initialize(object_class, object = nil, options = {}) @load_object_class = object_class # Gather names of all possible 'setter' methods on AR class (instance variables and associations) - DataShift::MethodDictionary.find_operators( @load_object_class, :reload => true, :instance_methods => options[:instance_methods] ) - - # Create dictionary of data on all possible 'setter' methods which can be used to - # populate or integrate an object of type @load_object_class - DataShift::MethodDictionary.build_method_details(@load_object_class) + unless(MethodDictionary::for?(object_class) && options[:reload] == false) + #puts "Building Method Dictionary for class #{object_class}" + DataShift::MethodDictionary.find_operators( @load_object_class, :reload => options[:reload], :instance_methods => options[:instance_methods] ) + + # Create dictionary of data on all possible 'setter' methods which can be used to + # populate or integrate an object of type @load_object_class + DataShift::MethodDictionary.build_method_details(@load_object_class) + end unless(options[:load] == false) @method_mapper = DataShift::MethodMapper.new - @options = options.clone + @options = options.dup # clone can cause issues like 'can't modify frozen hash' + @verbose = @options[:verbose] @headers = [] @default_data_objects ||= {} @@ -125,12 +137,12 @@ # strict : Raise an exception of any headers can't be mapped to an attribute/association # ignore : List of column headers to ignore when building operator map # mandatory : List of columns that must be present in headers # # force_inclusion : List of columns that do not map to any operator but should be includeed in processing. - # This provides the opportunity for loaders to provide specific methods to handle these fields - # when no direct operator is available on the modle or it's associations + # This provides the opportunity for loaders to provide specific methods to handle these fields + # when no direct operator is available on the model or it's associations # def perform_load( file_name, options = {} ) raise DataShift::BadFile, "Cannot load #{file_name} file not found." unless(File.exists?(file_name)) @@ -177,11 +189,11 @@ logger.error("Failed to map header row to set of database operators : #{e.inspect}") raise MappingDefinitionError, "Failed to map header row to set of database operators" end unless(@method_mapper.missing_methods.empty?) - puts "WARNING: Following column headings could not be mapped : #{@method_mapper.missing_methods.inspect}" + puts "WARNING: These headings couldn't be mapped to class #{load_object_class} : #{@method_mapper.missing_methods.inspect}" raise MappingDefinitionError, "Missing mappings for columns : #{@method_mapper.missing_methods.join(",")}" if(strict) end unless(@method_mapper.contains_mandatory?(mandatory) ) @method_mapper.missing_mandatory(mandatory).each { |e| puts "ERROR: Mandatory column missing - expected column '#{e}'" } @@ -212,22 +224,24 @@ else @load_object.errors.add_base( "No matching method found for column #{column_name}") end end - def get_record_by(klazz, field, value) - x = (@options[:sku_prefix]) ? "#{@options[:sku_prefix]}#{value}" : value - + + # Find a record for model klazz, looking up on field for x + # Responds to global Options : + # :case_sensitive : Default is a case insensitive lookup. + # :use_like : Attempts a lookup using ike and x% ratehr than equality + + def get_record_by(klazz, field, x) + begin if(@options[:case_sensitive]) - puts "Search case sensitive for [#{x}] on #{field}" if(@verbose) return klazz.send("find_by_#{field}", x) elsif(@options[:use_like]) - puts "Term : #{klazz}.where(\"#{field} LIKE '#{x}%'\")" if(@verbose) return klazz.where("#{field} like ?", "#{x}%").first else - puts "Term : #{klazz}.where(\"lower(#{field}) = '#{x.downcase}'\")" if(@verbose) return klazz.where("lower(#{field}) = ?", x.downcase).first end rescue => e logger.error("Exception attempting to find a record for [#{x}] on #{klazz}.#{field}") logger.error e.backtrace @@ -236,12 +250,14 @@ end end # Default values and over rides can be provided in YAML config file. # - # Any Configuration under key 'LoaderBase' is merged into this classes - # existing options - taking precedence. + # Any Config under key 'LoaderBase' is merged over existing options - taking precedence. + # + # Any Config under a key equal to the full name of the Loader class (e.g DataShift::SpreeHelper::ImageLoader) + # is merged over existing options - taking precedence. # # Format : # # LoaderClass: # option: value @@ -295,10 +311,14 @@ end if(data['LoaderBase']) @options.merge!(data['LoaderBase']) end + + if(data[self.class.name]) + @options.merge!(data[self.class.name]) + end logger.info("Loader Options : #{@options.inspect}") end # Set member variables to hold details and value. @@ -324,22 +344,35 @@ @current_value = "#{@current_value}#{postfixes(operator)}" if(postfixes(operator)) @current_value end - # return the find_by operator and the values to find - def get_find_operator_and_rest( column_data) - - find_operator, col_values = "",nil - + # Return the find_by operator and the rest of the (row,columns) data + # price:0.99 + # + # Column headings can already contain the operator so possible that row only contains + # 0.99 + # We leave it to caller to manage any other aspects or problems in 'rest' + # + def get_find_operator_and_rest(inbound_data) + + operator, rest = inbound_data.split(LoaderBase::name_value_delim) + + #puts "DEBUG inbound_data: #{inbound_data} => #{operator} , #{rest}" + + # Find by operator embedded in row takes precedence over operator in column heading if(@current_method_detail.find_by_operator) - find_operator, col_values = @current_method_detail.find_by_operator, column_data - else - find_operator, col_values = column_data.split(LoaderBase::name_value_delim) + # row contains 0.99 so rest is effectively operator, and operator is in method details + if(rest.nil?) + rest = operator + operator = @current_method_detail.find_by_operator + end end - return find_operator, col_values + #puts "DEBUG: get_find_operator_and_rest: #{operator} => #{rest}" + + return operator, rest end # Process a value string from a column. # Assigning value(s) to correct association on @load_object. # Method detail represents a column from a file and it's correlated AR associations. @@ -375,9 +408,11 @@ # end raise "Cannot perform DB find by #{find_operator}. Expected format key:value" unless(find_operator && col_values) find_by_values = col_values.split(LoaderBase::multi_value_delim) + + find_by_values << @current_method_detail.find_by_value if(@current_method_detail.find_by_value) if(find_by_values.size > 1) @current_value = @current_method_detail.operator_class.send("find_all_by_#{find_operator}", find_by_values ) \ No newline at end of file