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.
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