datashift 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +14 -98
- data/VERSION +1 -1
- data/datashift.gemspec +6 -4
- data/lib/applications/jruby/jexcel_file.rb +69 -52
- data/lib/datashift/method_detail.rb +3 -2
- data/lib/datashift/method_dictionary.rb +7 -2
- data/lib/datashift/method_mapper.rb +12 -3
- data/lib/datashift/model_mapper.rb +4 -4
- data/lib/generators/excel_generator.rb +17 -6
- data/lib/loaders/excel_loader.rb +3 -2
- data/lib/loaders/loader_base.rb +62 -27
- data/lib/loaders/paperclip/image_loader.rb +75 -0
- data/lib/loaders/spree/image_loader.rb +11 -42
- data/lib/loaders/spree/product_loader.rb +94 -52
- data/lib/thor/generate_excel.thor +5 -2
- data/lib/thor/spree/bootstrap_cleanup.thor +22 -16
- data/lib/thor/spree/products_images.thor +58 -45
- data/lib/thor/spree/reports.thor +66 -0
- data/spec/db/migrate/20110803201325_create_test_bed.rb +12 -1
- data/spec/excel_generator_spec.rb +39 -1
- data/spec/excel_loader_spec.rb +1 -0
- data/spec/fixtures/datashift_Spree_db.sqlite +0 -0
- data/spec/fixtures/datashift_test_models_db.sqlite +0 -0
- data/spec/fixtures/spree/SpreeMultiVariant.csv +4 -0
- data/spec/fixtures/spree/SpreeProductsSimple.csv +1 -1
- data/spec/fixtures/spree/SpreeProductsSimple.xls +0 -0
- data/spec/fixtures/test_model_defs.rb +4 -0
- data/spec/spree_images_loader_spec.rb +11 -29
- data/spec/spree_loader_spec.rb +60 -15
- data/tasks/db_tasks.rake +45 -0
- metadata +6 -4
- data/datashift-0.6.0.gem +0 -0
- data/datashift-0.6.1.gem +0 -0
| @@ -35,7 +35,7 @@ module DataShift | |
| 35 35 | 
             
                attr_reader :operator, :operator_type
         | 
| 36 36 |  | 
| 37 37 | 
             
                # TODO make it a list/primary keys
         | 
| 38 | 
            -
                attr_accessor :find_by_operator
         | 
| 38 | 
            +
                attr_accessor :find_by_operator, :find_by_value
         | 
| 39 39 |  | 
| 40 40 | 
             
                # Store the raw (client supplied) name against the active record  klass(model).
         | 
| 41 41 | 
             
                # Operator is the associated method call on klass,
         | 
| @@ -43,9 +43,10 @@ module DataShift | |
| 43 43 | 
             
                # 
         | 
| 44 44 | 
             
                # col_types can typically be derived from klass.columns - set of ActiveRecord::ConnectionAdapters::Column
         | 
| 45 45 |  | 
| 46 | 
            -
                def initialize(client_name, klass, operator, type, col_types = {}, find_by_operator = nil )
         | 
| 46 | 
            +
                def initialize(client_name, klass, operator, type, col_types = {}, find_by_operator = nil, find_by_value = nil )
         | 
| 47 47 | 
             
                  @klass, @name = klass, client_name
         | 
| 48 48 | 
             
                  @find_by_operator = find_by_operator
         | 
| 49 | 
            +
                  @find_by_value = find_by_value
         | 
| 49 50 |  | 
| 50 51 | 
             
                  if( MethodDetail::supported_types_enum.member?(type.to_sym) )
         | 
| 51 52 | 
             
                    @operator_type = type.to_sym
         | 
| @@ -17,6 +17,11 @@ module DataShift | |
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| 19 19 |  | 
| 20 | 
            +
                # Has the dictionary been populated for  klass
         | 
| 21 | 
            +
                def self.for?(klass)
         | 
| 22 | 
            +
                  return !(has_many[klass] || belongs_to[klass] || has_one[klass] || assignments[klass]).nil?
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                
         | 
| 20 25 | 
             
                # Create simple picture of all the operator names for assignment available on an AR model,
         | 
| 21 26 | 
             
                # grouped by type of association (includes belongs_to and has_many which provides both << and = )
         | 
| 22 27 | 
             
                # Options:
         | 
| @@ -43,7 +48,7 @@ module DataShift | |
| 43 48 | 
             
                  #puts "Belongs To Associations:", belongs_to[klass].inspect
         | 
| 44 49 |  | 
| 45 50 | 
             
                  # Find the has_one associations which can be populated via  Model.has_one_name = OtherArModelObject
         | 
| 46 | 
            -
                  if( options[:reload] ||  | 
| 51 | 
            +
                  if( options[:reload] || has_one[klass].nil? )
         | 
| 47 52 | 
             
                    has_one[klass] = klass.reflect_on_all_associations(:has_one).map { |i| i.name.to_s }
         | 
| 48 53 | 
             
                  end
         | 
| 49 54 |  | 
| @@ -145,7 +150,7 @@ module DataShift | |
| 145 150 |  | 
| 146 151 | 
             
                    MethodDetail::supported_types_enum.each do |t|
         | 
| 147 152 | 
             
                      method_detail = method_details_mgr.find(n, t)
         | 
| 148 | 
            -
                      return method_detail if(method_detail)
         | 
| 153 | 
            +
                      return method_detail.clone if(method_detail)
         | 
| 149 154 | 
             
                    end
         | 
| 150 155 |  | 
| 151 156 | 
             
                  end
         | 
| @@ -32,6 +32,9 @@ module DataShift | |
| 32 32 | 
             
                # in the heading .. i.e Column header => 'BlogPosts:user_id' 
         | 
| 33 33 | 
             
                # ... association has many BlogPosts selected via find_by_user_id
         | 
| 34 34 | 
             
                # 
         | 
| 35 | 
            +
                # in the heading .. i.e Column header => 'BlogPosts:user_name:John Smith' 
         | 
| 36 | 
            +
                # ... association has many BlogPosts selected via find_by_user_name("John Smith")
         | 
| 37 | 
            +
                #
         | 
| 35 38 | 
             
                def self.column_delim
         | 
| 36 39 | 
             
                  @column_delim ||= ':'
         | 
| 37 40 | 
             
                  @column_delim
         | 
| @@ -60,11 +63,12 @@ module DataShift | |
| 60 63 | 
             
                  columns.each do |name|
         | 
| 61 64 | 
             
                    if(name.nil? or name.empty?)
         | 
| 62 65 | 
             
                      logger.warn("Column list contains empty or null columns") 
         | 
| 66 | 
            +
                      @method_details << nil
         | 
| 63 67 | 
             
                      next
         | 
| 64 68 | 
             
                    end
         | 
| 65 69 |  | 
| 66 70 | 
             
                    operator, lookup = name.split(MethodMapper::column_delim) 
         | 
| 67 | 
            -
             | 
| 71 | 
            +
                   
         | 
| 68 72 | 
             
                    md = MethodDictionary::find_method_detail( klass, operator )
         | 
| 69 73 |  | 
| 70 74 | 
             
                    # TODO be nice if we could cheeck that the assoc on klass responds to the specified
         | 
| @@ -76,8 +80,13 @@ module DataShift | |
| 76 80 |  | 
| 77 81 | 
             
                    if(md)
         | 
| 78 82 |  | 
| 79 | 
            -
                       | 
| 80 | 
            -
             | 
| 83 | 
            +
                      if(lookup)
         | 
| 84 | 
            +
                        find_by, find_value = lookup.split(MethodMapper::column_delim) 
         | 
| 85 | 
            +
                        md.find_by_value    = find_value 
         | 
| 86 | 
            +
                        md.find_by_operator = find_by # TODO and klass.x.respond_to?(active_record_helper))
         | 
| 87 | 
            +
                        #puts "DEBUG: Method Detail #{md.name};#{md.operator} : find_by_operator #{md.find_by_operator}"
         | 
| 88 | 
            +
                      end
         | 
| 89 | 
            +
                          
         | 
| 81 90 | 
             
                      @method_details << md
         | 
| 82 91 | 
             
                    else
         | 
| 83 92 | 
             
                      @missing_methods << operator
         | 
| @@ -6,19 +6,19 @@ class ModelMapper | |
| 6 6 | 
             
              # e.g "Spree::Property" returns the Spree::Property class
         | 
| 7 7 | 
             
              # Raises exception if no such class found
         | 
| 8 8 | 
             
              def self.const_get_from_string(str)
         | 
| 9 | 
            -
                str.split('::').inject(Object) do |mod, class_name| 
         | 
| 9 | 
            +
                str.to_s.split('::').inject(Object) do |mod, class_name| 
         | 
| 10 10 | 
             
                  mod.const_get(class_name) 
         | 
| 11 11 | 
             
                end 
         | 
| 12 12 | 
             
              end 
         | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 13 | 
            +
             | 
| 14 | 
            +
             | 
| 15 15 | 
             
              # Similar to const_get_from_string except this version
         | 
| 16 16 | 
             
              # returns nil if no such class found
         | 
| 17 17 | 
             
              # Support modules e.g "Spree::Property"
         | 
| 18 18 | 
             
              # 
         | 
| 19 19 | 
             
              def self.class_from_string( str )
         | 
| 20 20 | 
             
                  begin
         | 
| 21 | 
            -
                    ModelMapper::const_get_from_string(str)  #Kernel.const_get(model)
         | 
| 21 | 
            +
                    ModelMapper::const_get_from_string(str.to_s)  #Kernel.const_get(model)
         | 
| 22 22 | 
             
                  rescue NameError => e
         | 
| 23 23 | 
             
                   return nil
         | 
| 24 24 | 
             
                  end
         | 
| @@ -27,7 +27,9 @@ module DataShift | |
| 27 27 | 
             
                  end
         | 
| 28 28 |  | 
| 29 29 | 
             
                  # Create an Excel file template (header row) representing supplied Model
         | 
| 30 | 
            -
             | 
| 30 | 
            +
                  # Options:
         | 
| 31 | 
            +
                  # * <tt>:autosize</tt> - Autosize all the columns
         | 
| 32 | 
            +
                  #
         | 
| 31 33 | 
             
                  def generate(klass, options = {})
         | 
| 32 34 |  | 
| 33 35 | 
             
                    prepare(klass, options)
         | 
| @@ -36,18 +38,25 @@ module DataShift | |
| 36 38 |  | 
| 37 39 | 
             
                    logger.info("ExcelGenerator saving generated template #{@filename}")
         | 
| 38 40 |  | 
| 41 | 
            +
                    @excel.autosize if(options[:autosize])
         | 
| 42 | 
            +
                    
         | 
| 39 43 | 
             
                    @excel.save( @filename )
         | 
| 40 44 | 
             
                  end
         | 
| 41 45 |  | 
| 42 46 |  | 
| 43 | 
            -
                  # Create an Excel file  | 
| 44 | 
            -
                  #  | 
| 45 | 
            -
                  # | 
| 47 | 
            +
                  # Create an Excel file template (header row) representing supplied Model
         | 
| 48 | 
            +
                  # and it's associations
         | 
| 49 | 
            +
                  # 
         | 
| 50 | 
            +
                  # Options:
         | 
| 51 | 
            +
                  # * <tt>:autosize</tt> - Autosize all the columns
         | 
| 52 | 
            +
                  #
         | 
| 53 | 
            +
                  # * <tt>:exclude</tt> - Associations to exclude.
         | 
| 54 | 
            +
                  #   You can specify a hash of {association_type => [array of association names] }
         | 
| 55 | 
            +
                  #   to exclude from the template.
         | 
| 46 56 | 
             
                  #   
         | 
| 47 | 
            -
                  # Possible values are given by MethodDetail::supported_types_enum
         | 
| 57 | 
            +
                  # Possible association_type values are given by MethodDetail::supported_types_enum
         | 
| 48 58 | 
             
                  #  ... [:assignment, :belongs_to, :has_one, :has_many]
         | 
| 49 59 | 
             
                  #
         | 
| 50 | 
            -
                  # Options
         | 
| 51 60 | 
             
                  def generate_with_associations(klass, options = {})
         | 
| 52 61 |  | 
| 53 62 | 
             
                    prepare(klass, options)
         | 
| @@ -69,6 +78,8 @@ module DataShift | |
| 69 78 | 
             
                    end
         | 
| 70 79 |  | 
| 71 80 | 
             
                    @excel.set_headers( headers )
         | 
| 81 | 
            +
                    
         | 
| 82 | 
            +
                    @excel.autosize if(options[:autosize])
         | 
| 72 83 |  | 
| 73 84 | 
             
                    @excel.save( filename() )
         | 
| 74 85 | 
             
                  end
         | 
    
        data/lib/loaders/excel_loader.rb
    CHANGED
    
    | @@ -66,7 +66,7 @@ module DataShift | |
| 66 66 | 
             
                    # For example if model has an attribute 'price' will map columns called Price, price, PRICE etc to this attribute
         | 
| 67 67 | 
             
                    map_headers_to_operators( @headers, options )
         | 
| 68 68 |  | 
| 69 | 
            -
                    logger.info "Excel Loader  | 
| 69 | 
            +
                    logger.info "Excel Loader processing #{@excel.num_rows} rows"
         | 
| 70 70 | 
             
                    load_object_class.transaction do
         | 
| 71 71 | 
             
                      @loaded_objects =  []
         | 
| 72 72 |  | 
| @@ -124,8 +124,9 @@ module DataShift | |
| 124 124 | 
             
                      end
         | 
| 125 125 | 
             
                    end
         | 
| 126 126 | 
             
                    puts "Excel loading stage complete - #{loaded_objects.size} rows added."
         | 
| 127 | 
            +
                    puts "There were NO failures." if failed_objects.empty?
         | 
| 127 128 |  | 
| 128 | 
            -
                    puts "WARNING : #{failed_objects.size} rows contained errors and #{failed_objects.size} records NOT created." unless failed_objects.empty?
         | 
| 129 | 
            +
                    puts "WARNING : Check logs : #{failed_objects.size} rows contained errors and #{failed_objects.size} records NOT created." unless failed_objects.empty?
         | 
| 129 130 | 
             
                  end
         | 
| 130 131 |  | 
| 131 132 | 
             
                  def value_at(row, column)
         | 
    
        data/lib/loaders/loader_base.rb
    CHANGED
    
    | @@ -85,21 +85,33 @@ module DataShift | |
| 85 85 | 
             
                def self.set_multi_assoc_delim(x) @multi_assoc_delim = x; end
         | 
| 86 86 |  | 
| 87 87 |  | 
| 88 | 
            +
                # Setup loading
         | 
| 89 | 
            +
                # 
         | 
| 90 | 
            +
                # Options to drive building the method dictionary for a class, enabling headers to be mapped to operators on that class.
         | 
| 91 | 
            +
                #  
         | 
| 88 92 | 
             
                # Options
         | 
| 89 | 
            -
                # | 
| 93 | 
            +
                #  :load [default = true] : Load the method dictionary for object_class
         | 
| 94 | 
            +
                #  
         | 
| 95 | 
            +
                #  :reload           : Force load of the method dictionary for object_class even if already loaded
         | 
| 96 | 
            +
                #  :instance_methods : Include setter type instance methods for assignment as well as AR columns
         | 
| 90 97 |  | 
| 98 | 
            +
                
         | 
| 91 99 | 
             
                def initialize(object_class, object = nil, options = {})
         | 
| 92 100 | 
             
                  @load_object_class = object_class
         | 
| 93 101 |  | 
| 94 102 | 
             
                  # Gather names of all possible 'setter' methods on AR class (instance variables and associations)
         | 
| 95 | 
            -
                   | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 103 | 
            +
                  unless(MethodDictionary::for?(object_class) && options[:reload] == false)
         | 
| 104 | 
            +
                    #puts "Building Method Dictionary for class #{object_class}"
         | 
| 105 | 
            +
                    DataShift::MethodDictionary.find_operators( @load_object_class, :reload => options[:reload], :instance_methods => options[:instance_methods] )
         | 
| 106 | 
            +
                    
         | 
| 107 | 
            +
                    # Create dictionary of data on all possible 'setter' methods which can be used to
         | 
| 108 | 
            +
                    # populate or integrate an object of type @load_object_class
         | 
| 109 | 
            +
                    DataShift::MethodDictionary.build_method_details(@load_object_class)
         | 
| 110 | 
            +
                  end unless(options[:load] == false)
         | 
| 100 111 |  | 
| 101 112 | 
             
                  @method_mapper = DataShift::MethodMapper.new
         | 
| 102 | 
            -
                  @options = options.clone
         | 
| 113 | 
            +
                  @options = options.dup    # clone can cause issues like 'can't modify frozen hash'
         | 
| 114 | 
            +
             | 
| 103 115 | 
             
                  @verbose = @options[:verbose]
         | 
| 104 116 | 
             
                  @headers = []
         | 
| 105 117 |  | 
| @@ -127,8 +139,8 @@ module DataShift | |
| 127 139 | 
             
                #  mandatory        : List of columns that must be present in headers
         | 
| 128 140 | 
             
                #  
         | 
| 129 141 | 
             
                #  force_inclusion  : List of columns that do not map to any operator but should be includeed in processing.
         | 
| 130 | 
            -
                # | 
| 131 | 
            -
                # | 
| 142 | 
            +
                #                     This provides the opportunity for loaders to provide specific methods to handle these fields
         | 
| 143 | 
            +
                #                     when no direct operator is available on the model or it's associations
         | 
| 132 144 | 
             
                #
         | 
| 133 145 | 
             
                def perform_load( file_name, options = {} )
         | 
| 134 146 |  | 
| @@ -179,7 +191,7 @@ module DataShift | |
| 179 191 | 
             
                  end
         | 
| 180 192 |  | 
| 181 193 | 
             
                  unless(@method_mapper.missing_methods.empty?)
         | 
| 182 | 
            -
                    puts "WARNING:  | 
| 194 | 
            +
                    puts "WARNING: These headings couldn't be mapped to class #{load_object_class} : #{@method_mapper.missing_methods.inspect}"
         | 
| 183 195 | 
             
                    raise MappingDefinitionError, "Missing mappings for columns : #{@method_mapper.missing_methods.join(",")}" if(strict)
         | 
| 184 196 | 
             
                  end
         | 
| 185 197 |  | 
| @@ -214,18 +226,20 @@ module DataShift | |
| 214 226 | 
             
                  end
         | 
| 215 227 | 
             
                end
         | 
| 216 228 |  | 
| 217 | 
            -
                 | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 229 | 
            +
                
         | 
| 230 | 
            +
                # Find a record for model klazz, looking up on field for x
         | 
| 231 | 
            +
                # Responds to global Options :
         | 
| 232 | 
            +
                # :case_sensitive : Default is a case insensitive lookup.
         | 
| 233 | 
            +
                # :use_like : Attempts a lookup using ike and x% ratehr than equality
         | 
| 234 | 
            +
                
         | 
| 235 | 
            +
                def get_record_by(klazz, field, x)
         | 
| 236 | 
            +
                
         | 
| 220 237 | 
             
                  begin
         | 
| 221 238 | 
             
                    if(@options[:case_sensitive]) 
         | 
| 222 | 
            -
                      puts "Search case sensitive for [#{x}] on #{field}" if(@verbose)
         | 
| 223 239 | 
             
                      return klazz.send("find_by_#{field}", x)
         | 
| 224 240 | 
             
                    elsif(@options[:use_like])
         | 
| 225 | 
            -
                      puts "Term : #{klazz}.where(\"#{field} LIKE '#{x}%'\")" if(@verbose)
         | 
| 226 241 | 
             
                      return klazz.where("#{field} like ?", "#{x}%").first
         | 
| 227 242 | 
             
                    else
         | 
| 228 | 
            -
                      puts "Term : #{klazz}.where(\"lower(#{field}) = '#{x.downcase}'\")" if(@verbose)
         | 
| 229 243 | 
             
                      return klazz.where("lower(#{field}) = ?", x.downcase).first
         | 
| 230 244 | 
             
                    end
         | 
| 231 245 | 
             
                  rescue => e
         | 
| @@ -238,8 +252,10 @@ module DataShift | |
| 238 252 |  | 
| 239 253 | 
             
                # Default values and over rides can be provided in YAML config file.
         | 
| 240 254 | 
             
                # 
         | 
| 241 | 
            -
                # Any  | 
| 242 | 
            -
                # | 
| 255 | 
            +
                # Any Config under key 'LoaderBase' is merged over existing options - taking precedence.
         | 
| 256 | 
            +
                #  
         | 
| 257 | 
            +
                # Any Config under a key equal to the full name of the Loader class (e.g DataShift::SpreeHelper::ImageLoader)
         | 
| 258 | 
            +
                # is merged over existing options - taking precedence.
         | 
| 243 259 | 
             
                # 
         | 
| 244 260 | 
             
                #  Format :
         | 
| 245 261 | 
             
                #  
         | 
| @@ -297,6 +313,10 @@ module DataShift | |
| 297 313 | 
             
                  if(data['LoaderBase'])
         | 
| 298 314 | 
             
                    @options.merge!(data['LoaderBase'])
         | 
| 299 315 | 
             
                  end
         | 
| 316 | 
            +
                   
         | 
| 317 | 
            +
                  if(data[self.class.name])    
         | 
| 318 | 
            +
                    @options.merge!(data[self.class.name])
         | 
| 319 | 
            +
                  end
         | 
| 300 320 |  | 
| 301 321 | 
             
                  logger.info("Loader Options : #{@options.inspect}")
         | 
| 302 322 | 
             
                end
         | 
| @@ -326,18 +346,31 @@ module DataShift | |
| 326 346 | 
             
                  @current_value
         | 
| 327 347 | 
             
                end
         | 
| 328 348 |  | 
| 329 | 
            -
                #  | 
| 330 | 
            -
                 | 
| 331 | 
            -
                
         | 
| 332 | 
            -
             | 
| 333 | 
            -
             | 
| 349 | 
            +
                # Return the find_by operator and the rest of the (row,columns) data
         | 
| 350 | 
            +
                #   price:0.99
         | 
| 351 | 
            +
                # 
         | 
| 352 | 
            +
                # Column headings can already contain the operator so possible that row only contains
         | 
| 353 | 
            +
                #   0.99
         | 
| 354 | 
            +
                # We leave it to caller to manage any other aspects or problems in 'rest'
         | 
| 355 | 
            +
                #
         | 
| 356 | 
            +
                def get_find_operator_and_rest(inbound_data)
         | 
| 357 | 
            +
                    
         | 
| 358 | 
            +
                  operator, rest = inbound_data.split(LoaderBase::name_value_delim) 
         | 
| 359 | 
            +
                 
         | 
| 360 | 
            +
                  #puts "DEBUG inbound_data: #{inbound_data} => #{operator} , #{rest}"
         | 
| 361 | 
            +
                   
         | 
| 362 | 
            +
                  # Find by operator embedded in row takes precedence over operator in column heading
         | 
| 334 363 | 
             
                  if(@current_method_detail.find_by_operator)
         | 
| 335 | 
            -
                     | 
| 336 | 
            -
             | 
| 337 | 
            -
             | 
| 364 | 
            +
                    # row contains 0.99 so rest is effectively operator, and operator is in method details
         | 
| 365 | 
            +
                    if(rest.nil?)
         | 
| 366 | 
            +
                      rest = operator
         | 
| 367 | 
            +
                      operator = @current_method_detail.find_by_operator
         | 
| 368 | 
            +
                    end
         | 
| 338 369 | 
             
                  end
         | 
| 339 370 |  | 
| 340 | 
            -
                   | 
| 371 | 
            +
                  #puts "DEBUG: get_find_operator_and_rest: #{operator} => #{rest}"
         | 
| 372 | 
            +
                  
         | 
| 373 | 
            +
                  return operator, rest
         | 
| 341 374 | 
             
                end
         | 
| 342 375 |  | 
| 343 376 | 
             
                # Process a value string from a column.
         | 
| @@ -377,6 +410,8 @@ module DataShift | |
| 377 410 | 
             
                        raise "Cannot perform DB find by #{find_operator}. Expected format key:value" unless(find_operator && col_values)
         | 
| 378 411 |  | 
| 379 412 | 
             
                        find_by_values = col_values.split(LoaderBase::multi_value_delim)
         | 
| 413 | 
            +
                        
         | 
| 414 | 
            +
                        find_by_values << @current_method_detail.find_by_value if(@current_method_detail.find_by_value)
         | 
| 380 415 |  | 
| 381 416 | 
             
                        if(find_by_values.size > 1)
         | 
| 382 417 |  | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            # Copyright:: (c) Autotelik Media Ltd 2012
         | 
| 2 | 
            +
            # Author ::   Tom Statter
         | 
| 3 | 
            +
            # Date ::     June 2012
         | 
| 4 | 
            +
            # License::   MIT. Free, Open Source.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # => Provides facilities for bulk uploading/exporting attachments provided by PaperClip 
         | 
| 7 | 
            +
            # gem
         | 
| 8 | 
            +
            require 'loader_base'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module DataShift
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              module ImageLoading
         | 
| 13 | 
            +
             
         | 
| 14 | 
            +
                include DataShift::Logging
         | 
| 15 | 
            +
                
         | 
| 16 | 
            +
                def self.get_files(path, options = {})
         | 
| 17 | 
            +
                  glob = (options['recursive'] || options[:recursive])  ? "**/*.{jpg,png,gif}" : "*.{jpg,png,gif}"
         | 
| 18 | 
            +
                  
         | 
| 19 | 
            +
                  Dir.glob("#{path}/#{glob}")
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              
         | 
| 22 | 
            +
                def get_file( attachment_path )
         | 
| 23 | 
            +
                  
         | 
| 24 | 
            +
                  unless File.exists?(attachment_path) && File.readable?(attachment_path)
         | 
| 25 | 
            +
                    logger.error("Cannot process Image from #{Dir.pwd}: Invalid Path #{attachment_path}")
         | 
| 26 | 
            +
                    raise "Cannot process Image : Invalid Path #{attachment_path}"
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                 
         | 
| 29 | 
            +
                  file = begin
         | 
| 30 | 
            +
                    File.new(attachment_path, "rb")
         | 
| 31 | 
            +
                  rescue => e
         | 
| 32 | 
            +
                    puts e.inspect
         | 
| 33 | 
            +
                    raise "ERROR : Failed to read image #{attachment_path}"
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                  
         | 
| 36 | 
            +
                  file
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
                
         | 
| 39 | 
            +
                # Note the paperclip attachment model defines the storage path via something like :
         | 
| 40 | 
            +
                # => :path => ":rails_root/public/blah/blahs/:id/:style/:basename.:extension"
         | 
| 41 | 
            +
                # Options 
         | 
| 42 | 
            +
                #   has_attached_file_name : Paperclip attachment name defined with macro 'has_attached_file :name'  e.g has_attached_file :avatar
         | 
| 43 | 
            +
                #
         | 
| 44 | 
            +
                def create_image(klass, attachment_path, viewable_record = nil, options = {})
         | 
| 45 | 
            +
                   
         | 
| 46 | 
            +
                  has_attached_file = options[:has_attached_file_name] || :attachment
         | 
| 47 | 
            +
                  
         | 
| 48 | 
            +
                  alt = if(options[:alt])
         | 
| 49 | 
            +
                    options[:alt]
         | 
| 50 | 
            +
                  else
         | 
| 51 | 
            +
                    (viewable_record and viewable_record.respond_to? :name) ? viewable_record.name : ""
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                
         | 
| 54 | 
            +
                  position = (viewable_record and viewable_record.respond_to?(:images)) ? viewable_record.images.length : 0
         | 
| 55 | 
            +
                      
         | 
| 56 | 
            +
                  file = get_file(attachment_path)
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  begin
         | 
| 59 | 
            +
                    
         | 
| 60 | 
            +
                    image = klass.new( 
         | 
| 61 | 
            +
                      {has_attached_file.to_sym => file, :viewable => viewable_record, :alt => alt, :position => position},
         | 
| 62 | 
            +
                      :without_protection => true
         | 
| 63 | 
            +
                    )  
         | 
| 64 | 
            +
                    
         | 
| 65 | 
            +
                    #image.attachment.reprocess!  not sure this is required anymore
         | 
| 66 | 
            +
                    
         | 
| 67 | 
            +
                    puts image.save ? "Success: Created Image: #{image.id} : #{image.attachment_file_name}" : "ERROR : Problem saving to DB Image: #{image.inspect}"
         | 
| 68 | 
            +
                  rescue => e
         | 
| 69 | 
            +
                    puts "PaperClip error - Problem creating an Image from : #{attachment_path}"
         | 
| 70 | 
            +
                    puts e.inspect, e.backtrace
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
                  
         | 
| 75 | 
            +
            end
         | 
| @@ -4,58 +4,24 @@ | |
| 4 4 | 
             
            # License::   MIT. Free, Open Source.
         | 
| 5 5 | 
             
            #
         | 
| 6 6 | 
             
            require 'loader_base'
         | 
| 7 | 
            +
            require 'paperclip/image_loader'
         | 
| 7 8 |  | 
| 8 9 | 
             
            module DataShift
         | 
| 9 10 |  | 
| 10 11 |  | 
| 11 | 
            -
              module  | 
| 12 | 
            +
              module DataShift::SpreeImageLoading
         | 
| 12 13 |  | 
| 13 14 | 
             
                include DataShift::Logging
         | 
| 15 | 
            +
                include DataShift::ImageLoading
         | 
| 14 16 |  | 
| 15 | 
            -
                def get_file( attachment_path )
         | 
| 16 | 
            -
                  
         | 
| 17 | 
            -
                  unless File.exists?(attachment_path) && File.readable?(attachment_path)
         | 
| 18 | 
            -
                    logger.error("Cannot process Image from #{Dir.pwd}: Invalid Path #{attachment_path}")
         | 
| 19 | 
            -
                    raise "Cannot process Image : Invalid Path #{attachment_path}"
         | 
| 20 | 
            -
                  end
         | 
| 21 | 
            -
                 
         | 
| 22 | 
            -
                  file = begin
         | 
| 23 | 
            -
                    File.new(attachment_path, "rb")
         | 
| 24 | 
            -
                  rescue => e
         | 
| 25 | 
            -
                    puts e.inspect
         | 
| 26 | 
            -
                    raise "ERROR : Failed to read image #{attachment_path}"
         | 
| 27 | 
            -
                  end
         | 
| 28 | 
            -
                  
         | 
| 29 | 
            -
                  file
         | 
| 30 | 
            -
                end
         | 
| 31 | 
            -
                
         | 
| 32 17 | 
             
                # Note the Spree Image model sets default storage path to
         | 
| 33 18 | 
             
                # => :path => ":rails_root/public/assets/products/:id/:style/:basename.:extension"
         | 
| 34 19 |  | 
| 35 20 | 
             
                def create_image(klass, attachment_path, viewable_record = nil, options = {})
         | 
| 36 | 
            -
                   
         | 
| 37 | 
            -
                  alt = if(options[:alt])
         | 
| 38 | 
            -
                    options[:alt]
         | 
| 39 | 
            -
                  else
         | 
| 40 | 
            -
                    (viewable_record and viewable_record.respond_to? :name) ? viewable_record.name : ""
         | 
| 41 | 
            -
                  end
         | 
| 42 | 
            -
                
         | 
| 43 | 
            -
                  position = (viewable_record and viewable_record.respond_to?(:images)) ? viewable_record.images.length : 0
         | 
| 44 | 
            -
                      
         | 
| 45 | 
            -
                  file = get_file(attachment_path)
         | 
| 46 | 
            -
                  
         | 
| 47 | 
            -
                  if(SpreeHelper::version.to_f > 1 && viewable_record.is_a?(Spree::Product) )
         | 
| 48 | 
            -
                   
         | 
| 49 | 
            -
                    image = klass.new( :attachment => file, :alt => alt, :position => position)  
         | 
| 50 21 |  | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
                   | 
| 54 | 
            -
                    image = klass.new( :attachment => file,:viewable => viewable_record, :alt => alt, :position => position)  
         | 
| 55 | 
            -
                  end
         | 
| 56 | 
            -
                  #image.attachment.reprocess!
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                  puts image.save ? "Success: Created Image: #{image.inspect}" : "ERROR : Problem saving to DB Image: #{image.inspect}"
         | 
| 22 | 
            +
                  viewable =  (SpreeHelper::version.to_f > 1 && viewable_record.is_a?(Spree::Product) ) ? viewable_record.master : viewable_record
         | 
| 23 | 
            +
                  
         | 
| 24 | 
            +
                  super(klass, attachment_path, viewable, options)
         | 
| 59 25 | 
             
                end
         | 
| 60 26 | 
             
              end
         | 
| 61 27 |  | 
| @@ -64,12 +30,15 @@ module DataShift | |
| 64 30 | 
             
                # TODO - extract this out of SpreeHelper to create  a general paperclip loader
         | 
| 65 31 | 
             
                class ImageLoader < LoaderBase
         | 
| 66 32 |  | 
| 67 | 
            -
                  include DataShift:: | 
| 33 | 
            +
                  include DataShift::SpreeImageLoading
         | 
| 68 34 | 
             
                  include DataShift::CsvLoading
         | 
| 69 35 | 
             
                  include DataShift::ExcelLoading
         | 
| 70 36 |  | 
| 71 37 | 
             
                  def initialize(image = nil, options = {})
         | 
| 72 | 
            -
                     | 
| 38 | 
            +
                    
         | 
| 39 | 
            +
                    opts = options.merge(:load => false)  # Don't need operators and no table Spree::Image
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    super( SpreeHelper::get_spree_class('Image'), image, opts )
         | 
| 73 42 |  | 
| 74 43 | 
             
                    if(SpreeHelper::version.to_f > 1.0 )
         | 
| 75 44 | 
             
                      @attachment_klazz  = DataShift::SpreeHelper::get_spree_class('Variant' )
         |