datashift 0.9.0 → 0.10.0

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