datashift 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +63 -64
- data/Rakefile +4 -7
- data/VERSION +1 -1
- data/datashift.gemspec +92 -62
- data/lib/applications/apache_poi_extensions.rb +62 -0
- data/lib/applications/excel.rb +78 -0
- data/lib/applications/excel_base.rb +65 -0
- data/lib/applications/jexcel_file.rb +222 -0
- data/lib/applications/jexcel_file_extensions.rb +244 -0
- data/lib/applications/jruby/{jexcel_file.rb → old_pre_proxy_jexcel_file.rb} +0 -0
- data/lib/applications/ruby_poi_translations.rb +64 -0
- data/lib/applications/spreadsheet_extensions.rb +31 -0
- data/lib/datashift/method_details_manager.rb +4 -0
- data/lib/exporters/csv_exporter.rb +3 -1
- data/lib/exporters/excel_exporter.rb +59 -74
- data/lib/generators/excel_generator.rb +70 -74
- data/lib/guards.rb +57 -0
- data/lib/loaders/excel_loader.rb +105 -105
- data/lib/loaders/loader_base.rb +43 -21
- data/lib/loaders/paperclip/attachment_loader.rb +104 -0
- data/lib/loaders/paperclip/datashift_paperclip.rb +78 -0
- data/lib/loaders/paperclip/{image_loader.rb → image_loading.rb} +2 -18
- data/lib/thor/{generate_excel.thor → generate.thor} +48 -0
- data/lib/thor/paperclip.thor +85 -0
- data/lib/thor/tools.thor +23 -2
- data/spec/Gemfile +1 -7
- data/spec/csv_exporter_spec.rb +4 -4
- data/spec/csv_loader_spec.rb +1 -1
- data/spec/excel_exporter_spec.rb +43 -45
- data/spec/excel_generator_spec.rb +132 -60
- data/spec/excel_loader_spec.rb +134 -140
- data/spec/excel_spec.rb +179 -0
- data/spec/fixtures/ProjectsMultiCategoriesHeaderLookup.xls +0 -0
- data/spec/fixtures/config/database.yml +2 -2
- data/spec/fixtures/db/datashift_test_models_db.sqlite +0 -0
- data/spec/{db → fixtures/db}/migrate/20110803201325_create_test_bed.rb +0 -0
- data/spec/fixtures/load_datashift.thor +3 -0
- data/spec/fixtures/models/category.rb +7 -0
- data/spec/fixtures/models/empty.rb +2 -0
- data/spec/fixtures/models/loader_release.rb +10 -0
- data/spec/fixtures/models/long_and_complex_table_linked_to_version.rb +6 -0
- data/spec/fixtures/models/milestone.rb +8 -0
- data/spec/fixtures/models/owner.rb +5 -0
- data/spec/fixtures/models/project.rb +26 -0
- data/spec/fixtures/models/test_model_defs.rb +67 -0
- data/spec/fixtures/models/version.rb +7 -0
- data/spec/loader_spec.rb +4 -3
- data/spec/method_dictionary_spec.rb +7 -6
- data/spec/method_mapper_spec.rb +3 -2
- data/spec/rails_sandbox/.gitignore +15 -0
- data/spec/rails_sandbox/Gemfile +40 -0
- data/spec/rails_sandbox/README.rdoc +261 -0
- data/spec/rails_sandbox/Rakefile +7 -0
- data/spec/rails_sandbox/app/assets/images/rails.png +0 -0
- data/spec/rails_sandbox/app/assets/javascripts/application.js +15 -0
- data/spec/rails_sandbox/app/assets/stylesheets/application.css +13 -0
- data/spec/rails_sandbox/app/controllers/application_controller.rb +3 -0
- data/spec/rails_sandbox/app/helpers/application_helper.rb +2 -0
- data/spec/rails_sandbox/app/mailers/.gitkeep +0 -0
- data/spec/rails_sandbox/app/models/.gitkeep +0 -0
- data/spec/rails_sandbox/app/models/category.rb +7 -0
- data/spec/rails_sandbox/app/models/empty.rb +2 -0
- data/spec/rails_sandbox/app/models/loader_release.rb +10 -0
- data/spec/rails_sandbox/app/models/long_and_complex_table_linked_to_version.rb +6 -0
- data/spec/rails_sandbox/app/models/milestone.rb +8 -0
- data/spec/rails_sandbox/app/models/owner.rb +5 -0
- data/spec/rails_sandbox/app/models/project.rb +26 -0
- data/spec/rails_sandbox/app/models/test_model_defs.rb +67 -0
- data/spec/rails_sandbox/app/models/version.rb +7 -0
- data/spec/rails_sandbox/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_sandbox/config.ru +4 -0
- data/spec/rails_sandbox/config/application.rb +62 -0
- data/spec/rails_sandbox/config/boot.rb +6 -0
- data/spec/rails_sandbox/config/database.yml +20 -0
- data/spec/rails_sandbox/config/environment.rb +5 -0
- data/spec/rails_sandbox/config/environments/development.rb +37 -0
- data/spec/rails_sandbox/config/environments/production.rb +67 -0
- data/spec/rails_sandbox/config/environments/test.rb +37 -0
- data/spec/rails_sandbox/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_sandbox/config/initializers/inflections.rb +15 -0
- data/spec/rails_sandbox/config/initializers/mime_types.rb +5 -0
- data/spec/rails_sandbox/config/initializers/secret_token.rb +7 -0
- data/spec/rails_sandbox/config/initializers/session_store.rb +8 -0
- data/spec/rails_sandbox/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_sandbox/config/locales/en.yml +5 -0
- data/spec/rails_sandbox/config/routes.rb +58 -0
- data/spec/rails_sandbox/db/migrate/20110803201325_create_test_bed.rb +96 -0
- data/spec/rails_sandbox/db/schema.rb +81 -0
- data/spec/rails_sandbox/db/seeds.rb +7 -0
- data/spec/rails_sandbox/lib/assets/.gitkeep +0 -0
- data/spec/rails_sandbox/lib/tasks/.gitkeep +0 -0
- data/spec/rails_sandbox/log/.gitkeep +0 -0
- data/spec/rails_sandbox/public/404.html +26 -0
- data/spec/rails_sandbox/public/422.html +26 -0
- data/spec/rails_sandbox/public/500.html +25 -0
- data/spec/rails_sandbox/public/favicon.ico +0 -0
- data/spec/rails_sandbox/public/index.html +241 -0
- data/spec/rails_sandbox/public/robots.txt +5 -0
- data/spec/rails_sandbox/script/rails +6 -0
- data/spec/rails_sandbox/test/fixtures/.gitkeep +0 -0
- data/spec/rails_sandbox/test/functional/.gitkeep +0 -0
- data/spec/rails_sandbox/test/integration/.gitkeep +0 -0
- data/spec/rails_sandbox/test/performance/browsing_test.rb +12 -0
- data/spec/rails_sandbox/test/test_helper.rb +13 -0
- data/spec/rails_sandbox/test/unit/.gitkeep +0 -0
- data/spec/rails_sandbox/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/rails_sandbox/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/rails_sandbox/vendor/plugins/.gitkeep +0 -0
- data/spec/spec_helper.rb +144 -121
- data/spec/thor_spec.rb +34 -14
- metadata +207 -194
- data/lib/helpers/spree_helper.rb +0 -213
- data/lib/loaders/spreadsheet_loader.rb +0 -144
- data/lib/loaders/spree/image_loader.rb +0 -90
- data/lib/loaders/spree/product_loader.rb +0 -354
- data/lib/thor/spree/bootstrap_cleanup.thor +0 -61
- data/lib/thor/spree/products_images.thor +0 -252
- data/lib/thor/spree/reports.thor +0 -56
- data/public/spree/products/large/DEMO_001_ror_bag.jpeg +0 -0
- data/public/spree/products/large/DEMO_002_Powerstation.jpg +0 -0
- data/public/spree/products/large/DEMO_003_ror_mug.jpeg +0 -0
- data/public/spree/products/mini/DEMO_001_ror_bag.jpeg +0 -0
- data/public/spree/products/mini/DEMO_002_Powerstation.jpg +0 -0
- data/public/spree/products/mini/DEMO_003_ror_mug.jpeg +0 -0
- data/public/spree/products/original/DEMO_001_ror_bag.jpeg +0 -0
- data/public/spree/products/original/DEMO_002_Powerstation.jpg +0 -0
- data/public/spree/products/original/DEMO_003_ror_mug.jpeg +0 -0
- data/public/spree/products/product/DEMO_001_ror_bag.jpeg +0 -0
- data/public/spree/products/product/DEMO_002_Powerstation.jpg +0 -0
- data/public/spree/products/product/DEMO_003_ror_mug.jpeg +0 -0
- data/public/spree/products/small/DEMO_001_ror_bag.jpeg +0 -0
- data/public/spree/products/small/DEMO_002_Powerstation.jpg +0 -0
- data/public/spree/products/small/DEMO_003_ror_mug.jpeg +0 -0
- data/spec/fixtures/datashift_Spree_db.sqlite +0 -0
- data/spec/fixtures/datashift_test_models_db.sqlite +0 -0
- data/spec/fixtures/negative/SpreeProdMiss1Mandatory.csv +0 -4
- data/spec/fixtures/negative/SpreeProdMiss1Mandatory.xls +0 -0
- data/spec/fixtures/negative/SpreeProdMissManyMandatory.csv +0 -4
- data/spec/fixtures/negative/SpreeProdMissManyMandatory.xls +0 -0
- data/spec/fixtures/spree/SpreeImages.xls +0 -0
- data/spec/fixtures/spree/SpreeMultiVariant.csv +0 -4
- data/spec/fixtures/spree/SpreeProducts.csv +0 -4
- data/spec/fixtures/spree/SpreeProducts.xls +0 -0
- data/spec/fixtures/spree/SpreeProductsDefaults.yml +0 -15
- data/spec/fixtures/spree/SpreeProductsMandatoryOnly.xls +0 -0
- data/spec/fixtures/spree/SpreeProductsMultiColumn.csv +0 -4
- data/spec/fixtures/spree/SpreeProductsMultiColumn.xls +0 -0
- data/spec/fixtures/spree/SpreeProductsSimple.csv +0 -4
- data/spec/fixtures/spree/SpreeProductsSimple.xls +0 -0
- data/spec/fixtures/spree/SpreeProductsWithImages.csv +0 -4
- data/spec/fixtures/spree/SpreeProductsWithImages.xls +0 -0
- data/spec/fixtures/spree/SpreeZoneExample.csv +0 -5
- data/spec/fixtures/spree/SpreeZoneExample.xls +0 -0
- data/spec/spree_exporter_spec.rb +0 -72
- data/spec/spree_generator_spec.rb +0 -96
- data/spec/spree_images_loader_spec.rb +0 -107
- data/spec/spree_loader_spec.rb +0 -375
- data/spec/spree_method_mapping_spec.rb +0 -226
- data/spec/spree_variants_loader_spec.rb +0 -189
- data/tasks/export/excel_generator.rake +0 -102
- data/tasks/import/excel.rake +0 -75
- data/test/helper.rb +0 -18
- data/test/test_interact.rb +0 -7
data/lib/helpers/spree_helper.rb
DELETED
@@ -1,213 +0,0 @@
|
|
1
|
-
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
-
# Author :: Tom Statter
|
3
|
-
# Date :: Aug 2011
|
4
|
-
# License:: MIT
|
5
|
-
#
|
6
|
-
# Details:: Spree Helper for Product Loading.
|
7
|
-
#
|
8
|
-
# Utils to try to manage different Spree versions seamlessly.
|
9
|
-
#
|
10
|
-
# Spree Helper for RSpec testing, enables mixing in Support for
|
11
|
-
# testing or loading Rails Spree e-commerce.
|
12
|
-
#
|
13
|
-
# The Spree version you want to test should be picked up from spec/Gemfile
|
14
|
-
#
|
15
|
-
# Since datashift gem is not a Rails app or a Spree App, provides utilities to internally
|
16
|
-
# create a Spree Database, and to load Spree components, enabling standalone testing.
|
17
|
-
#
|
18
|
-
# => Has been tested with 0.11.2, 0.7, 1.0.0
|
19
|
-
#
|
20
|
-
# => TODO - See if we can improve DB creation/migration ....
|
21
|
-
# N.B Some or all of Spree Tests may fail very first time run,
|
22
|
-
# as the database is auto generated
|
23
|
-
# =>
|
24
|
-
|
25
|
-
module DataShift
|
26
|
-
|
27
|
-
module SpreeHelper
|
28
|
-
|
29
|
-
|
30
|
-
def self.root
|
31
|
-
Gem.loaded_specs['spree_core'] ? Gem.loaded_specs['spree_core'].full_gem_path : ""
|
32
|
-
end
|
33
|
-
|
34
|
-
# Helpers so we can cope with both pre 1.0 and post 1.0 versions of Spree in same datashift version
|
35
|
-
|
36
|
-
def self.get_spree_class(x)
|
37
|
-
if(is_namespace_version())
|
38
|
-
ModelMapper::class_from_string("Spree::#{x}")
|
39
|
-
else
|
40
|
-
ModelMapper::class_from_string(x.to_s)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.get_product_class
|
45
|
-
if(is_namespace_version())
|
46
|
-
Spree::Product
|
47
|
-
else
|
48
|
-
Product
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.version
|
53
|
-
Gem.loaded_specs['spree'] ? Gem.loaded_specs['spree'].version.version : "0.0.0"
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.is_namespace_version
|
57
|
-
SpreeHelper::version.to_f >= 1
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.lib_root
|
61
|
-
File.join(root, 'lib')
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.app_root
|
65
|
-
File.join(root, 'app')
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.load()
|
69
|
-
require 'spree'
|
70
|
-
require 'spree_core'
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
# Datashift is usually included and tasks pulled in by a parent/host application.
|
75
|
-
# So here we are hacking our way around the fact that datashift is not a Rails/Spree app/engine
|
76
|
-
# so that we can ** run our specs ** directly in datashift library
|
77
|
-
# i.e without ever having to install datashift in a host application
|
78
|
-
#
|
79
|
-
# NOTES:
|
80
|
-
# => Will chdir into the sandbox to load environment as need to mimic being at root of a rails project
|
81
|
-
# chdir back after environment loaded
|
82
|
-
|
83
|
-
def self.boot( database_env)
|
84
|
-
|
85
|
-
if( ! is_namespace_version )
|
86
|
-
db_connect( database_env )
|
87
|
-
@dslog.info "Booting Spree using pre 1.0.0 version"
|
88
|
-
boot_pre_1
|
89
|
-
@dslog.info "Booted Spree using pre 1.0.0 version"
|
90
|
-
else
|
91
|
-
|
92
|
-
db_connect( database_env )
|
93
|
-
|
94
|
-
@dslog.info "Booting Spree using version #{SpreeHelper::version}"
|
95
|
-
|
96
|
-
require 'rails/all'
|
97
|
-
|
98
|
-
store_path = Dir.pwd
|
99
|
-
|
100
|
-
spree_sanbox_app = File.expand_path('../../../spec/sandbox', __FILE__)
|
101
|
-
|
102
|
-
unless(File.exists?(spree_sanbox_app))
|
103
|
-
puts "Creating new Rails sandbox for Spree : #{spree_sanbox_app}"
|
104
|
-
Dir.chdir( File.expand_path( "#{spree_sanbox_app}/..") )
|
105
|
-
system('rails new sandbox')
|
106
|
-
end
|
107
|
-
|
108
|
-
rails_root = spree_sanbox_app
|
109
|
-
|
110
|
-
$:.unshift rails_root
|
111
|
-
|
112
|
-
begin
|
113
|
-
require 'config/environment.rb'
|
114
|
-
rescue => e
|
115
|
-
#somethign in deface seems to blow up suddenly on 1.1
|
116
|
-
# puts e.backtrace
|
117
|
-
puts "Warning - Potential issue initializing Spree sanbox #{e.inspect}"
|
118
|
-
end
|
119
|
-
|
120
|
-
Dir.chdir( store_path )
|
121
|
-
|
122
|
-
@dslog.info "Booted Spree using version #{SpreeHelper::version}"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def self.boot_pre_1
|
127
|
-
|
128
|
-
require 'rake'
|
129
|
-
require 'rubygems/package_task'
|
130
|
-
require 'thor/group'
|
131
|
-
|
132
|
-
require 'spree_core/preferences/model_hooks'
|
133
|
-
#
|
134
|
-
# Initialize preference system
|
135
|
-
ActiveRecord::Base.class_eval do
|
136
|
-
include Spree::Preferences
|
137
|
-
include Spree::Preferences::ModelHooks
|
138
|
-
end
|
139
|
-
|
140
|
-
gem 'paperclip'
|
141
|
-
gem 'nested_set'
|
142
|
-
|
143
|
-
require 'nested_set'
|
144
|
-
require 'paperclip'
|
145
|
-
require 'acts_as_list'
|
146
|
-
|
147
|
-
CollectiveIdea::Acts::NestedSet::Railtie.extend_active_record
|
148
|
-
ActiveRecord::Base.send(:include, Paperclip::Glue)
|
149
|
-
|
150
|
-
gem 'activemerchant'
|
151
|
-
require 'active_merchant'
|
152
|
-
require 'active_merchant/billing/gateway'
|
153
|
-
|
154
|
-
ActiveRecord::Base.send(:include, ActiveMerchant::Billing)
|
155
|
-
|
156
|
-
require 'scopes'
|
157
|
-
|
158
|
-
# Not sure how Rails manages this seems lots of circular dependencies so
|
159
|
-
# keep trying stuff till no more errors
|
160
|
-
|
161
|
-
Dir[lib_root + '/*.rb'].each do |r|
|
162
|
-
begin
|
163
|
-
require r if File.file?(r)
|
164
|
-
rescue => e
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
Dir[lib_root + '/**/*.rb'].each do |r|
|
169
|
-
begin
|
170
|
-
require r if File.file?(r) && ! r.include?('testing') && ! r.include?('generators')
|
171
|
-
rescue => e
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
load_models( true )
|
176
|
-
|
177
|
-
Dir[lib_root + '/*.rb'].each do |r|
|
178
|
-
begin
|
179
|
-
require r if File.file?(r)
|
180
|
-
rescue => e
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
Dir[lib_root + '/**/*.rb'].each do |r|
|
185
|
-
begin
|
186
|
-
require r if File.file?(r) && ! r.include?('testing') && ! r.include?('generators')
|
187
|
-
rescue => e
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# require 'lib/product_filters'
|
192
|
-
|
193
|
-
load_models( true )
|
194
|
-
|
195
|
-
end
|
196
|
-
|
197
|
-
def self.load_models( report_errors = nil )
|
198
|
-
puts 'Loading Spree models from', root
|
199
|
-
Dir[root + '/app/models/**/*.rb'].each {|r|
|
200
|
-
begin
|
201
|
-
require r if File.file?(r)
|
202
|
-
rescue => e
|
203
|
-
puts("WARNING failed to load #{r}", e.inspect) if(report_errors == true)
|
204
|
-
end
|
205
|
-
}
|
206
|
-
end
|
207
|
-
|
208
|
-
def self.migrate_up
|
209
|
-
ActiveRecord::Migrator.up( File.join(root, 'db/migrate') )
|
210
|
-
end
|
211
|
-
|
212
|
-
end
|
213
|
-
end
|
@@ -1,144 +0,0 @@
|
|
1
|
-
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
-
# Author :: Tom Statter
|
3
|
-
# Date :: Jan 2012
|
4
|
-
# License:: MIT
|
5
|
-
#
|
6
|
-
# Details:: Specific loader to support Excel files via http://rubygems.org/gems/spreadsheet
|
7
|
-
#
|
8
|
-
# Offers an alternative for non JRuby usage(see excel_loader)
|
9
|
-
#
|
10
|
-
# Maps column headings to operations on the model.
|
11
|
-
# Iterates over all the rows using mapped operations to assign row data to a database object,
|
12
|
-
# i.e pulls data from each column and sends to object.
|
13
|
-
#
|
14
|
-
require 'datashift/exceptions'
|
15
|
-
|
16
|
-
module DataShift
|
17
|
-
|
18
|
-
unless(Guards::jruby?)
|
19
|
-
|
20
|
-
require 'loaders/loader_base'
|
21
|
-
|
22
|
-
module SpreadsheetLoading
|
23
|
-
|
24
|
-
gem 'spreadsheet'
|
25
|
-
require 'spreadsheet'
|
26
|
-
|
27
|
-
|
28
|
-
# Spreadsheet.client_encoding = 'UTF-8'F
|
29
|
-
|
30
|
-
# Options:
|
31
|
-
# [:header_row] : Default is 0. Use alternative row as header definition.
|
32
|
-
# [:mandatory] : Array of mandatory column names
|
33
|
-
# [:strict] : Raise exception when no mapping found for a column heading (non mandatory)
|
34
|
-
# [:sheet_number]
|
35
|
-
|
36
|
-
def perform_spreadsheet_load( file_name, options = {} )
|
37
|
-
|
38
|
-
@mandatory = options[:mandatory] || []
|
39
|
-
|
40
|
-
@excel = Spreadsheet.open file_name
|
41
|
-
|
42
|
-
puts "\nLoading from Excel file: #{file_name}"
|
43
|
-
|
44
|
-
sheet_number = options[:sheet_number] || 0
|
45
|
-
|
46
|
-
@sheet = @excel.sheet( sheet_number )
|
47
|
-
|
48
|
-
header_row_index = options[:header_row] || 0
|
49
|
-
@header_row = @sheet.getRow(header_row_index)
|
50
|
-
|
51
|
-
raise MissingHeadersError, "No headers found - Check Sheet #{@sheet} is complete and Row #{header_row_index} contains headers" unless(@header_row)
|
52
|
-
|
53
|
-
@headers = []
|
54
|
-
|
55
|
-
(0..JExcelFile::MAX_COLUMNS).each do |i|
|
56
|
-
cell = @header_row.getCell(i)
|
57
|
-
break unless cell
|
58
|
-
header = "#{@excel.cell_value(cell).to_s}".strip
|
59
|
-
break if header.empty?
|
60
|
-
@headers << header
|
61
|
-
end
|
62
|
-
|
63
|
-
raise MissingHeadersError, "No headers found - Check Sheet #{@sheet} is complete and Row #{header_row_index} contains headers" if(@headers.empty?)
|
64
|
-
|
65
|
-
# Create a method_mapper which maps list of headers into suitable calls on the Active Record class
|
66
|
-
map_headers_to_operators( @headers, options)
|
67
|
-
|
68
|
-
load_object_class.transaction do
|
69
|
-
@loaded_objects = []
|
70
|
-
|
71
|
-
(1..@excel.num_rows).collect do |row|
|
72
|
-
|
73
|
-
# Excel num_rows seems to return all 'visible' rows, which appears to be greater than the actual data rows
|
74
|
-
# (TODO - write spec to process .xls with a huge number of rows)
|
75
|
-
#
|
76
|
-
# This is rubbish but currently manually detect when actual data ends, this isn't very smart but
|
77
|
-
# got no better idea than ending once we hit the first completely empty row
|
78
|
-
break if @excel.sheet.getRow(row).nil?
|
79
|
-
|
80
|
-
contains_data = false
|
81
|
-
|
82
|
-
# TODO - Smart sorting of column processing order ....
|
83
|
-
# Does not currently ensure mandatory columns (for valid?) processed first but model needs saving
|
84
|
-
# before associations can be processed so user should ensure mandatory columns are prior to associations
|
85
|
-
|
86
|
-
# as part of this we also attempt to save early, for example before assigning to
|
87
|
-
# has_and_belongs_to associations which require the load_object has an id for the join table
|
88
|
-
|
89
|
-
# Iterate over the columns method_mapper found in Excel,
|
90
|
-
# pulling data out of associated column
|
91
|
-
@method_mapper.method_details.each_with_index do |method_detail, col|
|
92
|
-
|
93
|
-
value = value_at(row, col)
|
94
|
-
|
95
|
-
contains_data = true unless(value.nil? || value.to_s.empty?)
|
96
|
-
|
97
|
-
#puts "DEBUG: Excel process METHOD :#{method_detail.inspect}", value.inspect
|
98
|
-
prepare_data(method_detail, value)
|
99
|
-
|
100
|
-
process()
|
101
|
-
end
|
102
|
-
|
103
|
-
break unless(contains_data == true)
|
104
|
-
|
105
|
-
# TODO - requirements to handle not valid ?
|
106
|
-
# all or nothing or carry on and dump out the exception list at end
|
107
|
-
#puts "DEBUG: FINAL SAVE #{load_object.inspect}"
|
108
|
-
save
|
109
|
-
#puts "DEBUG: SAVED #{load_object.inspect}"
|
110
|
-
|
111
|
-
# don't forget to reset the object or we'll update rather than create
|
112
|
-
new_load_object
|
113
|
-
|
114
|
-
end
|
115
|
-
end
|
116
|
-
puts "Spreadsheet loading stage complete - #{loaded_objects.size} rows added."
|
117
|
-
end
|
118
|
-
|
119
|
-
def value_at(row, column)
|
120
|
-
@excel.get_cell_value( @excel.sheet.getRow(row), column)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
|
125
|
-
class SpreadsheetLoader < LoaderBase
|
126
|
-
|
127
|
-
include SpreadsheetLoading
|
128
|
-
|
129
|
-
def initialize(klass, object = nil, options = {})
|
130
|
-
super( klass, object, options )
|
131
|
-
raise "Cannot load - failed to create a #{klass}" unless @load_object
|
132
|
-
end
|
133
|
-
|
134
|
-
def perform_load( file_name, options = {} )
|
135
|
-
perform_spreadsheet_load( file_name, options )
|
136
|
-
|
137
|
-
puts "Spreadsheet loading stage complete - #{loaded_objects.size} rows added."
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
141
|
-
|
142
|
-
end
|
143
|
-
|
144
|
-
end
|
@@ -1,90 +0,0 @@
|
|
1
|
-
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
-
# Author :: Tom Statter
|
3
|
-
# Date :: Jan 2011
|
4
|
-
# License:: MIT. Free, Open Source.
|
5
|
-
#
|
6
|
-
require 'loader_base'
|
7
|
-
require 'paperclip/image_loader'
|
8
|
-
|
9
|
-
module DataShift
|
10
|
-
|
11
|
-
|
12
|
-
module DataShift::SpreeImageLoading
|
13
|
-
|
14
|
-
include DataShift::Logging
|
15
|
-
include DataShift::ImageLoading
|
16
|
-
|
17
|
-
# Note the Spree Image model sets default storage path to
|
18
|
-
# => :path => ":rails_root/public/assets/products/:id/:style/:basename.:extension"
|
19
|
-
|
20
|
-
def create_image(klass, attachment_path, viewable_record = nil, options = {})
|
21
|
-
|
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)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
module SpreeHelper
|
29
|
-
|
30
|
-
# TODO - extract this out of SpreeHelper to create a general paperclip loader
|
31
|
-
class ImageLoader < LoaderBase
|
32
|
-
|
33
|
-
include DataShift::SpreeImageLoading
|
34
|
-
include DataShift::CsvLoading
|
35
|
-
include DataShift::ExcelLoading
|
36
|
-
|
37
|
-
def initialize(image = nil, options = {})
|
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 )
|
42
|
-
|
43
|
-
if(SpreeHelper::version.to_f > 1.0 )
|
44
|
-
@attachment_klazz = DataShift::SpreeHelper::get_spree_class('Variant' )
|
45
|
-
else
|
46
|
-
@attachment_klazz = DataShift::SpreeHelper::get_spree_class('Product' )
|
47
|
-
end
|
48
|
-
|
49
|
-
puts "Attachment Class is #{@attachment_klazz}" if(@verbose)
|
50
|
-
|
51
|
-
raise "Failed to create Image for loading" unless @load_object
|
52
|
-
end
|
53
|
-
|
54
|
-
def process()
|
55
|
-
|
56
|
-
if(current_value && @current_method_detail.operator?('attachment') )
|
57
|
-
|
58
|
-
# assign the image file data as an attachment
|
59
|
-
@load_object.attachment = get_file(current_value)
|
60
|
-
|
61
|
-
puts "Image attachment created : #{@load_object.inspect}"
|
62
|
-
|
63
|
-
elsif(current_value && @current_method_detail.operator )
|
64
|
-
|
65
|
-
# find the db record to assign our Image to
|
66
|
-
add_record( get_record_by(@attachment_klazz, @current_method_detail.operator, current_value) )
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
def add_record(record)
|
73
|
-
if(record)
|
74
|
-
if(SpreeHelper::version.to_f > 1 )
|
75
|
-
@load_object.viewable = record
|
76
|
-
else
|
77
|
-
@load_object.viewable = record.product # SKU stored on Variant but we want it's master Product
|
78
|
-
end
|
79
|
-
@load_object.save
|
80
|
-
puts "Image viewable set : #{record.inspect}"
|
81
|
-
|
82
|
-
else
|
83
|
-
puts "WARNING - Cannot set viewable - No matching record supplied"
|
84
|
-
logger.error"Failed to find a matching record"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|
90
|
-
end
|
@@ -1,354 +0,0 @@
|
|
1
|
-
# Copyright:: (c) Autotelik Media Ltd 2010
|
2
|
-
# Author :: Tom Statter
|
3
|
-
# Date :: Aug 2010
|
4
|
-
# License:: MIT ?
|
5
|
-
#
|
6
|
-
# Details:: Specific over-rides/additions to support Spree Products
|
7
|
-
#
|
8
|
-
require 'loader_base'
|
9
|
-
require 'csv_loader'
|
10
|
-
require 'excel_loader'
|
11
|
-
require 'image_loader'
|
12
|
-
|
13
|
-
module DataShift
|
14
|
-
|
15
|
-
module SpreeHelper
|
16
|
-
|
17
|
-
class ProductLoader < LoaderBase
|
18
|
-
|
19
|
-
include DataShift::CsvLoading
|
20
|
-
include DataShift::ExcelLoading
|
21
|
-
include DataShift::ImageLoading
|
22
|
-
|
23
|
-
# depending on version get_product_class should return us right class, namespaced or not
|
24
|
-
|
25
|
-
def initialize(product = nil)
|
26
|
-
super( SpreeHelper::get_product_class(), product, :instance_methods => true )
|
27
|
-
|
28
|
-
@@image_klass ||= SpreeHelper::get_spree_class('Image')
|
29
|
-
@@option_type_klass ||= SpreeHelper::get_spree_class('OptionType')
|
30
|
-
@@option_value_klass ||= SpreeHelper::get_spree_class('OptionValue')
|
31
|
-
@@property_klass ||= SpreeHelper::get_spree_class('Property')
|
32
|
-
@@product_property_klass ||= SpreeHelper::get_spree_class('ProductProperty')
|
33
|
-
@@taxonomy_klass ||= SpreeHelper::get_spree_class('Taxonomy')
|
34
|
-
@@taxon_klass ||= SpreeHelper::get_spree_class('Taxon')
|
35
|
-
@@variant_klass ||= SpreeHelper::get_spree_class('Variant')
|
36
|
-
|
37
|
-
raise "Failed to create Product for loading" unless @load_object
|
38
|
-
|
39
|
-
logger.debug "PRODUCT #{@load_object.inspect} MASTER: #{@load_object.master.inspect}"
|
40
|
-
end
|
41
|
-
|
42
|
-
def perform_load( file_name, options = {} )
|
43
|
-
# In >= 1.1.0 Image moved to master Variant from Product so no association called Images on Product anymore
|
44
|
-
options[:force_inclusion] = options[:force_inclusion] ? ['images'] : [*options[:force_inclusion]] << 'images'
|
45
|
-
|
46
|
-
super(file_name, options)
|
47
|
-
end
|
48
|
-
|
49
|
-
# Over ride base class process with some Spree::Product specifics
|
50
|
-
#
|
51
|
-
# What process a value string from a column, assigning value(s) to correct association on Product.
|
52
|
-
# Method map represents a column from a file and it's correlated Product association.
|
53
|
-
# Value string which may contain multiple values for a collection (has_many) association.
|
54
|
-
#
|
55
|
-
def process()
|
56
|
-
|
57
|
-
# Special cases for Products, generally where a simple one stage lookup won't suffice
|
58
|
-
# otherwise simply use default processing from base class
|
59
|
-
if(current_value && (@current_method_detail.operator?('variants') || @current_method_detail.operator?('option_types')) )
|
60
|
-
|
61
|
-
add_options
|
62
|
-
|
63
|
-
elsif(@current_method_detail.operator?('taxons') && current_value)
|
64
|
-
|
65
|
-
add_taxons
|
66
|
-
|
67
|
-
elsif(@current_method_detail.operator?('product_properties') && current_value)
|
68
|
-
|
69
|
-
add_properties
|
70
|
-
|
71
|
-
elsif(@current_method_detail.operator?('images') && current_value)
|
72
|
-
|
73
|
-
add_images
|
74
|
-
|
75
|
-
elsif(current_value && (@current_method_detail.operator?('count_on_hand') || @current_method_detail.operator?('on_hand')) )
|
76
|
-
|
77
|
-
|
78
|
-
# Unless we can save here, in danger of count_on_hand getting wiped out.
|
79
|
-
# If we set (on_hand or count_on_hand) on an unsaved object, during next subsequent save
|
80
|
-
# looks like some validation code or something calls Variant.on_hand= with 0
|
81
|
-
# If we save first, then our values seem to stick
|
82
|
-
|
83
|
-
# TODO smart column ordering to ensure always valid - if we always make it very last column might not get wiped ?
|
84
|
-
#
|
85
|
-
save_if_new
|
86
|
-
|
87
|
-
|
88
|
-
# Spree has some stock management stuff going on, so dont usually assign to column vut use
|
89
|
-
# on_hand and on_hand=
|
90
|
-
if(@load_object.variants.size > 0)
|
91
|
-
|
92
|
-
if(current_value.to_s.include?(LoaderBase::multi_assoc_delim))
|
93
|
-
|
94
|
-
#puts "DEBUG: COUNT_ON_HAND PER VARIANT",current_value.is_a?(String),
|
95
|
-
|
96
|
-
# Check if we processed Option Types and assign count per option
|
97
|
-
values = current_value.to_s.split(LoaderBase::multi_assoc_delim)
|
98
|
-
|
99
|
-
if(@load_object.variants.size == values.size)
|
100
|
-
@load_object.variants.each_with_index {|v, i| v.on_hand = values[i].to_i }
|
101
|
-
@load_object.save
|
102
|
-
else
|
103
|
-
puts "WARNING: Count on hand entries did not match number of Variants - None Set"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# Can only set count on hand on Product if no Variants exist, else model throws
|
108
|
-
|
109
|
-
elsif(@load_object.variants.size == 0)
|
110
|
-
if(current_value.to_s.include?(LoaderBase::multi_assoc_delim))
|
111
|
-
puts "WARNING: Multiple count_on_hand values specified but no Variants/OptionTypes created"
|
112
|
-
load_object.on_hand = current_value.to_s.split(LoaderBase::multi_assoc_delim).first.to_i
|
113
|
-
else
|
114
|
-
load_object.on_hand = current_value.to_i
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
else
|
119
|
-
super
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
private
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
# Special case for OptionTypes as it's two stage process
|
128
|
-
# First add the possible option_types to Product, then we are able
|
129
|
-
# to define Variants on those options values.
|
130
|
-
# To defiene a Variant :
|
131
|
-
# 1) define at least one OptionType on Product, for example Size
|
132
|
-
# 2) Provide a value for at least one of these OptionType
|
133
|
-
# 3) A composite Variant can be created by supplyiung a vlaue for more than one OptionType
|
134
|
-
# fro example Colour : Red and Size Medium
|
135
|
-
# Supported Syntax :
|
136
|
-
# '|' seperates Variants
|
137
|
-
# ',' list of option values
|
138
|
-
# Examples :
|
139
|
-
# mime_type:jpeg;print_type:black_white|mime_type:jpeg|mime_type:png, PDF;print_type:colour
|
140
|
-
#
|
141
|
-
def add_options
|
142
|
-
|
143
|
-
# TODO smart column ordering to ensure always valid by time we get to associations
|
144
|
-
save_if_new
|
145
|
-
|
146
|
-
# example : mime_type:jpeg;print_type:black_white|mime_type:jpeg|mime_type:png, PDF;print_type:colour
|
147
|
-
|
148
|
-
variants = get_each_assoc#current_value.split( LoaderBase::multi_assoc_delim )
|
149
|
-
|
150
|
-
# 1) mime_type:jpeg;print_type:black_white 2) mime_type:jpeg 3) mime_type:png, PDF;print_type:colour
|
151
|
-
|
152
|
-
variants.each do |per_variant|
|
153
|
-
|
154
|
-
option_types = per_variant.split(';') # [mime_type:jpeg, print_type:black_white]
|
155
|
-
|
156
|
-
optiontype_vlist_map = {}
|
157
|
-
|
158
|
-
option_types.each do |ostr|
|
159
|
-
|
160
|
-
oname, value_str = ostr.split(LoaderBase::name_value_delim)
|
161
|
-
|
162
|
-
option_type = @@option_type_klass.find_by_name(oname)
|
163
|
-
|
164
|
-
unless option_type
|
165
|
-
option_type = @@option_type_klass.create( :name => oname, :presentation => oname.humanize)
|
166
|
-
# TODO - dynamic creation should be an option
|
167
|
-
|
168
|
-
unless option_type
|
169
|
-
puts "WARNING: OptionType #{oname} NOT found and could not create - Not set Product"
|
170
|
-
next
|
171
|
-
end
|
172
|
-
puts "Created missing OptionType #{option_type.inspect}"
|
173
|
-
end
|
174
|
-
|
175
|
-
# OptionTypes must be specified first on Product to enable Variants to be created
|
176
|
-
# TODO - is include? very inefficient ??
|
177
|
-
@load_object.option_types << option_type unless @load_object.option_types.include?(option_type)
|
178
|
-
|
179
|
-
# Can be simply list of OptionTypes, some or all without values
|
180
|
-
next unless(value_str)
|
181
|
-
|
182
|
-
optiontype_vlist_map[option_type] = []
|
183
|
-
|
184
|
-
# Now get the value(s) for the option e.g red,blue,green for OptType 'colour'
|
185
|
-
optiontype_vlist_map[option_type] = value_str.split(',')
|
186
|
-
end
|
187
|
-
|
188
|
-
next if(optiontype_vlist_map.empty?) # only option types specified - no values
|
189
|
-
|
190
|
-
# Now create set of Variants, some of which maybe composites
|
191
|
-
# Find the longest set of OVs to use as base for combining with the rest
|
192
|
-
sorted_map = optiontype_vlist_map.sort_by { |k,v| v.size }.reverse
|
193
|
-
|
194
|
-
# ovalues = 'pdf','jpeg','png'
|
195
|
-
option_type, ovalues = sorted_map.shift
|
196
|
-
# TODO .. benchmarking to find most efficient way to create these but ensure Product.variants list
|
197
|
-
# populated .. currently need to call reload to ensure this (seems reqd for Spree 1/Rails 3, wasn't required b4
|
198
|
-
ovalues.each do |ovname|
|
199
|
-
|
200
|
-
ov_list = []
|
201
|
-
|
202
|
-
ov = @@option_value_klass.find_or_create_by_name_and_option_type_id(ovname.strip, option_type.id)
|
203
|
-
|
204
|
-
ov_list << ov if ov
|
205
|
-
|
206
|
-
sorted_map.each do |ot, ovlist| ovlist.each do |for_composite|
|
207
|
-
ov = @@option_value_klass.find_or_create_by_name_and_option_type_id(for_composite.strip, ot.id)
|
208
|
-
|
209
|
-
ov_list << ov if(ov)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
unless(ov_list.empty?)
|
214
|
-
i = @load_object.variants.size + 1
|
215
|
-
|
216
|
-
# This one line seems to works for 1.1.0 - 3.2 but not 1.0.0 - 3.1 ??
|
217
|
-
if(SpreeHelper::version.to_f >= 1.1)
|
218
|
-
variant = @load_object.variants.create( :sku => "#{@load_object.sku}_#{i}", :price => @load_object.price)
|
219
|
-
else
|
220
|
-
variant = @@variant_klass.create( :product => @load_object, :sku => "#{@load_object.sku}_#{i}", :price => @load_object.price, :available_on => @load_object.available_on)
|
221
|
-
end
|
222
|
-
|
223
|
-
variant.option_values << ov_list if(variant)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
#puts "DEBUG Load Object now has Variants : #{@load_object.variants.inspect}"
|
228
|
-
@load_object.reload unless @load_object.new_record?
|
229
|
-
#puts "DEBUG Load Object now has Variants : #{@load_object.variants.inspect}"
|
230
|
-
end
|
231
|
-
|
232
|
-
end # each Variant
|
233
|
-
|
234
|
-
# Special case for Images
|
235
|
-
# A list of paths to Images with a optional 'alt' value - supplied in form :
|
236
|
-
# path:alt|path2:alt2|path_3:alt3 etc
|
237
|
-
#
|
238
|
-
def add_images
|
239
|
-
# TODO smart column ordering to ensure always valid by time we get to associations
|
240
|
-
save_if_new
|
241
|
-
|
242
|
-
images = get_each_assoc#current_value.split(LoaderBase::multi_assoc_delim)
|
243
|
-
|
244
|
-
images.each do |image|
|
245
|
-
puts "Add Image #{image}"
|
246
|
-
img_path, alt_text = image.split(LoaderBase::name_value_delim)
|
247
|
-
|
248
|
-
# moved from Prod to Variant in spree 1.x.x
|
249
|
-
attachment = (SpreeHelper::version.to_f > 1) ? @load_object.master : @load_object
|
250
|
-
|
251
|
-
image = create_image(@@image_klass, img_path, attachment, :alt => alt_text)
|
252
|
-
logger.debug("Product assigned an Image : #{image.inspect}")
|
253
|
-
end
|
254
|
-
|
255
|
-
end
|
256
|
-
|
257
|
-
|
258
|
-
# Special case for ProductProperties since it can have additional value applied.
|
259
|
-
# A list of Properties with a optional Value - supplied in form :
|
260
|
-
# property_name:value|property_name|property_name:value
|
261
|
-
# Example :
|
262
|
-
# test_pp_002|test_pp_003:Example free value|yet_another_property
|
263
|
-
|
264
|
-
def add_properties
|
265
|
-
# TODO smart column ordering to ensure always valid by time we get to associations
|
266
|
-
save_if_new
|
267
|
-
|
268
|
-
property_list = get_each_assoc#current_value.split(LoaderBase::multi_assoc_delim)
|
269
|
-
|
270
|
-
property_list.each do |pstr|
|
271
|
-
|
272
|
-
# Special case, we know we lookup on name so operator is effectively the name to lookup
|
273
|
-
find_by_name, find_by_value = get_find_operator_and_rest( pstr )
|
274
|
-
|
275
|
-
raise "Cannot find Property via #{find_by_name} (with value #{find_by_value})" unless(find_by_name)
|
276
|
-
|
277
|
-
property = @@property_klass.find_by_name(find_by_name)
|
278
|
-
|
279
|
-
unless property
|
280
|
-
property = @@property_klass.create( :name => find_by_name, :presentation => find_by_name.humanize)
|
281
|
-
logger.info "Created New Property #{property.inspect}"
|
282
|
-
end
|
283
|
-
|
284
|
-
if(property)
|
285
|
-
if(SpreeHelper::version.to_f >= 1.1)
|
286
|
-
# Property now protected from mass assignment
|
287
|
-
x = @@product_property_klass.new( :value => find_by_value )
|
288
|
-
x.property = property
|
289
|
-
x.save
|
290
|
-
@load_object.product_properties << x
|
291
|
-
logger.info "Created New ProductProperty #{x.inspect}"
|
292
|
-
else
|
293
|
-
@load_object.product_properties << @@product_property_klass.create( :property => property, :value => find_by_values)
|
294
|
-
end
|
295
|
-
else
|
296
|
-
puts "WARNING: Property #{find_by_name} NOT found - Not set Product"
|
297
|
-
end
|
298
|
-
|
299
|
-
end
|
300
|
-
|
301
|
-
end
|
302
|
-
|
303
|
-
# Nested tree structure support ..
|
304
|
-
# TAXON FORMAT
|
305
|
-
# name|name>child>child|name
|
306
|
-
|
307
|
-
def add_taxons
|
308
|
-
# TODO smart column ordering to ensure always valid by time we get to associations
|
309
|
-
save_if_new
|
310
|
-
|
311
|
-
chain_list = get_each_assoc#current_value().split(LoaderBase::multi_assoc_delim)
|
312
|
-
|
313
|
-
chain_list.each do |chain|
|
314
|
-
|
315
|
-
name_list = chain.split(/\s*>\s*/)
|
316
|
-
|
317
|
-
# manage per chain
|
318
|
-
parent_taxonomy, parent, taxon = nil, nil, nil
|
319
|
-
|
320
|
-
# Each chain can contain either a single Taxon, or the tree like structure parent>child>child
|
321
|
-
taxons = name_list.collect do |name|
|
322
|
-
|
323
|
-
#puts "DEBUG: NAME #{name.inspect}"
|
324
|
-
begin
|
325
|
-
taxon = @@taxon_klass.find_by_name( name )
|
326
|
-
|
327
|
-
if(taxon)
|
328
|
-
parent_taxonomy ||= taxon.taxonomy
|
329
|
-
else
|
330
|
-
parent_taxonomy ||= @@taxonomy_klass.find_or_create_by_name(name)
|
331
|
-
|
332
|
-
taxon = @@taxon_klass.find_or_create_by_name_and_parent_id_and_taxonomy_id(name, parent && parent.id, parent_taxonomy.id)
|
333
|
-
end
|
334
|
-
rescue => e
|
335
|
-
puts e.inspect
|
336
|
-
puts "ERROR : Cannot assign Taxon ['#{taxon}'] to Product ['#{load_object.name}']"
|
337
|
-
next
|
338
|
-
end
|
339
|
-
|
340
|
-
parent = taxon
|
341
|
-
taxon
|
342
|
-
end
|
343
|
-
|
344
|
-
unique_list = taxons.compact.uniq - (@load_object.taxons || [])
|
345
|
-
|
346
|
-
logger.debug("Product assigned to Taxons : #{unique_list.collect(&:name).inspect}")
|
347
|
-
@load_object.taxons << unique_list unless(unique_list.empty?)
|
348
|
-
end
|
349
|
-
|
350
|
-
end
|
351
|
-
|
352
|
-
end
|
353
|
-
end
|
354
|
-
end
|