datashift 0.1.0 → 0.2.1

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