ar_loader 0.0.6 → 0.0.8

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 (53) hide show
  1. data/LICENSE +9 -9
  2. data/README.markdown +268 -221
  3. data/Rakefile +76 -76
  4. data/lib/VERSION +1 -1
  5. data/lib/ar_loader.rb +87 -66
  6. data/lib/ar_loader/exceptions.rb +2 -0
  7. data/lib/{engine → ar_loader}/file_definitions.rb +353 -353
  8. data/lib/{engine → ar_loader}/mapping_file_definitions.rb +87 -87
  9. data/lib/ar_loader/method_detail.rb +257 -0
  10. data/lib/ar_loader/method_mapper.rb +213 -0
  11. data/lib/helpers/jruby/jexcel_file.rb +187 -0
  12. data/lib/{engine → helpers/jruby}/word.rb +79 -70
  13. data/lib/helpers/spree_helper.rb +85 -0
  14. data/lib/loaders/csv_loader.rb +87 -0
  15. data/lib/loaders/excel_loader.rb +132 -0
  16. data/lib/loaders/loader_base.rb +205 -73
  17. data/lib/loaders/spree/image_loader.rb +45 -41
  18. data/lib/loaders/spree/product_loader.rb +140 -91
  19. data/lib/to_b.rb +24 -24
  20. data/spec/csv_loader_spec.rb +27 -0
  21. data/spec/database.yml +19 -6
  22. data/spec/db/migrate/20110803201325_create_test_bed.rb +78 -0
  23. data/spec/excel_loader_spec.rb +113 -98
  24. data/spec/fixtures/BadAssociationName.xls +0 -0
  25. data/spec/fixtures/DemoNegativeTesting.xls +0 -0
  26. data/spec/fixtures/DemoTestModelAssoc.xls +0 -0
  27. data/spec/fixtures/ProjectsMultiCategories.xls +0 -0
  28. data/spec/fixtures/SimpleProjects.xls +0 -0
  29. data/spec/fixtures/SpreeProducts.xls +0 -0
  30. data/spec/fixtures/SpreeZoneExample.csv +5 -0
  31. data/spec/fixtures/SpreeZoneExample.xls +0 -0
  32. data/spec/loader_spec.rb +116 -0
  33. data/spec/logs/test.log +5000 -0
  34. data/spec/method_mapper_spec.rb +222 -0
  35. data/spec/models.rb +55 -0
  36. data/spec/spec_helper.rb +85 -18
  37. data/spec/spree_loader_spec.rb +223 -157
  38. data/tasks/config/seed_fu_product_template.erb +15 -15
  39. data/tasks/config/tidy_config.txt +12 -12
  40. data/tasks/db_tasks.rake +64 -64
  41. data/tasks/excel_loader.rake +63 -113
  42. data/tasks/file_tasks.rake +36 -37
  43. data/tasks/loader.rake +45 -0
  44. data/tasks/spree/image_load.rake +108 -107
  45. data/tasks/spree/product_loader.rake +49 -107
  46. data/tasks/word_to_seedfu.rake +166 -166
  47. metadata +66 -61
  48. data/lib/engine/jruby/jexcel_file.rb +0 -182
  49. data/lib/engine/jruby/method_mapper_excel.rb +0 -44
  50. data/lib/engine/method_detail.rb +0 -140
  51. data/lib/engine/method_mapper.rb +0 -157
  52. data/lib/engine/method_mapper_csv.rb +0 -28
  53. data/spec/db/migrate/20110803201325_create_testbed.rb +0 -25
@@ -1,92 +1,141 @@
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
-
10
- class ProductLoader < LoaderBase
11
-
12
- def initialize(klass = Product, product = nil)
13
- super( klass, product )
14
- raise "Failed to create Product for loading" unless @load_object
15
- end
16
-
17
- # What process a value string from a column, assigning value(s) to correct association on Product.
18
- # Method map represents a column from a file and it's correlated Product association.
19
- # Value string which may contain multiple values for a collection association.
20
- # Product to assign that value to.
21
- def process( method_map, value)
22
- #puts "INFO: PRODUCT LOADER processing #{@load_object}"
23
- @value = value
24
-
25
- #puts "DEBUG : process #{method_map.inspect} : #{value.inspect}"
26
- # Special case for OptionTypes as it's two stage process
27
- # First add the possible option_types to Product, then we are able
28
- # to define Variants on those options.
29
-
30
- if(method_map.name == 'option_types' && @value)
31
-
32
- option_types = @value.split(@@multi_assoc_delim)
33
- option_types.each do |ostr|
34
- oname, value_str = ostr.split(@@name_value_delim)
35
- option_type = OptionType.find_or_create_by_name(oname)
36
- unless option_type
37
- puts "WARNING: OptionType #{oname} NOT found - Not set Product"
38
- next
39
- end
40
-
41
- @load_object.option_types << option_type unless @load_object.option_types.include?(option_type)
42
-
43
- # Now get the value(s) for the option e.g red,blue,green for OptType 'colour'
44
- ovalues = value_str.split(',')
45
- ovalues.each_with_index do |ovname, i|
46
- ovname.strip!
47
- ov = OptionValue.find_by_name(ovname)
48
- if ov
49
- object = Variant.new( :sku => "#{@load_object.sku}_#{i}", :price => @load_object.price, :available_on => @load_object.available_on)
50
- #puts "DEBUG: Create New Variant: #{object.inspect}"
51
- object.option_values << ov
52
- @load_object.variants << object
53
- else
54
- puts "WARNING: Option #{ovname} NOT FOUND - No Variant created"
55
- end
56
- end
57
- end
58
-
59
- # Special case for ProductProperties since it can have additional value applied.
60
- # A list of Properties with a optional Value - supplied in form :
61
- # Property:value|Property2:value|Property3:value
62
- #
63
- elsif(method_map.name == 'product_properties' && @value)
64
-
65
- property_list = @value.split(@@multi_assoc_delim)
66
-
67
- property_list.each do |pstr|
68
- pname, pvalue = pstr.split(@@name_value_delim)
69
- property = Property.find_by_name(pname)
70
- unless property
71
- puts "WARNING: Property #{pname} NOT found - Not set Product"
72
- next
73
- end
74
- @load_object.product_properties << ProductProperty.create( :property => property, :value => pvalue)
75
- end
76
-
77
- elsif(method_map.name == 'count_on_hand' && @load_object.variants.size > 0 &&
78
- @value.is_a?(String) && @value.include?(@@multi_assoc_delim))
79
- # Check if we processed Option Types and assign count per option
80
- values = @value.split(@@multi_assoc_delim)
81
- if(@load_object.variants.size == values.size)
82
- @load_object.variants.each_with_index {|v, i| v.count_on_hand == values[i] }
83
- else
84
- puts "WARNING: Count on hand entries does not match number of Variants"
85
- end
86
-
87
- else
88
- super(method_map, value)
89
- end
90
-
91
- 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
+
11
+
12
+ module ARLoader
13
+
14
+ class ProductLoader < ExcelLoader
15
+
16
+ def initialize(product = nil)
17
+ super( Product, product )
18
+ raise "Failed to create Product for loading" unless @load_object
19
+ end
20
+
21
+ # What process a value string from a column, assigning value(s) to correct association on Product.
22
+ # Method map represents a column from a file and it's correlated Product association.
23
+ # Value string which may contain multiple values for a collection association.
24
+ # Product to assign that value to.
25
+ def process( method_detail, value)
26
+
27
+ @current_value = value
28
+ @current_method_detail = method_detail
29
+
30
+ #puts "DEBUG: process #{method_detail.inspect} : #{current_value.inspect}"
31
+ # Special case for OptionTypes as it's two stage process
32
+ # First add the possible option_types to Product, then we are able
33
+ # to define Variants on those options values.
34
+
35
+ if(method_detail.operator?('option_types') && current_value)
36
+
37
+ #puts "DEBUG: Special Case: VARIANTS: #{method_detail.inspect} : #{current_value.inspect}"
38
+
39
+ # TODO smart column ordering to ensure always valid by time we get to associations
40
+ save if(load_object.valid? && load_object.new_record?)
41
+
42
+ option_types = current_value.split(@@multi_assoc_delim)
43
+
44
+ option_types.each do |ostr|
45
+ oname, value_str = ostr.split(@@name_value_delim)
46
+ option_type = OptionType.find_or_create_by_name(oname)
47
+ unless option_type
48
+ puts "WARNING: OptionType #{oname} NOT found - Not set Product"
49
+ next
50
+ end
51
+
52
+ @load_object.option_types << option_type unless @load_object.option_types.include?(option_type)
53
+
54
+ # Now get the value(s) for the option e.g red,blue,green for OptType 'colour'
55
+ ovalues = value_str.split(',')
56
+
57
+ ovalues.each_with_index do |ovname, i|
58
+ ovname.strip!
59
+ ov = OptionValue.find_or_create_by_name(ovname)
60
+ if ov
61
+ object = Variant.new( :sku => "#{@load_object.sku}_#{i}", :price => @load_object.price, :available_on => @load_object.available_on)
62
+ #puts "DEBUG: Create New Variant: #{object.inspect}"
63
+ object.option_values << ov
64
+ @load_object.variants << object
65
+ else
66
+ puts "WARNING: Option #{ovname} NOT FOUND - No Variant created"
67
+ end
68
+ end
69
+ end
70
+
71
+ elsif(method_detail.operator?('taxons') && current_value)
72
+
73
+ # TODO smart column ordering to ensure always valid by time we get to associations
74
+ save if(load_object.valid? && load_object.new_record?)
75
+
76
+ name_list = current_value.split(@@multi_assoc_delim)
77
+
78
+ taxons = name_list.collect do |t|
79
+
80
+ taxon = Taxon.find_by_name(t)
81
+
82
+ unless taxon
83
+ parent = Taxonomy.find_by_name(t)
84
+
85
+ #puts "DEBUG: Taxonomy #{parent} for #{t}"
86
+ begin
87
+ parent = Taxonomy.create!( :name => t ) unless parent
88
+
89
+ taxon = Taxon.create(:name => t, :taxonomy_id => parent.id)
90
+ rescue => e
91
+ e.backtrace
92
+ e.inspect
93
+ puts "ERROR : Cannot assign Taxon ['#{t}'] to Product ['#{load_object.name}']"
94
+ next
95
+ end
96
+ end
97
+ taxon
98
+ end
99
+
100
+ taxons.compact!
101
+
102
+ @load_object.taxons << taxons unless(taxons.empty?)
103
+
104
+ # Special case for ProductProperties since it can have additional value applied.
105
+ # A list of Properties with a optional Value - supplied in form :
106
+ # Property:value|Property2:value|Property3:value
107
+ #
108
+ elsif(method_detail.operator?('product_properties') && current_value)
109
+
110
+ # TODO smart column ordering to ensure always valid by time we get to associations
111
+ save if(load_object.valid? && load_object.new_record?)
112
+
113
+ property_list = current_value.split(@@multi_assoc_delim)
114
+
115
+ property_list.each do |pstr|
116
+ pname, pvalue = pstr.split(@@name_value_delim)
117
+ property = Property.find_by_name(pname)
118
+ unless property
119
+ puts "WARNING: Property #{pname} NOT found - Not set Product"
120
+ next
121
+ end
122
+ @load_object.product_properties << ProductProperty.create( :property => property, :value => pvalue)
123
+ end
124
+
125
+ elsif(method_detail.operator?('count_on_hand') && @load_object.variants.size > 0 && current_value.is_a?(String) && current_value.include?(@@multi_assoc_delim))
126
+ # Check if we processed Option Types and assign count per option
127
+ values = current_value.split(@@multi_assoc_delim)
128
+ if(@load_object.variants.size == values.size)
129
+ @load_object.variants.each_with_index {|v, i| v.count_on_hand == values[i] }
130
+ else
131
+ puts "WARNING: Count on hand entries does not match number of Variants"
132
+ end
133
+
134
+ else
135
+ super(method_detail, value)
136
+ end
137
+
138
+ end
139
+ end
140
+
92
141
  end
data/lib/to_b.rb CHANGED
@@ -1,24 +1,24 @@
1
- class Object
2
- def to_b
3
- case self
4
- when true, false: self
5
- when nil: false
6
- else
7
- to_i != 0
8
- end
9
- end
10
- end
11
-
12
- class String
13
- TRUE_REGEXP = /^(yes|true|on|t|1|\-1)$/i.freeze
14
- FALSE_REGEXP = /^(no|false|off|f|0)$/i.freeze
15
-
16
- def to_b
17
- case self
18
- when TRUE_REGEXP: true
19
- when FALSE_REGEXP: false
20
- else
21
- to_i != 0
22
- end
23
- end
24
- end
1
+ class Object
2
+ def to_b
3
+ case self
4
+ when true, false: self
5
+ when nil: false
6
+ else
7
+ to_i != 0
8
+ end
9
+ end
10
+ end
11
+
12
+ class String
13
+ TRUE_REGEXP = /^(yes|true|on|t|1|\-1)$/i.freeze
14
+ FALSE_REGEXP = /^(no|false|off|f|0)$/i.freeze
15
+
16
+ def to_b
17
+ case self
18
+ when TRUE_REGEXP: true
19
+ when FALSE_REGEXP: false
20
+ else
21
+ to_i != 0
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ # Copyright:: (c) Autotelik Media Ltd 2011
2
+ # Author :: Tom Statter
3
+ # Date :: Aug 2011
4
+ # License:: MIT
5
+ #
6
+ # Details:: Specs for Excel aspect of Active Record Loader
7
+ #
8
+ require File.dirname(__FILE__) + '/spec_helper'
9
+
10
+ require 'erb'
11
+ require 'excel_loader'
12
+
13
+ describe 'CSV Loader' do
14
+
15
+ before(:all) do
16
+ db_connect( 'test_file' ) # , test_memory, test_mysql
17
+ migrate_up
18
+ @klazz = Project
19
+ end
20
+
21
+ before(:each) do
22
+ MethodMapper.clear
23
+ MethodMapper.find_operators( @klazz )
24
+ MethodMapper.find_operators( @assoc_klazz )
25
+ end
26
+
27
+ end
data/spec/database.yml CHANGED
@@ -1,6 +1,19 @@
1
- <% adapter = 'jdbcsqlite3' %>
2
-
3
- test:
4
- adapter: <%= adapter %>
5
- encoding: utf8
6
- database: :memory
1
+ <% adapter = 'sqlite3' %>
2
+ <% adapter = 'jdbcsqlite3' if(Guards::jruby? ) %>
3
+
4
+ test_mysql:
5
+ adapter: jdbcmysql
6
+ database: ar_loader_test
7
+ username: test
8
+ password: test
9
+ host: localhost
10
+
11
+ test_memory:
12
+ adapter: <%= adapter %>
13
+ database: :memory
14
+
15
+ test_file:
16
+ adapter: <%= adapter %>
17
+ database: 'ar_loader_test'
18
+ encoding: utf8
19
+ host: localhost
@@ -0,0 +1,78 @@
1
+ class CreateTestBed < ActiveRecord::Migration
2
+
3
+ def self.up
4
+
5
+ # has many :milestones
6
+ create_table :projects do |t|
7
+ t.string :title
8
+ t.string :value_as_string
9
+ t.text :value_as_text
10
+ t.boolean :value_as_boolean, :default => false
11
+ t.datetime :value_as_datetime, :default => nil
12
+ t.integer :value_as_integer, :default => 0
13
+ t.decimal :value_as_double, :precision => 8, :scale => 2, :default => 0.0
14
+ t.timestamps
15
+ end
16
+
17
+ # belongs_to :project, project => has_many
18
+ create_table :milestones do |t|
19
+ t.string :name
20
+ t.datetime :datetime, :default => nil
21
+ t.decimal :cost, :precision => 8, :scale => 2, :default => 0.0
22
+ t.references :project
23
+ t.timestamps
24
+ end
25
+
26
+ # belongs_to :project, project => has_one
27
+ create_table :owners do |t|
28
+ t.string :name
29
+ t.references :project
30
+ t.timestamps
31
+ end
32
+
33
+ # has_belongs_to_many :project
34
+ create_table :categories do |t|
35
+ t.string :reference
36
+ t.timestamps
37
+ end
38
+
39
+ # testing has_belongs_to_many (hence no id)
40
+ create_table :categories_projects, :id => false do |t|
41
+ t.references :category
42
+ t.references :project
43
+ end
44
+
45
+ create_table :versions do |t|
46
+ t.string :name
47
+ t.timestamps
48
+ end
49
+
50
+ # testing project has_many release + versions :through
51
+ create_table :loader_releases do |t|
52
+ t.string :name
53
+ t.references :project
54
+ t.references :version
55
+ t.timestamps
56
+ end
57
+
58
+
59
+ create_table :long_and_complex_table_linked_to_versions do |t|
60
+ t.references :version
61
+ end
62
+
63
+ create_table :empties do |t|
64
+ end
65
+
66
+ end
67
+
68
+ def self.down
69
+ drop_table :projects
70
+ drop_table :categories
71
+ drop_table :loader_releases
72
+ drop_table :versions
73
+ drop_table :categories_projectss
74
+ drop_table :milestones
75
+ drop_table :long_and_complex_table_linked_to_versions
76
+ drop_table :empties
77
+ end
78
+ end
@@ -1,99 +1,114 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
2
-
3
- require 'erb'
4
- require File.dirname(__FILE__) + '/../lib/ar_loader'
5
-
6
- # We are not setup as a Rails project so need to mimic an active record database setup so
7
- # we have some AR models top test against. Create an in memory database from scratch.
8
- #
9
- def db_connect( env = 'test')
10
-
11
- configuration = {:rails_env => env }
12
-
13
- # Some active record stuff seems to rely on the RAILS_ENV being set ?
14
-
15
- ENV['RAILS_ENV'] = configuration[:rails_env]
16
-
17
- configuration[:database_configuration] = YAML::load(ERB.new(IO.read( File.dirname(__FILE__) + '/database.yml')).result)
18
- db = configuration[:database_configuration][ configuration[:rails_env] ]
19
-
20
- puts "Setting DB Config - #{db.inspect}"
21
- ActiveRecord::Base.configurations = db
22
-
23
- puts "Connecting to DB"
24
- ActiveRecord::Base.establish_connection( db )
25
-
26
- puts "Connected to DB Config - #{configuration[:rails_env]}"
27
- end
28
-
29
- def create_in_memory_database
30
- ActiveRecord::Migrator.up('db/migrate')
31
- end
32
-
33
-
34
- class TestModel < ActiveRecord::Base
35
- has_many :TestAssociationModel
36
- end
37
-
38
- class TestAssociationModel < ActiveRecord::Base
39
- belongs_to :test_model
40
- end
41
-
42
- describe 'ExcelLoader' do
43
-
44
- before do
45
- db_connect
46
- create_in_memory_database
47
- @klazz = TestModel
48
- MethodMapper.clear
49
- end
50
-
51
- it "should populate operators for a given AR model" do
52
- MethodMapper.find_operators( @klazz )
53
-
54
- MethodMapper.has_many.should_not be_empty
55
- MethodMapper.assignments.should_not be_empty
56
-
57
- hmf = MethodMapper.has_many_for(@klazz)
58
- arf = MethodMapper.assignments_for(@klazz)
59
-
60
- (hmf & arf).should_not be_empty # Associations provide << or =
61
-
62
- MethodMapper.column_types.should be_is_a(Hash)
63
- MethodMapper.column_types.should_not be_empty
64
-
65
- end
66
-
67
- it "should populate operators respecting unique option" do
68
- MethodMapper.find_operators( @klazz, :unique => true )
69
-
70
- hmf = MethodMapper.has_many_for(@klazz)
71
- arf = MethodMapper.assignments_for(@klazz)
72
-
73
- (hmf & arf).should be_empty
74
- end
75
-
76
- it "should populate assignment method and col type for different forms of a column name" do
77
-
78
- MethodMapper.find_operators( @klazz )
79
- end
80
-
81
- it "should populate both methods for different forms of an association name" do
82
-
83
- MethodMapper.find_operators( @klazz )
84
- end
85
-
86
- it "should not populate anything when non existent column name" do
87
- MethodMapper.find_operators( @klazz )
88
- end
89
-
90
- it "should enable correct assignment and sending of a value to AR model" do
91
- MethodMapper.find_operators( @klazz )
92
- end
93
-
94
- it "should enable correct assignment and sending of association to AR model" do
95
- MethodMapper.find_operators( @klazz )
96
- end
97
-
98
-
1
+ # Copyright:: (c) Autotelik Media Ltd 2011
2
+ # Author :: Tom Statter
3
+ # Date :: Aug 2011
4
+ # License:: MIT
5
+ #
6
+ # Details:: Specs for Excel aspect of Active Record Loader
7
+ #
8
+ require File.dirname(__FILE__) + '/spec_helper'
9
+
10
+ require 'erb'
11
+ require 'excel_loader'
12
+
13
+ include ARLoader
14
+
15
+ describe 'Excel Loader' do
16
+
17
+ before(:all) do
18
+ db_connect( 'test_file' ) # , test_memory, test_mysql
19
+
20
+ db_clear() # todo read up about proper transactional fixtures
21
+
22
+
23
+ @klazz = Project
24
+ @assoc_klazz = Category
25
+ end
26
+
27
+ before(:each) do
28
+
29
+ Project.delete_all
30
+
31
+ %w{category_001 category_002 category_003}.each do |cat|
32
+ @assoc_klazz.find_or_create_by_reference(cat)
33
+ end
34
+
35
+
36
+ MethodMapper.clear
37
+ MethodMapper.find_operators( @klazz )
38
+ MethodMapper.find_operators( @assoc_klazz )
39
+ end
40
+
41
+ it "should be able to create a new excel loader and load object" do
42
+ loader = ExcelLoader.new( @klazz)
43
+
44
+ loader.load_object.should_not be_nil
45
+ loader.load_object.should be_is_a(@klazz)
46
+ loader.load_object.new_record?.should be_true
47
+ end
48
+
49
+ it "should process a simple .xls spreedsheet" do
50
+
51
+ loader = ExcelLoader.new(@klazz)
52
+
53
+ count = @klazz.count
54
+ loader.load( $fixture_path + '/SimpleProjects.xls')
55
+
56
+ loader.loaded_count.should == (@klazz.count - count)
57
+
58
+ end
59
+
60
+ it "should process associations in .xls spreedsheet" do
61
+
62
+ @klazz.find_by_title('001').should be_nil
63
+ count = @klazz.count
64
+
65
+ loader = ExcelLoader.new(@klazz)
66
+
67
+ loader.load( $fixture_path + '/DemoTestModelAssoc.xls')
68
+
69
+ loader.loaded_count.should be > 3
70
+ loader.loaded_count.should == (@klazz.count - count)
71
+
72
+ {'001' => 2, '002' => 1, '003' => 3, '004' => 0 }.each do|title, expected|
73
+ project = @klazz.find_by_title(title)
74
+
75
+ project.should_not be_nil
76
+ puts "#{project.inspect} [#{project.categories.size}]"
77
+
78
+ project.should have(expected).categories
79
+ end
80
+ end
81
+
82
+ it "should process multiple associations in excel spreedsheet" do
83
+
84
+ loader = ExcelLoader.new(Project)
85
+
86
+ count = Project.count
87
+ loader.load( $fixture_path + '/ProjectsMultiCategories.xls')
88
+
89
+ loader.loaded_count.should == (Project.count - count)
90
+
91
+ {'004' => 3, '005' => 1, '006' => 0, '007' => 1 }.each do|title, expected|
92
+ project = @klazz.find_by_title(title)
93
+
94
+ project.should_not be_nil
95
+ puts "#{project.inspect} [#{project.categories.size}]"
96
+
97
+ project.should have(expected).categories
98
+ end
99
+
100
+ end
101
+
102
+ it "should not process badly defined excel spreedsheet" do
103
+ loader = ExcelLoader.new(Project)
104
+ expect {loader.load( $fixture_path + '/BadAssociationName.xls')}.to raise_error(MappingDefinitionError)
105
+ end
106
+
107
+ #@pending
108
+ it "should raise an error when mandatory columns missing" do
109
+ #loader = ExcelLoader.new(Project)
110
+ #required = [:x, :. :z]
111
+ #expect {loader.load( $fixture_path + '/BadAssociationName.xls', { :mandatopry:=> required}) }.to raise_error(????????)
112
+ end
113
+
99
114
  end