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.
- data/LICENSE +9 -9
- data/README.markdown +268 -221
- data/Rakefile +76 -76
- data/lib/VERSION +1 -1
- data/lib/ar_loader.rb +87 -66
- data/lib/ar_loader/exceptions.rb +2 -0
- data/lib/{engine → ar_loader}/file_definitions.rb +353 -353
- data/lib/{engine → ar_loader}/mapping_file_definitions.rb +87 -87
- data/lib/ar_loader/method_detail.rb +257 -0
- data/lib/ar_loader/method_mapper.rb +213 -0
- data/lib/helpers/jruby/jexcel_file.rb +187 -0
- data/lib/{engine → helpers/jruby}/word.rb +79 -70
- data/lib/helpers/spree_helper.rb +85 -0
- data/lib/loaders/csv_loader.rb +87 -0
- data/lib/loaders/excel_loader.rb +132 -0
- data/lib/loaders/loader_base.rb +205 -73
- data/lib/loaders/spree/image_loader.rb +45 -41
- data/lib/loaders/spree/product_loader.rb +140 -91
- data/lib/to_b.rb +24 -24
- data/spec/csv_loader_spec.rb +27 -0
- data/spec/database.yml +19 -6
- data/spec/db/migrate/20110803201325_create_test_bed.rb +78 -0
- data/spec/excel_loader_spec.rb +113 -98
- data/spec/fixtures/BadAssociationName.xls +0 -0
- data/spec/fixtures/DemoNegativeTesting.xls +0 -0
- data/spec/fixtures/DemoTestModelAssoc.xls +0 -0
- data/spec/fixtures/ProjectsMultiCategories.xls +0 -0
- data/spec/fixtures/SimpleProjects.xls +0 -0
- data/spec/fixtures/SpreeProducts.xls +0 -0
- data/spec/fixtures/SpreeZoneExample.csv +5 -0
- data/spec/fixtures/SpreeZoneExample.xls +0 -0
- data/spec/loader_spec.rb +116 -0
- data/spec/logs/test.log +5000 -0
- data/spec/method_mapper_spec.rb +222 -0
- data/spec/models.rb +55 -0
- data/spec/spec_helper.rb +85 -18
- data/spec/spree_loader_spec.rb +223 -157
- data/tasks/config/seed_fu_product_template.erb +15 -15
- data/tasks/config/tidy_config.txt +12 -12
- data/tasks/db_tasks.rake +64 -64
- data/tasks/excel_loader.rake +63 -113
- data/tasks/file_tasks.rake +36 -37
- data/tasks/loader.rake +45 -0
- data/tasks/spree/image_load.rake +108 -107
- data/tasks/spree/product_loader.rake +49 -107
- data/tasks/word_to_seedfu.rake +166 -166
- metadata +66 -61
- data/lib/engine/jruby/jexcel_file.rb +0 -182
- data/lib/engine/jruby/method_mapper_excel.rb +0 -44
- data/lib/engine/method_detail.rb +0 -140
- data/lib/engine/method_mapper.rb +0 -157
- data/lib/engine/method_mapper_csv.rb +0 -28
- data/spec/db/migrate/20110803201325_create_testbed.rb +0 -25
@@ -1,88 +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 BadConfigError.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: self.store(values[0], values[1])
|
41
|
-
when 3: self.store([values[0], values[1]], values[2])
|
42
|
-
when 4: self.store([values[0], values[1]],[values[2], values[3]])
|
43
|
-
else
|
44
|
-
raise BadConfigError.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 BadConfigError.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
|
-
|
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 BadConfigError.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: self.store(values[0], values[1])
|
41
|
+
when 3: self.store([values[0], values[1]], values[2])
|
42
|
+
when 4: self.store([values[0], values[1]],[values[2], values[3]])
|
43
|
+
else
|
44
|
+
raise BadConfigError.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 BadConfigError.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
88
|
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:: 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 ARLoader
|
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
|
+
@@default_values = {}
|
29
|
+
@@prefixes = {}
|
30
|
+
|
31
|
+
|
32
|
+
# Name is the raw, client supplied name
|
33
|
+
attr_reader :name, :col_type, :current_value
|
34
|
+
|
35
|
+
attr_reader :operator, :operator_type
|
36
|
+
|
37
|
+
# Store the raw (client supplied) name against the active record klass(model), operator and types
|
38
|
+
def initialize(client_name, klass, operator, type, col_types = {} )
|
39
|
+
@klass, @name = klass, client_name
|
40
|
+
|
41
|
+
if( MethodDetail::type_enum.member?(type.to_sym) )
|
42
|
+
@operator_type = type
|
43
|
+
else
|
44
|
+
raise "Bad operator Type #{type} passed to Method Detail"
|
45
|
+
end
|
46
|
+
|
47
|
+
@operator = operator
|
48
|
+
|
49
|
+
# Note : Not all assignments will currently have a column type, for example
|
50
|
+
# those that are derived from a delegate_belongs_to
|
51
|
+
@col_type = col_types[operator]
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Return the actual operator's name for supplied method type
|
56
|
+
# where type one of :assignment, :has_one, :belongs_to, :has_many etc
|
57
|
+
def operator_for( type )
|
58
|
+
return operator if(@operator_type == type)
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def operator?(name)
|
63
|
+
operator == name
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return the operator's expected class name, if can be derived, else nil
|
67
|
+
def operator_class_name()
|
68
|
+
@operator_class_name ||= if(operator_for(:has_many) || operator_for(:belongs_to) || operator_for(:has_one))
|
69
|
+
begin
|
70
|
+
Kernel.const_get(operator.classify)
|
71
|
+
operator.classify
|
72
|
+
rescue; ""; end
|
73
|
+
|
74
|
+
elsif(@col_type)
|
75
|
+
@col_type.type.to_s.classify
|
76
|
+
else
|
77
|
+
""
|
78
|
+
end
|
79
|
+
|
80
|
+
@operator_class_name
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return the operator's expected class, if can be derived, else nil
|
84
|
+
def operator_class()
|
85
|
+
@operator_class ||= if(operator_for(:has_many) || operator_for(:belongs_to) || operator_for(:has_one))
|
86
|
+
begin
|
87
|
+
Kernel.const_get(operator.classify)
|
88
|
+
rescue; ""; end
|
89
|
+
|
90
|
+
elsif(@col_type)
|
91
|
+
begin
|
92
|
+
Kernel.const_get(@col_type.type.to_s.classify)
|
93
|
+
rescue; nil; end
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
@operator_class
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
def validate_value(value)
|
104
|
+
|
105
|
+
return @@default_values[@name] if(@@default_values[@name])
|
106
|
+
|
107
|
+
return "#{@@prefixes[@name]}#{value}" if(@@prefixes[@name])
|
108
|
+
|
109
|
+
value
|
110
|
+
end
|
111
|
+
|
112
|
+
def assign(record, value )
|
113
|
+
|
114
|
+
@current_value = validate_value(value)
|
115
|
+
|
116
|
+
puts "WARNING nil value supplied for Column [#{@name}]" if(@current_value.nil?)
|
117
|
+
|
118
|
+
if( operator_for(:belongs_to) )
|
119
|
+
|
120
|
+
#puts "DEBUG : BELONGS_TO : #{@name} : #{operator} - Lookup #{@current_value} in DB"
|
121
|
+
insistent_belongs_to(record, @current_value)
|
122
|
+
|
123
|
+
elsif( operator_for(:has_many) )
|
124
|
+
|
125
|
+
#puts "DEBUG : HAS_MANY : #{@name} : #{operator}(#{operator_class}) - Lookup #{@current_value} in DB"
|
126
|
+
if(value.is_a?(Array) || value.is_a?(operator_class))
|
127
|
+
record.send(operator) << value
|
128
|
+
else
|
129
|
+
puts "ERROR #{value.class} - Not expected type for has_many #{operator} - cannot assign"
|
130
|
+
# TODO - Not expected type - maybe try to look it up somehow ?"
|
131
|
+
#insistent_has_many(record, @current_value)
|
132
|
+
end
|
133
|
+
|
134
|
+
elsif( operator_for(:has_one) )
|
135
|
+
|
136
|
+
#puts "DEBUG : HAS_MANY : #{@name} : #{operator}(#{operator_class}) - Lookup #{@current_value} in DB"
|
137
|
+
if(value.is_a?(operator_class))
|
138
|
+
record.send(operator + '=', value)
|
139
|
+
else
|
140
|
+
puts "ERROR #{value.class} - Not expected type for has_one #{operator} - cannot assign"
|
141
|
+
# TODO - Not expected type - maybe try to look it up somehow ?"
|
142
|
+
#insistent_has_many(record, @current_value)
|
143
|
+
end
|
144
|
+
|
145
|
+
elsif( operator_for(:assignment) && @col_type )
|
146
|
+
#puts "DEBUG : COl TYPE defined for #{@name} : #{@assignment} => #{@current_value} #{@col_type.inspect}"
|
147
|
+
#puts "DEBUG : COl TYPE CAST: #{@current_value} => #{@col_type.type_cast( @current_value ).inspect}"
|
148
|
+
record.send( operator + '=' , @col_type.type_cast( @current_value ) )
|
149
|
+
|
150
|
+
elsif( operator_for(:assignment) )
|
151
|
+
#puts "DEBUG : Brute force assignment of value #{@current_value} supplied for Column [#{@name}]"
|
152
|
+
# brute force case for assignments without a column type (which enables us to do correct type_cast)
|
153
|
+
# so in this case, attempt straightforward assignment then if that fails, basic ops such as to_s, to_i, to_f etc
|
154
|
+
insistent_assignment(record, @current_value)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.set_default_value( name, value )
|
159
|
+
@@default_values[name] = value
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.default_value(name)
|
163
|
+
@@default_values[name]
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.set_prefix( name, value )
|
167
|
+
@@prefixes[name] = value
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.prefix_value(name)
|
171
|
+
@@prefixes[name]
|
172
|
+
end
|
173
|
+
|
174
|
+
def pp
|
175
|
+
"#{@name} => #{operator}"
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
def self.insistent_method_list
|
180
|
+
@insistent_method_list ||= [:to_s, :to_i, :to_f, :to_b]
|
181
|
+
@insistent_method_list
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
# Attempt to find the associated object via id, name, title ....
|
187
|
+
def insistent_belongs_to( record, value )
|
188
|
+
|
189
|
+
if( value.class == operator_class)
|
190
|
+
record.send(operator) << value
|
191
|
+
else
|
192
|
+
|
193
|
+
@@insistent_find_by_list.each do |x|
|
194
|
+
begin
|
195
|
+
next unless operator_class.respond_to?( "find_by_#{x}" )
|
196
|
+
item = operator_class.send( "find_by_#{x}", value)
|
197
|
+
if(item)
|
198
|
+
record.send(operator + '=', item)
|
199
|
+
break
|
200
|
+
end
|
201
|
+
rescue => e
|
202
|
+
puts "ERROR: #{e.inspect}"
|
203
|
+
if(x == MethodDetail::insistent_method_list.last)
|
204
|
+
raise "I'm sorry I have failed to assign [#{value}] to #{@assignment}" unless value.nil?
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Attempt to find the associated object via id, name, title ....
|
212
|
+
def insistent_has_many( record, value )
|
213
|
+
|
214
|
+
if( value.class == operator_class)
|
215
|
+
record.send(operator) << value
|
216
|
+
else
|
217
|
+
@@insistent_find_by_list.each do |x|
|
218
|
+
begin
|
219
|
+
item = operator_class.send( "find_by_#{x}", value)
|
220
|
+
if(item)
|
221
|
+
record.send(operator) << item
|
222
|
+
break
|
223
|
+
end
|
224
|
+
rescue => e
|
225
|
+
puts "ERROR: #{e.inspect}"
|
226
|
+
if(x == MethodDetail::insistent_method_list.last)
|
227
|
+
raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def insistent_assignment( record, value )
|
235
|
+
#puts "DEBUG: RECORD CLASS #{record.class}"
|
236
|
+
op = operator + '='
|
237
|
+
|
238
|
+
begin
|
239
|
+
record.send(op, value)
|
240
|
+
rescue => e
|
241
|
+
MethodDetail::insistent_method_list.each do |f|
|
242
|
+
begin
|
243
|
+
record.send(op, value.send( f) )
|
244
|
+
break
|
245
|
+
rescue => e
|
246
|
+
#puts "DEBUG: insistent_assignment: #{e.inspect}"
|
247
|
+
if f == MethodDetail::insistent_method_list.last
|
248
|
+
puts "I'm sorry I have failed to assign [#{value}] to #{operator}"
|
249
|
+
raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil?
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
@@ -0,0 +1,213 @@
|
|
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 ARLoader
|
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
|
+
|
41
|
+
def populate_methods( klass, method_list )
|
42
|
+
@method_details, @missing_methods = [], []
|
43
|
+
|
44
|
+
method_list.each do |x|
|
45
|
+
md = MethodMapper::find_method_detail( klass, x )
|
46
|
+
md ? @method_details << md : @missing_methods << x
|
47
|
+
end
|
48
|
+
#@method_details.compact! .. currently we may neeed to map via the index on @method_details so don't remove nils for now
|
49
|
+
@method_details
|
50
|
+
end
|
51
|
+
|
52
|
+
# The raw client supplied names
|
53
|
+
def method_names()
|
54
|
+
@method_details.collect( &:name )
|
55
|
+
end
|
56
|
+
|
57
|
+
# The true operator names discovered from model
|
58
|
+
def operator_names()
|
59
|
+
@method_details.collect( &:operator )
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns true if discovered methods contain every operator in mandatory_list
|
63
|
+
def contains_mandatory?( mandatory_list )
|
64
|
+
puts operator_names.inspect
|
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
|
+
#
|
75
|
+
def self.find_operators(klass, options = {} )
|
76
|
+
|
77
|
+
# Find the has_many associations which can be populated via <<
|
78
|
+
if( options[:reload] || @@has_many[klass].nil? )
|
79
|
+
@@has_many[klass] = klass.reflect_on_all_associations(:has_many).map { |i| i.name.to_s }
|
80
|
+
klass.reflect_on_all_associations(:has_and_belongs_to_many).inject(@@has_many[klass]) { |x,i| x << i.name.to_s }
|
81
|
+
end
|
82
|
+
# puts "DEBUG: Has Many Associations:", @@has_many[klass].inspect
|
83
|
+
|
84
|
+
# Find the belongs_to associations which can be populated via Model.belongs_to_name = OtherArModelObject
|
85
|
+
if( options[:reload] || @@belongs_to[klass].nil? )
|
86
|
+
@@belongs_to[klass] = klass.reflect_on_all_associations(:belongs_to).map { |i| i.name.to_s }
|
87
|
+
end
|
88
|
+
|
89
|
+
#puts "Belongs To Associations:", @@belongs_to[klass].inspect
|
90
|
+
|
91
|
+
# Find the has_one associations which can be populated via Model.has_one_name = OtherArModelObject
|
92
|
+
if( options[:reload] || self.has_one[klass].nil? )
|
93
|
+
self.has_one[klass] = klass.reflect_on_all_associations(:has_one).map { |i| i.name.to_s }
|
94
|
+
end
|
95
|
+
|
96
|
+
#puts "has_one Associations:", self.has_one[klass].inspect
|
97
|
+
|
98
|
+
# Find the model's column associations which can be populated via = value
|
99
|
+
if( options[:reload] || @@assignments[klass].nil? )
|
100
|
+
|
101
|
+
@@assignments[klass] = (klass.column_names + klass.instance_methods.grep(/=/).map{|i| i.gsub(/=/, '')})
|
102
|
+
@@assignments[klass] -= @@has_many[klass] if(@@has_many[klass])
|
103
|
+
@@assignments[klass] -= @@belongs_to[klass] if(@@belongs_to[klass])
|
104
|
+
@@assignments[klass] -= self.has_one[klass] if(self.has_one[klass])
|
105
|
+
|
106
|
+
@@assignments[klass].uniq!
|
107
|
+
|
108
|
+
@@assignments[klass].each do |assign|
|
109
|
+
@@column_types[klass] ||= {}
|
110
|
+
found = klass.columns.find{ |col| col.name == assign }
|
111
|
+
@@column_types[klass].merge!( found.name => found) if found
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Find the proper format of name, appropriate call + column type for a given name.
|
117
|
+
# e.g Given users entry in spread sheet check for pluralization, missing underscores etc
|
118
|
+
#
|
119
|
+
# If not nil, returned method can be used directly in for example klass.new.send( call, .... )
|
120
|
+
#
|
121
|
+
def self.find_method_detail( klass, external_name )
|
122
|
+
assign = nil
|
123
|
+
|
124
|
+
name = external_name.to_s
|
125
|
+
|
126
|
+
# TODO - check out regexp to do this work better plus Inflections ??
|
127
|
+
# Want to be able to handle any of ["Count On hand", 'count_on_hand', "Count OnHand", "COUNT ONHand" etc]
|
128
|
+
[
|
129
|
+
name,
|
130
|
+
name.tableize,
|
131
|
+
name.gsub(' ', '_'),
|
132
|
+
name.gsub(' ', '_').downcase,
|
133
|
+
name.gsub(/(\s+)/, '_').downcase,
|
134
|
+
name.gsub(' ', ''),
|
135
|
+
name.gsub(' ', '').downcase,
|
136
|
+
name.gsub(' ', '_').underscore].each do |n|
|
137
|
+
|
138
|
+
assign = (assignments_for(klass).include?(n)) ? n : nil
|
139
|
+
|
140
|
+
return MethodDetail.new(name, klass, assign, :assignment, @@column_types[klass]) if(assign)
|
141
|
+
|
142
|
+
assign = (has_one_for(klass).include?(n)) ? n : nil
|
143
|
+
|
144
|
+
return MethodDetail.new(name, klass, assign, :has_one, @@column_types[klass]) if(assign)
|
145
|
+
|
146
|
+
assign = (has_many_for(klass).include?(n)) ? n : nil
|
147
|
+
|
148
|
+
return MethodDetail.new(name, klass, assign, :has_many, @@column_types[klass]) if(assign)
|
149
|
+
|
150
|
+
assign = (belongs_to_for(klass).include?(n)) ? n : nil
|
151
|
+
|
152
|
+
return MethodDetail.new(name, klass, assign, :belongs_to, @@column_types[klass]) if(assign)
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.clear
|
160
|
+
@@belongs_to.clear
|
161
|
+
@@has_many.clear
|
162
|
+
@@assignments.clear
|
163
|
+
@@column_types.clear
|
164
|
+
self.has_one.clear
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.column_key(klass, column)
|
168
|
+
"#{klass.name}:#{column}"
|
169
|
+
end
|
170
|
+
|
171
|
+
# TODO - remove use of class variables - not good Ruby design
|
172
|
+
def self.belongs_to
|
173
|
+
@@belongs_to
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.has_many
|
177
|
+
@@has_many
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.has_one
|
181
|
+
@has_one ||= {}
|
182
|
+
@has_one
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.assignments
|
186
|
+
@@assignments
|
187
|
+
end
|
188
|
+
def self.column_types
|
189
|
+
@@column_types
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
def self.belongs_to_for(klass)
|
194
|
+
@@belongs_to[klass] || []
|
195
|
+
end
|
196
|
+
def self.has_many_for(klass)
|
197
|
+
@@has_many[klass] || []
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.has_one_for(klass)
|
201
|
+
self.has_one[klass] || []
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.assignments_for(klass)
|
205
|
+
@@assignments[klass] || []
|
206
|
+
end
|
207
|
+
def self.column_type_for(klass, column)
|
208
|
+
@@column_types[klass] ? @@column_types[klass][column] : []
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|