datashift 0.0.1 → 0.0.2

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