diff --git a/app/models/product_import.rb b/app/models/product_import.rb index 266810f..137985e 100644 --- a/app/models/product_import.rb +++ b/app/models/product_import.rb @@ -28,15 +28,15 @@ def import_data! @names_of_products_before_import << product.name end log("#{@names_of_products_before_import}") - + rows = CSV.read(self.data_file.path) - + if IMPORT_PRODUCT_SETTINGS[:first_row_is_headings] col = get_column_mappings(rows[0]) else col = IMPORT_PRODUCT_SETTINGS[:column_mappings] end - + log("Importing products for #{self.data_file_file_name} began at #{Time.now}") rows[IMPORT_PRODUCT_SETTINGS[:rows_to_skip]..-1].each do |row| product_information = {} @@ -48,12 +48,12 @@ def import_data! col.each do |key, value| product_information[key] = row[value] end - - + + #Manually set available_on if it is not already set product_information[:available_on] = DateTime.now - 1.day if product_information[:available_on].nil? - + #Trim whitespace off the beginning and end of row fields row.each do |r| next unless r.is_a?(String) @@ -91,21 +91,21 @@ def import_data! private - - + + # create_variant_for - # This method assumes that some form of checking has already been done to + # This method assumes that some form of checking has already been done to # make sure that we do actually want to create a variant. # It performs a similar task to a product, but it also must pick up on # size/color options def create_variant_for(product, options = {:with => {}}) return if options[:with].nil? variant = product.variants.new - + #Remap the options - oddly enough, Spree's product model has master_price and cost_price, while #variant has price and cost_price. options[:with][:price] = options[:with].delete(:master_price) - + #First, set the primitive fields on the object (prices, etc.) options[:with].each do |field, value| variant.send("#{field}=", value) if variant.respond_to?("#{field}=") @@ -121,55 +121,57 @@ def create_variant_for(product, options = {:with => {}}) ) end end - - + + if variant.valid? variant.save - + #Associate our new variant with any new taxonomies - IMPORT_PRODUCT_SETTINGS[:taxonomy_fields].each do |field| + IMPORT_PRODUCT_SETTINGS[:taxonomy_fields].each do |field| associate_product_with_taxon(variant.product, field.to_s, options[:with][field.to_sym]) end - + #Finally, attach any images that have been specified IMPORT_PRODUCT_SETTINGS[:image_fields].each do |field| find_and_attach_image_to(variant, options[:with][field.to_sym]) end - + #Log a success message - log("Variant of SKU #{variant.sku} successfully imported.\n") + log("Variant of SKU #{variant.sku} successfully imported.\n") else log("A variant could not be imported - here is the information we have:\n" + "#{pp options[:with]}, :error") return false end end - - + + # create_product_using - # This method performs the meaty bit of the import - taking the parameters for the + # This method performs the meaty bit of the import - taking the parameters for the # product we have gathered, and creating the product and related objects. # It also logs throughout the method to try and give some indication of process. def create_product_using(params_hash) product = Product.new - - #The product is inclined to complain if we just dump all params - # into the product (including images and taxonomies). + + #The product is inclined to complain if we just dump all params + # into the product (including images and taxonomies). # What this does is only assigns values to products if the product accepts that field. params_hash.each do |field, value| product.send("#{field}=", value) if product.respond_to?("#{field}=") end - + + after_product_built(product, params_hash) + #We can't continue without a valid product here unless product.valid? log("A product could not be imported - here is the information we have:\n" + "#{pp params_hash}, :error") return false end - + #Just log which product we're processing log(product.name) - + #This should be caught by code in the main import code that checks whether to create #variants or not. Since that check can be turned off, however, we should double check. if @names_of_products_before_import.include? product.name @@ -177,46 +179,46 @@ def create_product_using(params_hash) else #Save the object before creating asssociated objects product.save - - + + #Associate our new product with any taxonomies that we need to worry about - IMPORT_PRODUCT_SETTINGS[:taxonomy_fields].each do |field| + IMPORT_PRODUCT_SETTINGS[:taxonomy_fields].each do |field| associate_product_with_taxon(product, field.to_s, params_hash[field.to_sym]) end - + #Finally, attach any images that have been specified IMPORT_PRODUCT_SETTINGS[:image_fields].each do |field| find_and_attach_image_to(product, params_hash[field.to_sym]) end - + if IMPORT_PRODUCT_SETTINGS[:multi_domain_importing] && product.respond_to?(:stores) begin store = Store.find( - :first, - :conditions => ["id = ? OR code = ?", - params_hash[IMPORT_PRODUCT_SETTINGS[:store_field]], + :first, + :conditions => ["id = ? OR code = ?", + params_hash[IMPORT_PRODUCT_SETTINGS[:store_field]], params_hash[IMPORT_PRODUCT_SETTINGS[:store_field]] ] ) - + product.stores << store rescue log("#{product.name} could not be associated with a store. Ensure that Spree's multi_domain extension is installed and that fields are mapped to the CSV correctly.") end end - + #Log a success message log("#{product.name} successfully imported.\n") end return true end - + # get_column_mappings # This method attempts to automatically map headings in the CSV files # with fields in the product and variant models. # If the headings of columns are going to be called something other than this, # or if the files will not have headings, then the manual initializer - # mapping of columns must be used. + # mapping of columns must be used. # Row is an array of headings for columns - SKU, Master Price, etc.) # @return a hash of symbol heading => column index pairs def get_column_mappings(row) @@ -226,8 +228,8 @@ def get_column_mappings(row) end mappings end - - + + ### MISC HELPERS #### #Log a message to a file - logs in standard Rails format to logfile set up in the import_products initializer @@ -245,11 +247,11 @@ def log(message, severity = :info) ### IMAGE HELPERS ### # find_and_attach_image_to - # This method attaches images to products. The images may come + # This method attaches images to products. The images may come # from a local source (i.e. on disk), or they may be online (HTTP/HTTPS). def find_and_attach_image_to(product_or_variant, filename) return if filename.blank? - + #The image can be fetched from an HTTP or local source - either method returns a Tempfile file = filename =~ /\Ahttp[s]*:\/\// ? fetch_remote_image(filename) : fetch_local_image(filename) #An image has an attachment (the image file) and some object which 'views' it @@ -295,32 +297,45 @@ def fetch_remote_image(filename) # associate_product_with_taxon # This method accepts three formats of taxon hierarchy strings which will # associate the given products with taxons: - # 1. A string on it's own will will just find or create the taxon and + # 1. A string on it's own will will just find or create the taxon and # add the product to it. e.g. taxonomy = "Category", taxon_hierarchy = "Tools" will # add the product to the 'Tools' category. # 2. A item > item > item structured string will read this like a tree - allowing - # a particular taxon to be picked out + # a particular taxon to be picked out # 3. An item > item & item > item will work as above, but will associate multiple - # taxons with that product. This form should also work with format 1. + # taxons with that product. This form should also work with format 1. def associate_product_with_taxon(product, taxonomy, taxon_hierarchy) return if product.nil? || taxonomy.nil? || taxon_hierarchy.nil? - #Using find_or_create_by_name is more elegant, but our magical params code automatically downcases + #Using find_or_create_by_name is more elegant, but our magical params code automatically downcases # the taxonomy name, so unless we are using MySQL, this isn't going to work. taxonomy_name = taxonomy taxonomy = Taxonomy.find(:first, :conditions => ["lower(name) = ?", taxonomy]) taxonomy = Taxonomy.create(:name => taxonomy_name.capitalize) if taxonomy.nil? && IMPORT_PRODUCT_SETTINGS[:create_missing_taxonomies] - + taxon_hierarchy.split(/\s*\&\s*/).each do |hierarchy| hierarchy = hierarchy.split(/\s*>\s*/) last_taxon = taxonomy.root hierarchy.each do |taxon| last_taxon = last_taxon.children.find_or_create_by_name_and_taxonomy_id(taxon, taxonomy.id) end - + #Spree only needs to know the most detailed taxonomy item product.taxons << last_taxon unless product.taxons.include?(last_taxon) end end ### END TAXON HELPERS ### + + # May be implemented via decorator if useful: + # + # ProductImport.class_eval do + # + # private + # + # def after_product_built(product, params_hash) + # # so something with the product + # end + # end + def after_product_built(product, params_hash) + end end