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