datashift 0.0.1 → 0.0.2
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/.document +5 -5
- data/Gemfile +28 -25
- data/LICENSE.txt +26 -26
- data/README.markdown +302 -285
- data/README.rdoc +19 -19
- data/Rakefile +93 -95
- data/VERSION +5 -5
- data/datashift.gemspec +162 -178
- data/lib/applications/jruby/jexcel_file.rb +396 -396
- data/lib/applications/jruby/word.rb +79 -79
- data/lib/datashift.rb +152 -113
- data/lib/datashift/exceptions.rb +11 -11
- data/lib/datashift/file_definitions.rb +353 -353
- data/lib/datashift/mapping_file_definitions.rb +87 -87
- data/lib/datashift/method_detail.rb +236 -236
- data/lib/datashift/method_mapper.rb +256 -256
- data/lib/generators/csv_generator.rb +36 -36
- data/lib/generators/excel_generator.rb +121 -121
- data/lib/generators/generator_base.rb +13 -13
- data/lib/helpers/core_ext/to_b.rb +24 -24
- data/lib/helpers/spree_helper.rb +131 -131
- data/lib/java/poi-3.7/LICENSE +507 -507
- data/lib/java/poi-3.7/NOTICE +21 -21
- data/lib/java/poi-3.7/RELEASE_NOTES.txt +115 -115
- data/lib/loaders/csv_loader.rb +98 -98
- data/lib/loaders/excel_loader.rb +154 -149
- data/lib/loaders/loader_base.rb +403 -331
- data/lib/loaders/spreadsheet_loader.rb +136 -136
- data/lib/loaders/spree/image_loader.rb +45 -45
- data/lib/loaders/spree/product_loader.rb +224 -224
- data/spec/csv_loader_spec.rb +30 -30
- data/spec/datashift_spec.rb +26 -26
- data/spec/db/migrate/20110803201325_create_test_bed.rb +85 -85
- data/spec/excel_generator_spec.rb +78 -78
- data/spec/excel_loader_spec.rb +204 -176
- data/spec/file_definitions.rb +141 -141
- data/spec/fixtures/.~lock.ProjectsSingleCategories.xls# +1 -0
- data/spec/fixtures/ProjectsDefaults.yml +29 -0
- data/spec/fixtures/config/database.yml +24 -24
- data/spec/fixtures/interact_models_db.sqlite +0 -0
- data/spec/fixtures/interact_spree_db.sqlite +0 -0
- data/spec/fixtures/negative/SpreeProdMiss1Mandatory.csv +4 -4
- data/spec/fixtures/negative/SpreeProdMissManyMandatory.csv +4 -4
- data/spec/fixtures/spree/SpreeProducts.csv +4 -4
- data/spec/fixtures/spree/SpreeProductsMultiColumn.csv +4 -4
- data/spec/fixtures/spree/SpreeProductsSimple.csv +4 -4
- data/spec/fixtures/spree/SpreeZoneExample.csv +5 -5
- data/spec/fixtures/test_model_defs.rb +57 -57
- data/spec/loader_spec.rb +120 -120
- data/spec/method_mapper_spec.rb +237 -237
- data/spec/spec_helper.rb +115 -115
- data/spec/spree_generator_spec.rb +64 -64
- data/spec/spree_loader_spec.rb +310 -310
- data/spec/spree_method_mapping_spec.rb +214 -214
- data/tasks/config/seed_fu_product_template.erb +15 -15
- data/tasks/config/tidy_config.txt +12 -12
- data/tasks/db_tasks.rake +65 -64
- data/tasks/excel_generator.rake +78 -78
- data/tasks/file_tasks.rake +36 -36
- data/tasks/import/csv.rake +49 -49
- data/tasks/import/excel.rake +71 -66
- data/tasks/spree/image_load.rake +108 -108
- data/tasks/spree/product_loader.rake +43 -43
- data/tasks/word_to_seedfu.rake +166 -166
- data/test/helper.rb +18 -18
- data/test/test_interact.rb +7 -7
- metadata +7 -38
- data/Gemfile.lock +0 -211
- data/bin/autospec +0 -16
- data/bin/convert_to_should_syntax +0 -16
- data/bin/erubis +0 -16
- data/bin/htmldiff +0 -16
- data/bin/jeweler +0 -16
- data/bin/ldiff +0 -16
- data/bin/nokogiri +0 -16
- data/bin/rackup +0 -16
- data/bin/rails +0 -16
- data/bin/rake +0 -16
- data/bin/rake2thor +0 -16
- data/bin/ri +0 -16
- data/bin/rspec +0 -16
- data/bin/spree +0 -16
- data/bin/thor +0 -16
- data/bin/tilt +0 -16
- data/bin/tt +0 -16
@@ -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 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
|
-
|
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
88
|
end
|
@@ -1,237 +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
|
-
|
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
|
-
|
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
|
+
# logger.info("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
237
|
end
|