datashift 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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