datashift 0.9.0 → 0.10.0

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