datashift 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. data/.document +5 -0
  2. data/Gemfile +25 -0
  3. data/Gemfile.lock +211 -0
  4. data/LICENSE.txt +27 -0
  5. data/README.markdown +286 -0
  6. data/README.rdoc +19 -0
  7. data/Rakefile +96 -0
  8. data/VERSION +5 -0
  9. data/bin/autospec +16 -0
  10. data/bin/convert_to_should_syntax +16 -0
  11. data/bin/erubis +16 -0
  12. data/bin/htmldiff +16 -0
  13. data/bin/jeweler +16 -0
  14. data/bin/ldiff +16 -0
  15. data/bin/nokogiri +16 -0
  16. data/bin/rackup +16 -0
  17. data/bin/rails +16 -0
  18. data/bin/rake +16 -0
  19. data/bin/rake2thor +16 -0
  20. data/bin/ri +16 -0
  21. data/bin/rspec +16 -0
  22. data/bin/spree +16 -0
  23. data/bin/thor +16 -0
  24. data/bin/tilt +16 -0
  25. data/bin/tt +16 -0
  26. data/datashift.gemspec +178 -0
  27. data/lib/applications/jruby/jexcel_file.rb +397 -0
  28. data/lib/applications/jruby/word.rb +79 -0
  29. data/lib/datashift.rb +114 -0
  30. data/lib/datashift/exceptions.rb +12 -0
  31. data/lib/datashift/file_definitions.rb +353 -0
  32. data/lib/datashift/mapping_file_definitions.rb +88 -0
  33. data/lib/datashift/method_detail.rb +237 -0
  34. data/lib/datashift/method_mapper.rb +257 -0
  35. data/lib/generators/csv_generator.rb +36 -0
  36. data/lib/generators/excel_generator.rb +122 -0
  37. data/lib/generators/generator_base.rb +14 -0
  38. data/lib/helpers/core_ext/to_b.rb +24 -0
  39. data/lib/helpers/spree_helper.rb +131 -0
  40. data/lib/java/poi-3.7/._poi-3.7-20101029.jar5645100390082102460.tmp +0 -0
  41. data/lib/java/poi-3.7/LICENSE +507 -0
  42. data/lib/java/poi-3.7/NOTICE +21 -0
  43. data/lib/java/poi-3.7/RELEASE_NOTES.txt +115 -0
  44. data/lib/java/poi-3.7/lib/commons-logging-1.1.jar +0 -0
  45. data/lib/java/poi-3.7/lib/junit-3.8.1.jar +0 -0
  46. data/lib/java/poi-3.7/lib/log4j-1.2.13.jar +0 -0
  47. data/lib/java/poi-3.7/ooxml-lib/dom4j-1.6.1.jar +0 -0
  48. data/lib/java/poi-3.7/ooxml-lib/geronimo-stax-api_1.0_spec-1.0.jar +0 -0
  49. data/lib/java/poi-3.7/ooxml-lib/xmlbeans-2.3.0.jar +0 -0
  50. data/lib/java/poi-3.7/poi-3.7-20101029.jar +0 -0
  51. data/lib/java/poi-3.7/poi-examples-3.7-20101029.jar +0 -0
  52. data/lib/java/poi-3.7/poi-ooxml-3.7-20101029.jar +0 -0
  53. data/lib/java/poi-3.7/poi-ooxml-schemas-3.7-20101029.jar +0 -0
  54. data/lib/java/poi-3.7/poi-scratchpad-3.7-20101029.jar +0 -0
  55. data/lib/loaders/csv_loader.rb +99 -0
  56. data/lib/loaders/excel_loader.rb +150 -0
  57. data/lib/loaders/loader_base.rb +332 -0
  58. data/lib/loaders/spreadsheet_loader.rb +137 -0
  59. data/lib/loaders/spree/image_loader.rb +46 -0
  60. data/lib/loaders/spree/product_loader.rb +225 -0
  61. data/spec/csv_loader_spec.rb +31 -0
  62. data/spec/datashift_spec.rb +27 -0
  63. data/spec/db/migrate/20110803201325_create_test_bed.rb +85 -0
  64. data/spec/excel_generator_spec.rb +79 -0
  65. data/spec/excel_loader_spec.rb +177 -0
  66. data/spec/file_definitions.rb +141 -0
  67. data/spec/fixtures/BadAssociationName.xls +0 -0
  68. data/spec/fixtures/DemoNegativeTesting.xls +0 -0
  69. data/spec/fixtures/ProjectsMultiCategories.xls +0 -0
  70. data/spec/fixtures/ProjectsSingleCategories.xls +0 -0
  71. data/spec/fixtures/SimpleProjects.xls +0 -0
  72. data/spec/fixtures/config/database.yml +25 -0
  73. data/spec/fixtures/interact_models_db.sqlite +0 -0
  74. data/spec/fixtures/interact_spree_db.sqlite +0 -0
  75. data/spec/fixtures/negative/SpreeProdMiss1Mandatory.csv +4 -0
  76. data/spec/fixtures/negative/SpreeProdMiss1Mandatory.xls +0 -0
  77. data/spec/fixtures/negative/SpreeProdMissManyMandatory.csv +4 -0
  78. data/spec/fixtures/negative/SpreeProdMissManyMandatory.xls +0 -0
  79. data/spec/fixtures/simple_export_spec.xls +0 -0
  80. data/spec/fixtures/simple_template_spec.xls +0 -0
  81. data/spec/fixtures/spree/SpreeProducts.csv +4 -0
  82. data/spec/fixtures/spree/SpreeProducts.xls +0 -0
  83. data/spec/fixtures/spree/SpreeProductsMultiColumn.csv +4 -0
  84. data/spec/fixtures/spree/SpreeProductsMultiColumn.xls +0 -0
  85. data/spec/fixtures/spree/SpreeProductsSimple.csv +4 -0
  86. data/spec/fixtures/spree/SpreeProductsSimple.xls +0 -0
  87. data/spec/fixtures/spree/SpreeZoneExample.csv +5 -0
  88. data/spec/fixtures/spree/SpreeZoneExample.xls +0 -0
  89. data/spec/fixtures/test_model_defs.rb +57 -0
  90. data/spec/loader_spec.rb +121 -0
  91. data/spec/method_mapper_spec.rb +238 -0
  92. data/spec/spec_helper.rb +116 -0
  93. data/spec/spree_generator_spec.rb +65 -0
  94. data/spec/spree_loader_spec.rb +311 -0
  95. data/spec/spree_method_mapping_spec.rb +215 -0
  96. data/tasks/config/seed_fu_product_template.erb +15 -0
  97. data/tasks/config/tidy_config.txt +13 -0
  98. data/tasks/db_tasks.rake +65 -0
  99. data/tasks/excel_generator.rake +79 -0
  100. data/tasks/file_tasks.rake +37 -0
  101. data/tasks/import/csv.rake +50 -0
  102. data/tasks/import/excel.rake +67 -0
  103. data/tasks/spree/image_load.rake +109 -0
  104. data/tasks/spree/product_loader.rake +44 -0
  105. data/tasks/word_to_seedfu.rake +167 -0
  106. data/test/helper.rb +18 -0
  107. data/test/test_interact.rb +7 -0
  108. metadata +301 -0
@@ -0,0 +1,88 @@
1
+ # This class provides a value map (hash) from a text mapping file
2
+ #
3
+ # The map file is a text file of delimeted key -> values pairs
4
+ #
5
+ # SUPPORTED FILE FORMATS:
6
+ #
7
+ # 2 column e.g. a,b
8
+ # creates a simple hash {a => b)
9
+ #
10
+ # 3 column e.g. a,b,c
11
+ # a,b becomes the key, c is the vaule
12
+ # creates a hash { [a,b] => c }
13
+ #
14
+ # 4 column e.g. a,b,c,d
15
+ # a,b becomes the key, c,d the value
16
+ # creates a hash { [a,b] => [c,d] }
17
+ #
18
+ # TODO allow mapping file to be an xml file
19
+ #
20
+ class ValueMapFromFile < Hash
21
+
22
+ def intialize(file_path, delim = ',')
23
+ @delegate_to = {}
24
+ @delim = delim
25
+ load_map(file_path)
26
+ end
27
+
28
+ def load_map(file_path = nil, delim = ',')
29
+ @file = file_path unless file_path.nil?
30
+ @delim = delim
31
+
32
+ raise ArgumentError.new("Can not read map file: #{@file}") unless File.readable?(@file)
33
+
34
+ File.open(@file).each_line do |line|
35
+ next unless(line && line.chomp!)
36
+
37
+ values = line.split(@delim)
38
+
39
+ case values.nitems
40
+ when 2 then self.store(values[0], values[1])
41
+ when 3 then self.store([values[0], values[1]], values[2])
42
+ when 4 then self.store([values[0], values[1]],[values[2], values[3]])
43
+ else
44
+ raise ArgumentError.new("Bad key,value row in #{@file}: #{values.nitems} number of columns not supported")
45
+ end
46
+ end
47
+
48
+ return self
49
+ end
50
+ end
51
+
52
+
53
+ # Expects file of format [TradeType,LDN_TradeId,HUB_TradeId,LDN_AssetId,HUB_AssetId,LDN_StrutureId,HUB_StructureId,LDN_ProductType,HUB_ProductType]
54
+ # Convets in to and araya containing rows [LDN_TradeId, LDN_AssetId, HUB_TradeId, HUB_AssetId]
55
+ class AssetMapFromFile < Array
56
+
57
+ def intialize(file_path, delim = ',')
58
+ @delegate_to = {}
59
+ @delim = delim
60
+ load_map(file_path)
61
+ end
62
+
63
+ def load_map(file_path = nil, delim = ',')
64
+ @file = file_path unless file_path.nil?
65
+ @delim = delim
66
+
67
+ raise ArgumentError.new("Can not read asset map file: #{@file}") unless File.readable?(@file)
68
+
69
+ File.open(@file).each_line do |line|
70
+ next unless(line && line.chomp!)
71
+ # skip the header row
72
+ next if line.include?('TradeType')
73
+
74
+ values = line.split(@delim)
75
+
76
+ self.push(Array[values[1], values[3], values[2], values[4]])
77
+ end
78
+
79
+ return self
80
+ end
81
+
82
+ def write_map(file_path = nil, delim = ',')
83
+ mapfile = File.open( file_path, 'w')
84
+
85
+ self.each{|row| mapfile.write(row.join(delim)+"\n")}
86
+ end
87
+
88
+ end
@@ -0,0 +1,237 @@
1
+ # Copyright:: (c) Autotelik Media Ltd 2011
2
+ # Author :: Tom Statter
3
+ # Date :: Aug 2010
4
+ # License:: MIT
5
+ #
6
+ # Details:: This class provides info and access to the individual population methods
7
+ # on an AR model. Populated by, and coupled with MethodMapper,
8
+ # which does the model interrogation work and stores sets of MethodDetails.
9
+ #
10
+ # Enables 'loaders' to iterate over the MethodMapper results set,
11
+ # and assign values to AR object, without knowing anything about that receiving object.
12
+ #
13
+ require 'to_b'
14
+
15
+ module DataShift
16
+
17
+ class MethodDetail
18
+
19
+ def self.type_enum
20
+ @type_enum ||= Set[:assignment, :belongs_to, :has_one, :has_many]
21
+ @type_enum
22
+ end
23
+
24
+ # When looking up an association, try each of these in turn till a match
25
+ # i.e find_by_name .. find_by_title and so on, lastly try the raw id
26
+ @@insistent_find_by_list ||= [:name, :title, :id]
27
+
28
+ # Name is the raw, client supplied name
29
+ attr_reader :name, :col_type, :current_value
30
+
31
+ attr_reader :operator, :operator_type
32
+
33
+ # Store the raw (client supplied) name against the active record klass(model), operator and types
34
+ # col_types can typically be derived from klass.columns - set of ActiveRecord::ConnectionAdapters::Column
35
+
36
+ def initialize(client_name, klass, operator, type, col_types = {} )
37
+ @klass, @name = klass, client_name
38
+
39
+ if( MethodDetail::type_enum.member?(type.to_sym) )
40
+ @operator_type = type
41
+ else
42
+ raise "Bad operator Type #{type} passed to Method Detail"
43
+ end
44
+
45
+ @operator = operator
46
+
47
+ # Note : Not all assignments will currently have a column type, for example
48
+ # those that are derived from a delegate_belongs_to
49
+ if(col_types.empty?)
50
+ @col_type = klass.columns.find{ |col| col.name == operator }
51
+ else
52
+ @col_type = col_types[operator]
53
+ end
54
+ end
55
+
56
+
57
+ # Return the actual operator's name for supplied method type
58
+ # where type one of :assignment, :has_one, :belongs_to, :has_many etc
59
+ def operator_for( type )
60
+ return operator if(@operator_type == type)
61
+ nil
62
+ end
63
+
64
+ def operator?(name)
65
+ operator == name
66
+ end
67
+
68
+ # Return the operator's expected class name, if can be derived, else nil
69
+ def operator_class_name()
70
+ @operator_class_name ||= if(operator_for(:has_many) || operator_for(:belongs_to) || operator_for(:has_one))
71
+ begin
72
+ Kernel.const_get(operator.classify)
73
+ operator.classify
74
+ rescue; ""; end
75
+
76
+ elsif(@col_type)
77
+ @col_type.type.to_s.classify
78
+ else
79
+ ""
80
+ end
81
+
82
+ @operator_class_name
83
+ end
84
+
85
+ # Return the operator's expected class, if can be derived, else nil
86
+ def operator_class()
87
+ @operator_class ||= if(operator_for(:has_many) || operator_for(:belongs_to) || operator_for(:has_one))
88
+ begin
89
+ Kernel.const_get(operator.classify)
90
+ rescue; ""; end
91
+
92
+ elsif(@col_type)
93
+ begin
94
+ Kernel.const_get(@col_type.type.to_s.classify)
95
+ rescue; nil; end
96
+ else
97
+ nil
98
+ end
99
+
100
+ @operator_class
101
+ end
102
+
103
+
104
+ def assign(record, value )
105
+
106
+ @current_value = value
107
+
108
+ puts "WARNING nil value supplied for Column [#{@name}]" if(@current_value.nil?)
109
+
110
+ if( operator_for(:belongs_to) )
111
+
112
+ #puts "DEBUG : BELONGS_TO : #{@name} : #{operator} - Lookup #{@current_value} in DB"
113
+ insistent_belongs_to(record, @current_value)
114
+
115
+ elsif( operator_for(:has_many) )
116
+
117
+ #puts "DEBUG : HAS_MANY : #{@name} : #{operator}(#{operator_class}) - Lookup #{@current_value} in DB"
118
+ if(value.is_a?(Array) || value.is_a?(operator_class))
119
+ record.send(operator) << value
120
+ else
121
+ puts "ERROR #{value.class} - Not expected type for has_many #{operator} - cannot assign"
122
+ # TODO - Not expected type - maybe try to look it up somehow ?"
123
+ #insistent_has_many(record, @current_value)
124
+ end
125
+
126
+ elsif( operator_for(:has_one) )
127
+
128
+ #puts "DEBUG : HAS_MANY : #{@name} : #{operator}(#{operator_class}) - Lookup #{@current_value} in DB"
129
+ if(value.is_a?(operator_class))
130
+ record.send(operator + '=', value)
131
+ else
132
+ puts "ERROR #{value.class} - Not expected type for has_one #{operator} - cannot assign"
133
+ # TODO - Not expected type - maybe try to look it up somehow ?"
134
+ #insistent_has_many(record, @current_value)
135
+ end
136
+
137
+ elsif( operator_for(:assignment) && @col_type )
138
+ #puts "DEBUG : COl TYPE defined for #{@name} : #{@assignment} => #{@current_value} #{@col_type.type}"
139
+ #puts "DEBUG : COl TYPE CAST: #{@current_value} => #{@col_type.type_cast( @current_value ).inspect}"
140
+ record.send( operator + '=' , @col_type.type_cast( @current_value ) )
141
+
142
+ #puts "DEBUG : MethodDetails Assignment RESULT: #{record.send(operator)}"
143
+
144
+ elsif( operator_for(:assignment) )
145
+ #puts "DEBUG : Brute force assignment of value #{@current_value} supplied for Column [#{@name}]"
146
+ # brute force case for assignments without a column type (which enables us to do correct type_cast)
147
+ # so in this case, attempt straightforward assignment then if that fails, basic ops such as to_s, to_i, to_f etc
148
+ insistent_assignment(record, @current_value)
149
+ else
150
+ puts "WARNING: No operator found for assignment on #{self.inspect} for Column [#{@name}]"
151
+ end
152
+ end
153
+
154
+ def pp
155
+ "#{@name} => #{operator}"
156
+ end
157
+
158
+
159
+ def self.insistent_method_list
160
+ @insistent_method_list ||= [:to_s, :to_i, :to_f, :to_b]
161
+ @insistent_method_list
162
+ end
163
+
164
+ private
165
+
166
+ # Attempt to find the associated object via id, name, title ....
167
+ def insistent_belongs_to( record, value )
168
+
169
+ if( value.class == operator_class)
170
+ record.send(operator) << value
171
+ else
172
+
173
+ @@insistent_find_by_list.each do |x|
174
+ begin
175
+ next unless operator_class.respond_to?( "find_by_#{x}" )
176
+ item = operator_class.send( "find_by_#{x}", value)
177
+ if(item)
178
+ record.send(operator + '=', item)
179
+ break
180
+ end
181
+ rescue => e
182
+ puts "ERROR: #{e.inspect}"
183
+ if(x == MethodDetail::insistent_method_list.last)
184
+ raise "I'm sorry I have failed to assign [#{value}] to #{@assignment}" unless value.nil?
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ # Attempt to find the associated object via id, name, title ....
192
+ def insistent_has_many( record, value )
193
+
194
+ if( value.class == operator_class)
195
+ record.send(operator) << value
196
+ else
197
+ @@insistent_find_by_list.each do |x|
198
+ begin
199
+ item = operator_class.send( "find_by_#{x}", value)
200
+ if(item)
201
+ record.send(operator) << item
202
+ break
203
+ end
204
+ rescue => e
205
+ puts "ERROR: #{e.inspect}"
206
+ if(x == MethodDetail::insistent_method_list.last)
207
+ raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil?
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ def insistent_assignment( record, value )
215
+ #puts "DEBUG: RECORD CLASS #{record.class}"
216
+ op = operator + '='
217
+
218
+ begin
219
+ record.send(op, value)
220
+ rescue => e
221
+ MethodDetail::insistent_method_list.each do |f|
222
+ begin
223
+ record.send(op, value.send( f) )
224
+ break
225
+ rescue => e
226
+ #puts "DEBUG: insistent_assignment: #{e.inspect}"
227
+ if f == MethodDetail::insistent_method_list.last
228
+ puts "I'm sorry I have failed to assign [#{value}] to #{operator}"
229
+ raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil?
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ end
@@ -0,0 +1,257 @@
1
+ # Copyright:: (c) Autotelik Media Ltd 2011
2
+ # Author :: Tom Statter
3
+ # Date :: Aug 2010
4
+ # License:: MIT
5
+ #
6
+ # Details:: A base class that stores details of all possible associations on AR classes and,
7
+ # given user supplied class and name, attempts to find correct attribute/association.
8
+ #
9
+ # Derived classes define where the user supplied list of names originates from.
10
+ #
11
+ # Example usage, load from a spreadsheet where the column names are only
12
+ # an approximation of the actual associations. Given a column heading of
13
+ # 'Product Properties' on class Product, find_method_detail() would search AR model,
14
+ # and return details of real has_many association 'product_properties'.
15
+ #
16
+ # This real association can then be used to send spreadsheet row data to the AR object.
17
+ #
18
+ require 'method_detail'
19
+
20
+ module DataShift
21
+
22
+ class MethodMapper
23
+
24
+ attr_accessor :header_row, :headers
25
+ attr_accessor :method_details, :missing_methods
26
+
27
+ @@has_many = Hash.new
28
+ @@belongs_to = Hash.new
29
+ @@assignments = Hash.new
30
+ @@column_types = Hash.new
31
+
32
+ def initialize
33
+ @method_details = []
34
+ @headers = []
35
+ end
36
+
37
+ # Build complete picture of the methods whose names listed in method_list
38
+ # Handles method names as defined by a user or in file headers where names may
39
+ # not be exactly as required e.g handles capitalisation, white space, _ etc
40
+ # Returns: Array of matching method_details
41
+ #
42
+ def populate_methods( klass, method_list )
43
+ @method_details, @missing_methods = [], []
44
+
45
+ method_list.each do |x|
46
+ md = MethodMapper::find_method_detail( klass, x )
47
+ md ? @method_details << md : @missing_methods << x
48
+ end
49
+ #@method_details.compact! .. currently we may neeed to map via the index on @method_details so don't remove nils for now
50
+ @method_details
51
+ end
52
+
53
+ # The raw client supplied names
54
+ def method_names()
55
+ @method_details.collect( &:name )
56
+ end
57
+
58
+ # The true operator names discovered from model
59
+ def operator_names()
60
+ @method_details.collect( &:operator )
61
+ end
62
+
63
+ # Returns true if discovered methods contain every operator in mandatory_list
64
+ def contains_mandatory?( mandatory_list )
65
+ [ [*mandatory_list] - operator_names].flatten.empty?
66
+ end
67
+
68
+ def missing_mandatory( mandatory_list )
69
+ [ [*mandatory_list] - operator_names].flatten
70
+ end
71
+
72
+ # Create picture of the operators for assignment available on an AR model,
73
+ # including via associations (which provide both << and = )
74
+ # Options:
75
+ # :reload => clear caches and reperform lookup
76
+ # :instance_methods => if true include instance method type assignment operators as well as model's pure columns
77
+ #
78
+ def self.find_operators(klass, options = {} )
79
+
80
+ # Find the has_many associations which can be populated via <<
81
+ if( options[:reload] || @@has_many[klass].nil? )
82
+ @@has_many[klass] = klass.reflect_on_all_associations(:has_many).map { |i| i.name.to_s }
83
+ klass.reflect_on_all_associations(:has_and_belongs_to_many).inject(@@has_many[klass]) { |x,i| x << i.name.to_s }
84
+ end
85
+ # puts "DEBUG: Has Many Associations:", @@has_many[klass].inspect
86
+
87
+ # Find the belongs_to associations which can be populated via Model.belongs_to_name = OtherArModelObject
88
+ if( options[:reload] || @@belongs_to[klass].nil? )
89
+ @@belongs_to[klass] = klass.reflect_on_all_associations(:belongs_to).map { |i| i.name.to_s }
90
+ end
91
+
92
+ #puts "Belongs To Associations:", @@belongs_to[klass].inspect
93
+
94
+ # Find the has_one associations which can be populated via Model.has_one_name = OtherArModelObject
95
+ if( options[:reload] || self.has_one[klass].nil? )
96
+ self.has_one[klass] = klass.reflect_on_all_associations(:has_one).map { |i| i.name.to_s }
97
+ end
98
+
99
+ #puts "has_one Associations:", self.has_one[klass].inspect
100
+
101
+ # Find the model's column associations which can be populated via xxxxxx= value
102
+ # Note, not all reflections return method names in same style so we convert all to
103
+ # the raw form i.e without the '=' for consistency
104
+ if( options[:reload] || @@assignments[klass].nil? )
105
+
106
+ @@assignments[klass] = klass.column_names
107
+
108
+ if(options[:instance_methods] == true)
109
+ setters = klass.instance_methods.grep(/\w+=/).collect {|x| x.to_s }
110
+
111
+ if(klass.respond_to? :defined_activerecord_methods)
112
+ setters = setters - klass.defined_activerecord_methods.to_a
113
+ end
114
+
115
+ # get into same format as other names
116
+ @@assignments[klass] += setters.map{|i| i.gsub(/=/, '')}
117
+ end
118
+
119
+ @@assignments[klass] -= @@has_many[klass] if(@@has_many[klass])
120
+ @@assignments[klass] -= @@belongs_to[klass] if(@@belongs_to[klass])
121
+ @@assignments[klass] -= self.has_one[klass] if(self.has_one[klass])
122
+
123
+ @@assignments[klass].uniq!
124
+
125
+ @@assignments[klass].each do |assign|
126
+ @@column_types[klass] ||= {}
127
+ column_def = klass.columns.find{ |col| col.name == assign }
128
+ @@column_types[klass].merge!( assign => column_def) if column_def
129
+ end
130
+ end
131
+ end
132
+
133
+ def self.build_method_details( klass )
134
+ @method_details ||= {}
135
+
136
+ @method_details[klass] = []
137
+
138
+ assignments_for(klass).each do |n|
139
+ @method_details[klass] << MethodDetail.new(n, klass, n, :assignment)
140
+ end
141
+
142
+ has_one_for(klass).each do |n|
143
+ @method_details[klass] << MethodDetail.new(n, klass, n, :has_one)
144
+ end
145
+
146
+ has_many_for(klass).each do |n|
147
+ @method_details[klass] << MethodDetail.new(n, klass, n, :has_many)
148
+ end
149
+
150
+ belongs_to_for(klass).each do |n|
151
+ @method_details[klass] << MethodDetail.new(n, klass, n, :belongs_to)
152
+ end
153
+ end
154
+
155
+ def self.method_details
156
+ @method_details ||= {}
157
+ @method_details
158
+ end
159
+
160
+ # Find the proper format of name, appropriate call + column type for a given name.
161
+ # e.g Given users entry in spread sheet check for pluralization, missing underscores etc
162
+ #
163
+ # If not nil, returned method can be used directly in for example klass.new.send( call, .... )
164
+ #
165
+ def self.find_method_detail( klass, external_name )
166
+ operator = nil
167
+
168
+ name = external_name.to_s
169
+
170
+ # TODO - check out regexp to do this work better plus Inflections ??
171
+ # Want to be able to handle any of ["Count On hand", 'count_on_hand', "Count OnHand", "COUNT ONHand" etc]
172
+ [
173
+ name,
174
+ name.tableize,
175
+ name.gsub(' ', '_'),
176
+ name.gsub(' ', '_').downcase,
177
+ name.gsub(/(\s+)/, '_').downcase,
178
+ name.gsub(' ', ''),
179
+ name.gsub(' ', '').downcase,
180
+ name.gsub(' ', '_').underscore].each do |n|
181
+
182
+ operator = (assignments_for(klass).include?(n)) ? n : nil
183
+
184
+ return MethodDetail.new(name, klass, operator, :assignment, @@column_types[klass]) if(operator)
185
+
186
+ operator = (has_one_for(klass).include?(n)) ? n : nil
187
+
188
+ return MethodDetail.new(name, klass, operator, :has_one, @@column_types[klass]) if(operator)
189
+
190
+ operator = (has_many_for(klass).include?(n)) ? n : nil
191
+
192
+ return MethodDetail.new(name, klass, operator, :has_many, @@column_types[klass]) if(operator)
193
+
194
+ operator = (belongs_to_for(klass).include?(n)) ? n : nil
195
+
196
+ return MethodDetail.new(name, klass, operator, :belongs_to, @@column_types[klass]) if(operator)
197
+
198
+ end
199
+
200
+ nil
201
+ end
202
+
203
+ def self.clear
204
+ @@belongs_to.clear
205
+ @@has_many.clear
206
+ @@assignments.clear
207
+ @@column_types.clear
208
+ self.has_one.clear
209
+ end
210
+
211
+ def self.column_key(klass, column)
212
+ "#{klass.name}:#{column}"
213
+ end
214
+
215
+ # TODO - remove use of class variables - not good Ruby design
216
+ def self.belongs_to
217
+ @@belongs_to
218
+ end
219
+
220
+ def self.has_many
221
+ @@has_many
222
+ end
223
+
224
+ def self.has_one
225
+ @has_one ||= {}
226
+ @has_one
227
+ end
228
+
229
+ def self.assignments
230
+ @@assignments
231
+ end
232
+ def self.column_types
233
+ @@column_types
234
+ end
235
+
236
+
237
+ def self.belongs_to_for(klass)
238
+ @@belongs_to[klass] || []
239
+ end
240
+ def self.has_many_for(klass)
241
+ @@has_many[klass] || []
242
+ end
243
+
244
+ def self.has_one_for(klass)
245
+ self.has_one[klass] || []
246
+ end
247
+
248
+ def self.assignments_for(klass)
249
+ @@assignments[klass] || []
250
+ end
251
+ def self.column_type_for(klass, column)
252
+ @@column_types[klass] ? @@column_types[klass][column] : []
253
+ end
254
+
255
+ end
256
+
257
+ end