datashift 0.1.0 → 0.2.1
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/.document +5 -5
- data/LICENSE.txt +26 -26
- data/README.markdown +305 -303
- data/README.rdoc +19 -19
- data/Rakefile +93 -93
- data/VERSION +1 -1
- data/datashift-0.1.0.gem +0 -0
- data/datashift.gemspec +152 -136
- data/lib/applications/jruby/jexcel_file.rb +408 -408
- data/lib/applications/jruby/word.rb +79 -79
- data/lib/datashift.rb +152 -152
- data/lib/datashift/exceptions.rb +11 -11
- data/lib/datashift/file_definitions.rb +353 -353
- data/lib/datashift/mapping_file_definitions.rb +87 -87
- data/lib/datashift/method_detail.rb +275 -275
- data/lib/datashift/method_dictionary.rb +209 -209
- data/lib/datashift/method_mapper.rb +90 -90
- data/lib/generators/csv_generator.rb +36 -36
- data/lib/generators/excel_generator.rb +122 -122
- data/lib/generators/generator_base.rb +13 -13
- data/lib/helpers/core_ext/to_b.rb +24 -24
- data/lib/helpers/spree_helper.rb +153 -155
- data/lib/java/poi-3.7/LICENSE +507 -507
- data/lib/java/poi-3.7/NOTICE +21 -21
- data/lib/java/poi-3.7/RELEASE_NOTES.txt +115 -115
- data/lib/loaders/csv_loader.rb +98 -98
- data/lib/loaders/excel_loader.rb +155 -155
- data/lib/loaders/loader_base.rb +420 -420
- data/lib/loaders/spreadsheet_loader.rb +136 -136
- data/lib/loaders/spree/image_loader.rb +63 -64
- data/lib/loaders/spree/product_loader.rb +248 -250
- 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/csv_loader_spec.rb +30 -30
- data/spec/datashift_spec.rb +26 -26
- data/spec/db/migrate/20110803201325_create_test_bed.rb +85 -85
- data/spec/excel_exporter_spec.rb +78 -78
- data/spec/excel_generator_spec.rb +78 -78
- data/spec/excel_loader_spec.rb +223 -223
- data/spec/file_definitions.rb +141 -141
- data/spec/fixtures/ProjectsDefaults.yml +29 -29
- data/spec/fixtures/config/database.yml +27 -24
- data/spec/fixtures/datashift_Spree_db.sqlite +0 -0
- data/spec/fixtures/interact_models_db.sqlite +0 -0
- data/spec/fixtures/negative/SpreeProdMiss1Mandatory.csv +4 -4
- data/spec/fixtures/negative/SpreeProdMissManyMandatory.csv +4 -4
- data/spec/fixtures/spree/SpreeProducts.csv +4 -4
- data/spec/fixtures/spree/SpreeProductsMultiColumn.csv +4 -4
- data/spec/fixtures/spree/SpreeProductsSimple.csv +4 -4
- data/spec/fixtures/spree/SpreeProductsWithImages.csv +4 -0
- data/spec/fixtures/spree/SpreeZoneExample.csv +5 -5
- data/spec/fixtures/test_model_defs.rb +57 -57
- data/spec/loader_spec.rb +120 -120
- data/spec/method_dictionary_spec.rb +242 -242
- data/spec/method_mapper_spec.rb +41 -41
- data/spec/spec_helper.rb +116 -116
- data/spec/spree_generator_spec.rb +64 -64
- data/spec/spree_loader_spec.rb +324 -327
- data/spec/spree_method_mapping_spec.rb +214 -214
- data/tasks/config/seed_fu_product_template.erb +15 -15
- data/tasks/config/tidy_config.txt +12 -12
- data/tasks/db_tasks.rake +65 -65
- data/tasks/excel_generator.rake +78 -78
- data/tasks/file_tasks.rake +36 -36
- data/tasks/import/csv.rake +49 -49
- data/tasks/import/excel.rake +71 -71
- data/tasks/spree/image_load.rake +108 -108
- data/tasks/spree/product_loader.rake +43 -43
- data/tasks/word_to_seedfu.rake +166 -166
- data/test/helper.rb +18 -18
- data/test/test_interact.rb +7 -7
- metadata +22 -3
- data/spec/fixtures/interact_spree_db.sqlite +0 -0
@@ -1,137 +1,137 @@
|
|
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
|
-
require 'loaders/loader_base'
|
19
|
-
|
20
|
-
module SpreadsheetLoading
|
21
|
-
|
22
|
-
# Options:
|
23
|
-
# [:header_row] : Default is 0. Use alternative row as header definition.
|
24
|
-
# [:mandatory] : Array of mandatory column names
|
25
|
-
# [:strict] : Raise exception when no mapping found for a column heading (non mandatory)
|
26
|
-
# [:sheet_number]
|
27
|
-
|
28
|
-
def perform_spreadsheet_load( file_name, options = {} )
|
29
|
-
|
30
|
-
@mandatory = options[:mandatory] || []
|
31
|
-
|
32
|
-
@excel = JExcelFile.new
|
33
|
-
|
34
|
-
@excel.open(file_name)
|
35
|
-
|
36
|
-
#if(options[:verbose])
|
37
|
-
puts "\n\n\nLoading from Excel file: #{file_name}"
|
38
|
-
|
39
|
-
sheet_number = options[:sheet_number] || 0
|
40
|
-
|
41
|
-
@sheet = @excel.sheet( sheet_number )
|
42
|
-
|
43
|
-
header_row_index = options[:header_row] || 0
|
44
|
-
@header_row = @sheet.getRow(header_row_index)
|
45
|
-
|
46
|
-
raise MissingHeadersError, "No headers found - Check Sheet #{@sheet} is complete and Row #{header_row_index} contains headers" unless(@header_row)
|
47
|
-
|
48
|
-
@headers = []
|
49
|
-
|
50
|
-
(0..JExcelFile::MAX_COLUMNS).each do |i|
|
51
|
-
cell = @header_row.getCell(i)
|
52
|
-
break unless cell
|
53
|
-
header = "#{@excel.cell_value(cell).to_s}".strip
|
54
|
-
break if header.empty?
|
55
|
-
@headers << header
|
56
|
-
end
|
57
|
-
|
58
|
-
raise MissingHeadersError, "No headers found - Check Sheet #{@sheet} is complete and Row #{header_row_index} contains headers" if(@headers.empty?)
|
59
|
-
|
60
|
-
# Create a method_mapper which maps list of headers into suitable calls on the Active Record class
|
61
|
-
map_headers_to_operators( @headers, options[:strict] , @mandatory )
|
62
|
-
|
63
|
-
load_object_class.transaction do
|
64
|
-
@loaded_objects = []
|
65
|
-
|
66
|
-
(1..@excel.num_rows).collect do |row|
|
67
|
-
|
68
|
-
# Excel num_rows seems to return all 'visible' rows, which appears to be greater than the actual data rows
|
69
|
-
# (TODO - write spec to process .xls with a huge number of rows)
|
70
|
-
#
|
71
|
-
# This is rubbish but currently manually detect when actual data ends, this isn't very smart but
|
72
|
-
# got no better idea than ending once we hit the first completely empty row
|
73
|
-
break if @excel.sheet.getRow(row).nil?
|
74
|
-
|
75
|
-
contains_data = false
|
76
|
-
|
77
|
-
# TODO - Smart sorting of column processing order ....
|
78
|
-
# Does not currently ensure mandatory columns (for valid?) processed first but model needs saving
|
79
|
-
# before associations can be processed so user should ensure mandatory columns are prior to associations
|
80
|
-
|
81
|
-
# as part of this we also attempt to save early, for example before assigning to
|
82
|
-
# has_and_belongs_to associations which require the load_object has an id for the join table
|
83
|
-
|
84
|
-
# Iterate over the columns method_mapper found in Excel,
|
85
|
-
# pulling data out of associated column
|
86
|
-
@method_mapper.method_details.each_with_index do |method_detail, col|
|
87
|
-
|
88
|
-
value = value_at(row, col)
|
89
|
-
|
90
|
-
contains_data = true unless(value.nil? || value.to_s.empty?)
|
91
|
-
|
92
|
-
#puts "DEBUG: Excel process METHOD :#{method_detail.inspect}", value.inspect
|
93
|
-
prepare_data(method_detail, value)
|
94
|
-
|
95
|
-
process()
|
96
|
-
end
|
97
|
-
|
98
|
-
break unless(contains_data == true)
|
99
|
-
|
100
|
-
# TODO - requirements to handle not valid ?
|
101
|
-
# all or nothing or carry on and dump out the exception list at end
|
102
|
-
#puts "DEBUG: FINAL SAVE #{load_object.inspect}"
|
103
|
-
save
|
104
|
-
#puts "DEBUG: SAVED #{load_object.inspect}"
|
105
|
-
|
106
|
-
# don't forget to reset the object or we'll update rather than create
|
107
|
-
new_load_object
|
108
|
-
|
109
|
-
end
|
110
|
-
end
|
111
|
-
puts "Spreadsheet loading stage complete - #{loaded_objects.size} rows added."
|
112
|
-
end
|
113
|
-
|
114
|
-
def value_at(row, column)
|
115
|
-
@excel.get_cell_value( @excel.sheet.getRow(row), column)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
|
120
|
-
class SpreadsheetLoader < LoaderBase
|
121
|
-
|
122
|
-
include SpreadsheetLoading
|
123
|
-
|
124
|
-
def initialize(klass, object = nil, options = {})
|
125
|
-
super( klass, object, options )
|
126
|
-
raise "Cannot load - failed to create a #{klass}" unless @load_object
|
127
|
-
end
|
128
|
-
|
129
|
-
def perform_load( file_name, options = {} )
|
130
|
-
perform_spreadsheet_load( file_name, options )
|
131
|
-
|
132
|
-
puts "Spreadsheet loading stage complete - #{loaded_objects.size} rows added."
|
133
|
-
end
|
134
|
-
|
135
|
-
end
|
136
|
-
|
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
|
+
require 'loaders/loader_base'
|
19
|
+
|
20
|
+
module SpreadsheetLoading
|
21
|
+
|
22
|
+
# Options:
|
23
|
+
# [:header_row] : Default is 0. Use alternative row as header definition.
|
24
|
+
# [:mandatory] : Array of mandatory column names
|
25
|
+
# [:strict] : Raise exception when no mapping found for a column heading (non mandatory)
|
26
|
+
# [:sheet_number]
|
27
|
+
|
28
|
+
def perform_spreadsheet_load( file_name, options = {} )
|
29
|
+
|
30
|
+
@mandatory = options[:mandatory] || []
|
31
|
+
|
32
|
+
@excel = JExcelFile.new
|
33
|
+
|
34
|
+
@excel.open(file_name)
|
35
|
+
|
36
|
+
#if(options[:verbose])
|
37
|
+
puts "\n\n\nLoading from Excel file: #{file_name}"
|
38
|
+
|
39
|
+
sheet_number = options[:sheet_number] || 0
|
40
|
+
|
41
|
+
@sheet = @excel.sheet( sheet_number )
|
42
|
+
|
43
|
+
header_row_index = options[:header_row] || 0
|
44
|
+
@header_row = @sheet.getRow(header_row_index)
|
45
|
+
|
46
|
+
raise MissingHeadersError, "No headers found - Check Sheet #{@sheet} is complete and Row #{header_row_index} contains headers" unless(@header_row)
|
47
|
+
|
48
|
+
@headers = []
|
49
|
+
|
50
|
+
(0..JExcelFile::MAX_COLUMNS).each do |i|
|
51
|
+
cell = @header_row.getCell(i)
|
52
|
+
break unless cell
|
53
|
+
header = "#{@excel.cell_value(cell).to_s}".strip
|
54
|
+
break if header.empty?
|
55
|
+
@headers << header
|
56
|
+
end
|
57
|
+
|
58
|
+
raise MissingHeadersError, "No headers found - Check Sheet #{@sheet} is complete and Row #{header_row_index} contains headers" if(@headers.empty?)
|
59
|
+
|
60
|
+
# Create a method_mapper which maps list of headers into suitable calls on the Active Record class
|
61
|
+
map_headers_to_operators( @headers, options[:strict] , @mandatory )
|
62
|
+
|
63
|
+
load_object_class.transaction do
|
64
|
+
@loaded_objects = []
|
65
|
+
|
66
|
+
(1..@excel.num_rows).collect do |row|
|
67
|
+
|
68
|
+
# Excel num_rows seems to return all 'visible' rows, which appears to be greater than the actual data rows
|
69
|
+
# (TODO - write spec to process .xls with a huge number of rows)
|
70
|
+
#
|
71
|
+
# This is rubbish but currently manually detect when actual data ends, this isn't very smart but
|
72
|
+
# got no better idea than ending once we hit the first completely empty row
|
73
|
+
break if @excel.sheet.getRow(row).nil?
|
74
|
+
|
75
|
+
contains_data = false
|
76
|
+
|
77
|
+
# TODO - Smart sorting of column processing order ....
|
78
|
+
# Does not currently ensure mandatory columns (for valid?) processed first but model needs saving
|
79
|
+
# before associations can be processed so user should ensure mandatory columns are prior to associations
|
80
|
+
|
81
|
+
# as part of this we also attempt to save early, for example before assigning to
|
82
|
+
# has_and_belongs_to associations which require the load_object has an id for the join table
|
83
|
+
|
84
|
+
# Iterate over the columns method_mapper found in Excel,
|
85
|
+
# pulling data out of associated column
|
86
|
+
@method_mapper.method_details.each_with_index do |method_detail, col|
|
87
|
+
|
88
|
+
value = value_at(row, col)
|
89
|
+
|
90
|
+
contains_data = true unless(value.nil? || value.to_s.empty?)
|
91
|
+
|
92
|
+
#puts "DEBUG: Excel process METHOD :#{method_detail.inspect}", value.inspect
|
93
|
+
prepare_data(method_detail, value)
|
94
|
+
|
95
|
+
process()
|
96
|
+
end
|
97
|
+
|
98
|
+
break unless(contains_data == true)
|
99
|
+
|
100
|
+
# TODO - requirements to handle not valid ?
|
101
|
+
# all or nothing or carry on and dump out the exception list at end
|
102
|
+
#puts "DEBUG: FINAL SAVE #{load_object.inspect}"
|
103
|
+
save
|
104
|
+
#puts "DEBUG: SAVED #{load_object.inspect}"
|
105
|
+
|
106
|
+
# don't forget to reset the object or we'll update rather than create
|
107
|
+
new_load_object
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
puts "Spreadsheet loading stage complete - #{loaded_objects.size} rows added."
|
112
|
+
end
|
113
|
+
|
114
|
+
def value_at(row, column)
|
115
|
+
@excel.get_cell_value( @excel.sheet.getRow(row), column)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
class SpreadsheetLoader < LoaderBase
|
121
|
+
|
122
|
+
include SpreadsheetLoading
|
123
|
+
|
124
|
+
def initialize(klass, object = nil, options = {})
|
125
|
+
super( klass, object, options )
|
126
|
+
raise "Cannot load - failed to create a #{klass}" unless @load_object
|
127
|
+
end
|
128
|
+
|
129
|
+
def perform_load( file_name, options = {} )
|
130
|
+
perform_spreadsheet_load( file_name, options )
|
131
|
+
|
132
|
+
puts "Spreadsheet loading stage complete - #{loaded_objects.size} rows added."
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
137
|
end
|
@@ -1,65 +1,64 @@
|
|
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
|
-
|
8
|
-
module DataShift
|
9
|
-
|
10
|
-
module ImageLoading
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
puts
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
image.
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
+
|
8
|
+
module DataShift
|
9
|
+
|
10
|
+
module ImageLoading
|
11
|
+
|
12
|
+
# Note the Spree Image model sets default storage path to
|
13
|
+
# => :path => ":rails_root/public/assets/products/:id/:style/:basename.:extension"
|
14
|
+
|
15
|
+
def create_image(image_path, viewable_record = nil, options = {})
|
16
|
+
|
17
|
+
image = Image.new
|
18
|
+
|
19
|
+
unless File.exists?(image_path)
|
20
|
+
puts "ERROR : Invalid Path"
|
21
|
+
return image
|
22
|
+
end
|
23
|
+
|
24
|
+
alt = if(options[:alt])
|
25
|
+
options[:alt]
|
26
|
+
else
|
27
|
+
(viewable_record and viewable_record.respond_to? :name) ? viewable_record.name : ""
|
28
|
+
end
|
29
|
+
|
30
|
+
image.alt = alt
|
31
|
+
|
32
|
+
begin
|
33
|
+
image.attachment = File.new(image_path, "r")
|
34
|
+
rescue => e
|
35
|
+
puts e.inspect
|
36
|
+
puts "ERROR : Failed to read image #{image_path}"
|
37
|
+
return image
|
38
|
+
end
|
39
|
+
|
40
|
+
image.attachment.reprocess!
|
41
|
+
image.viewable = viewable_record if viewable_record
|
42
|
+
|
43
|
+
puts image.save ? "Success: Created Image: #{image.inspect}" : "ERROR : Problem saving to DB Image: #{image.inspect}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ImageLoader < LoaderBase
|
48
|
+
|
49
|
+
include DataShift::ImageLoading
|
50
|
+
|
51
|
+
def initialize(image = nil)
|
52
|
+
super( Image, image )
|
53
|
+
raise "Failed to create Image for loading" unless @load_object
|
54
|
+
end
|
55
|
+
|
56
|
+
# Note the Spree Image model sets default storage path to
|
57
|
+
# => :path => ":rails_root/public/assets/products/:id/:style/:basename.:extension"
|
58
|
+
|
59
|
+
def process( image_path, record = nil)
|
60
|
+
@load_object = create_image(path, record)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
65
64
|
end
|
@@ -1,251 +1,249 @@
|
|
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 Spree
|
16
|
-
|
17
|
-
class ProductLoader < LoaderBase
|
18
|
-
|
19
|
-
include DataShift::CsvLoading
|
20
|
-
include DataShift::ExcelLoading
|
21
|
-
include DataShift::ImageLoading
|
22
|
-
|
23
|
-
def initialize(product = nil)
|
24
|
-
super( Product, product, :instance_methods => true )
|
25
|
-
raise "Failed to create Product for loading" unless @load_object
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
ovname
|
145
|
-
ov
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
#
|
161
|
-
#
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
images
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
img_path,
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
#
|
181
|
-
#
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
property_list
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
property
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
taxon
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
taxons.
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
end
|
250
|
-
end
|
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 Spree
|
16
|
+
|
17
|
+
class ProductLoader < LoaderBase
|
18
|
+
|
19
|
+
include DataShift::CsvLoading
|
20
|
+
include DataShift::ExcelLoading
|
21
|
+
include DataShift::ImageLoading
|
22
|
+
|
23
|
+
def initialize(product = nil)
|
24
|
+
super( Product, product, :instance_methods => true )
|
25
|
+
raise "Failed to create Product for loading" unless @load_object
|
26
|
+
end
|
27
|
+
|
28
|
+
# Based on filename call appropriate loading function
|
29
|
+
# Currently supports :
|
30
|
+
# Excel/Open Office files saved as .xls
|
31
|
+
# CSV files
|
32
|
+
def perform_load( file_name, options = {} )
|
33
|
+
|
34
|
+
ext = File.extname(file_name)
|
35
|
+
|
36
|
+
if(ext == '.xls')
|
37
|
+
raise DataShift::BadRuby, "Please install and use JRuby for loading .xls files" unless(Guards::jruby?)
|
38
|
+
perform_excel_load(file_name, options)
|
39
|
+
elsif(ext == '.csv')
|
40
|
+
perform_csv_load(file_name, options)
|
41
|
+
else
|
42
|
+
raise DataShift::UnsupportedFileType, "#{ext} files not supported - Try CSV or OpenOffice/Excel .xls"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Over ride base class process with some Spree::Product specifics
|
47
|
+
#
|
48
|
+
# What process a value string from a column, assigning value(s) to correct association on Product.
|
49
|
+
# Method map represents a column from a file and it's correlated Product association.
|
50
|
+
# Value string which may contain multiple values for a collection (has_many) association.
|
51
|
+
#
|
52
|
+
def process()
|
53
|
+
|
54
|
+
# Special cases for Products, generally where a simple one stage lookup won't suffice
|
55
|
+
# otherwise simply use default processing from base class
|
56
|
+
if((@current_method_detail.operator?('variants') || @current_method_detail.operator?('option_types')) && current_value)
|
57
|
+
|
58
|
+
add_options
|
59
|
+
|
60
|
+
elsif(@current_method_detail.operator?('taxons') && current_value)
|
61
|
+
|
62
|
+
add_taxons
|
63
|
+
|
64
|
+
elsif(@current_method_detail.operator?('product_properties') && current_value)
|
65
|
+
|
66
|
+
add_properties
|
67
|
+
|
68
|
+
elsif(@current_method_detail.operator?('images') && current_value)
|
69
|
+
|
70
|
+
add_images
|
71
|
+
|
72
|
+
elsif(@current_method_detail.operator?('count_on_hand') || @current_method_detail.operator?('on_hand') )
|
73
|
+
|
74
|
+
# Unless we can save here, in danger of count_on_hand getting wiped out.
|
75
|
+
# If we set (on_hand or count_on_hand) on an unsaved object, during next subsequent save
|
76
|
+
# looks like some validation code or something calls Variant.on_hand= with 0
|
77
|
+
# If we save first, then our values seem to stick
|
78
|
+
|
79
|
+
# TODO smart column ordering to ensure always valid - if we always make it very last column might not get wiped ?
|
80
|
+
#
|
81
|
+
save_if_new
|
82
|
+
|
83
|
+
# Spree has some stock management stuff going on, so dont usually assign to column vut use
|
84
|
+
# on_hand and on_hand=
|
85
|
+
if(@load_object.variants.size > 0 && current_value.include?(LoaderBase::multi_assoc_delim))
|
86
|
+
|
87
|
+
#puts "DEBUG: COUNT_ON_HAND PER VARIANT",current_value.is_a?(String),
|
88
|
+
#&& current_value.is_a?(String) && current_value.include?(LoaderBase::multi_assoc_delim))
|
89
|
+
# Check if we processed Option Types and assign count per option
|
90
|
+
values = current_value.to_s.split(LoaderBase::multi_assoc_delim)
|
91
|
+
|
92
|
+
if(@load_object.variants.size == values.size)
|
93
|
+
@load_object.variants.each_with_index {|v, i| v.on_hand = values[i]; v.save; }
|
94
|
+
else
|
95
|
+
puts "WARNING: Count on hand entries did not match number of Variants - None Set"
|
96
|
+
end
|
97
|
+
else
|
98
|
+
#puts "DEBUG: COUNT_ON_HAND #{current_value.to_i}"
|
99
|
+
load_object.on_hand = current_value.to_i
|
100
|
+
end
|
101
|
+
|
102
|
+
else
|
103
|
+
super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# Special case for OptionTypes as it's two stage process
|
110
|
+
# First add the possible option_types to Product, then we are able
|
111
|
+
# to define Variants on those options values.
|
112
|
+
#
|
113
|
+
def add_options
|
114
|
+
# TODO smart column ordering to ensure always valid by time we get to associations
|
115
|
+
save_if_new
|
116
|
+
|
117
|
+
option_types = current_value.split( LoaderBase::multi_assoc_delim )
|
118
|
+
|
119
|
+
option_types.each do |ostr|
|
120
|
+
oname, value_str = ostr.split(LoaderBase::name_value_delim)
|
121
|
+
|
122
|
+
option_type = OptionType.find_by_name(oname)
|
123
|
+
|
124
|
+
unless option_type
|
125
|
+
option_type = OptionType.create( :name => oname, :presentation => oname.humanize)
|
126
|
+
# TODO - dynamic creation should be an option
|
127
|
+
|
128
|
+
unless option_type
|
129
|
+
puts "WARNING: OptionType #{oname} NOT found - Not set Product"
|
130
|
+
next
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
@load_object.option_types << option_type unless @load_object.option_types.include?(option_type)
|
135
|
+
|
136
|
+
# Can be simply list of OptionTypes, some or all without values
|
137
|
+
next unless(value_str)
|
138
|
+
|
139
|
+
# Now get the value(s) for the option e.g red,blue,green for OptType 'colour'
|
140
|
+
ovalues = value_str.split(',')
|
141
|
+
|
142
|
+
ovalues.each_with_index do |ovname, i|
|
143
|
+
ovname.strip!
|
144
|
+
ov = OptionValue.find_or_create_by_name(ovname)
|
145
|
+
if ov
|
146
|
+
object = Variant.create( :product => @load_object, :sku => "#{@load_object.sku}_#{i}", :price => @load_object.price, :available_on => @load_object.available_on)
|
147
|
+
#puts "DEBUG: Create New Variant: #{object.inspect}"
|
148
|
+
object.option_values << ov
|
149
|
+
#@load_object.variants << object
|
150
|
+
else
|
151
|
+
puts "WARNING: Option #{ovname} NOT FOUND - No Variant created"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
# Special case for Images
|
159
|
+
# A list of paths to Images with a optional 'alt' value - supplied in form :
|
160
|
+
# path:alt|path2:alt2|path_3:alt3 etc
|
161
|
+
#
|
162
|
+
def add_images
|
163
|
+
# TODO smart column ordering to ensure always valid by time we get to associations
|
164
|
+
save_if_new
|
165
|
+
|
166
|
+
images = current_value.split(LoaderBase::multi_assoc_delim)
|
167
|
+
|
168
|
+
images.each do |image|
|
169
|
+
|
170
|
+
img_path, alt_text = image.split(LoaderBase::name_value_delim)
|
171
|
+
|
172
|
+
image = create_image(img_path, @load_object, :alt => alt_text)
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
# Special case for ProductProperties since it can have additional value applied.
|
179
|
+
# A list of Properties with a optional Value - supplied in form :
|
180
|
+
# property.name:value|property.name|property.name:value
|
181
|
+
#
|
182
|
+
def add_properties
|
183
|
+
# TODO smart column ordering to ensure always valid by time we get to associations
|
184
|
+
save_if_new
|
185
|
+
|
186
|
+
property_list = current_value.split(LoaderBase::multi_assoc_delim)
|
187
|
+
|
188
|
+
property_list.each do |pstr|
|
189
|
+
pname, pvalue = pstr.split(LoaderBase::name_value_delim)
|
190
|
+
property = Property.find_by_name(pname)
|
191
|
+
|
192
|
+
unless property
|
193
|
+
property = Property.create( :name => pname, :presentation => pname.humanize)
|
194
|
+
end
|
195
|
+
|
196
|
+
if(property)
|
197
|
+
@load_object.product_properties << ProductProperty.create( :property => property, :value => pvalue)
|
198
|
+
else
|
199
|
+
puts "WARNING: Property #{pname} NOT found - Not set Product"
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
def add_taxons
|
208
|
+
# TODO smart column ordering to ensure always valid by time we get to associations
|
209
|
+
save_if_new
|
210
|
+
|
211
|
+
name_list = current_value.split(LoaderBase::multi_assoc_delim)
|
212
|
+
|
213
|
+
taxons = name_list.collect do |t|
|
214
|
+
|
215
|
+
taxon = Taxon.find_by_name(t)
|
216
|
+
|
217
|
+
unless taxon
|
218
|
+
parent = Taxonomy.find_by_name(t)
|
219
|
+
|
220
|
+
begin
|
221
|
+
if(parent)
|
222
|
+
# not sure this can happen but just incase we get a weird situation where we have
|
223
|
+
# a taxonomy without a root named the same - create the child taxon we require
|
224
|
+
taxon = Taxon.create(:name => t, :taxonomy_id => parent.id)
|
225
|
+
else
|
226
|
+
parent = Taxonomy.create!( :name => t )
|
227
|
+
|
228
|
+
taxon = parent.root
|
229
|
+
end
|
230
|
+
|
231
|
+
rescue => e
|
232
|
+
e.backtrace
|
233
|
+
e.inspect
|
234
|
+
puts "ERROR : Cannot assign Taxon ['#{t}'] to Product ['#{load_object.name}']"
|
235
|
+
next
|
236
|
+
end
|
237
|
+
end
|
238
|
+
taxon
|
239
|
+
end
|
240
|
+
|
241
|
+
taxons.compact!
|
242
|
+
|
243
|
+
@load_object.taxons << taxons unless(taxons.empty?)
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
251
249
|
end
|