forked from seamusabshere/data_miner
/
attribute.rb
231 lines (209 loc) · 5.78 KB
/
attribute.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
require 'conversions'
class DataMiner
class Attribute
attr_reader :step
attr_reader :name
attr_reader :options
def resource
step.resource
end
VALID_OPTIONS = %w{
from_units
to_units
static
dictionary
matcher
field_name
delimiter
split
units
sprintf
nullify
overwrite
upcase
units_field_name
units_field_number
field_number
chars
synthesize
}
def initialize(step, name, options = {})
@options = ::DataMiner.recursively_stringify_keys options
@step = step
@name = name
invalid_option_keys = @options.keys.select { |k| not VALID_OPTIONS.include? k }
raise "Invalid options: #{invalid_option_keys.map(&:inspect).to_sentence} (#{inspect})" if invalid_option_keys.any?
end
def inspect
%{#<DataMiner::Attribute(#{resource}##{name})>}
end
def value_in_dictionary(str)
dictionary.lookup str
end
def value_in_source(row)
value = if wants_static?
static
elsif field_number
if field_number.is_a?(::Range)
field_number.map { |n| row[n] }.join(delimiter)
else
row[field_number]
end
elsif field_name == 'row_hash'
row.row_hash
elsif row.is_a?(::Hash) or row.is_a?(::ActiveSupport::OrderedHash)
row[field_name]
end
return nil if value.nil?
return value if value.is_a?(::ActiveRecord::Base) # escape valve for parsers that look up associations directly
value = value.to_s
value = value[chars] if wants_chars?
value = do_split(value) if wants_split?
value.gsub! /[ ]+/, ' '
value.strip!
return nil if value.blank? and wants_nullification?
value.upcase! if wants_upcase?
value = do_convert row, value if wants_conversion?
value = do_sprintf value if wants_sprintf?
value
end
def match_row(row)
matcher.match row
end
def value_from_row(row)
return match_row row if wants_matcher?
value = value_in_source row
return value if value.is_a? ::ActiveRecord::Base # carry through trapdoor
value = value_in_dictionary value if wants_dictionary?
value = synthesize.call(row) if wants_synthesize?
value
end
def set_record_from_row(record, row)
return false if !wants_overwriting? and !record.send(name).nil?
record.send "#{name}=", value_from_row(row)
if wants_units?
unit = (to_units || unit_from_source(row)).to_s
unit = nil if unit.blank? and wants_nullification?
record.send "#{name}_units=", unit
end
end
def unit_from_source(row)
row[units_field_name || units_field_number].to_s.strip.underscore.to_sym
end
def do_convert(row, value)
raise "If you use 'from_units', you need to set 'to_units' (#{inspect})" unless wants_units?
value.to_f.convert((from_units || unit_from_source(row)), (to_units || unit_from_source(row)))
end
def do_sprintf(value)
if /\%[0-9\.]*f/.match sprintf
value = value.to_f
elsif /\%[0-9\.]*d/.match sprintf
value = value.to_i
end
sprintf % value
end
def do_split(value)
pattern = split_options['pattern'] || /\s+/ # default is split on whitespace
keep = split_options['keep'] || 0 # default is keep first element
value.to_s.split(pattern)[keep].to_s
end
def column_type
resource.columns_hash[name.to_s].type
end
# Our wants and needs :)
def wants_split?
split_options.present?
end
def wants_sprintf?
sprintf.present?
end
def wants_upcase?
upcase.present?
end
def wants_static?
options.has_key? 'static'
end
def wants_nullification?
nullify == true
end
def wants_chars?
chars.present?
end
def wants_synthesize?
synthesize.is_a?(::Proc)
end
def wants_overwriting?
overwrite != false
end
def wants_conversion?
from_units.present? or units_field_name.present? or units_field_number.present?
end
def wants_units?
to_units.present? or units_field_name.present? or units_field_number.present?
end
def wants_dictionary?
options['dictionary'].present?
end
def wants_matcher?
options['matcher'].present?
end
# Options that always have values
def field_name
(options['field_name'] || name).to_s
end
def delimiter
(options['delimiter'] || ', ')
end
# Options that can't be referred to by their names
def split_options
options['split']
end
def from_units
options['from_units']
end
def to_units
options['to_units'] || options['units']
end
def sprintf
options['sprintf']
end
def nullify
options['nullify']
end
def overwrite
options['overwrite']
end
def upcase
options['upcase']
end
def units_field_name
options['units_field_name']
end
def units_field_number
options['units_field_number']
end
def field_number
options['field_number']
end
def chars
options['chars']
end
def synthesize
options['synthesize']
end
def static
options['static']
end
# must be cleared before every run! (because it relies on remote data)
def dictionary
@dictionary ||= (options['dictionary'].is_a?(Dictionary) ? options['dictionary'] : Dictionary.new(options['dictionary']))
end
def matcher
@matcher ||= (options['matcher'].is_a?(::String) ? options['matcher'].constantize.new : options['matcher'])
end
def free
@dictionary.free if @dictionary.is_a?(Dictionary)
@dictionary = nil
end
end
end