datashift 0.10.1 → 0.10.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/Rakefile +6 -1
- data/VERSION +1 -1
- data/datashift.gemspec +13 -6
- data/lib/datashift.rb +2 -20
- data/lib/datashift/exceptions.rb +2 -0
- data/lib/datashift/method_detail.rb +15 -29
- data/lib/datashift/method_dictionary.rb +36 -21
- data/lib/datashift/method_mapper.rb +56 -16
- data/lib/datashift/populator.rb +23 -0
- data/lib/datashift/querying.rb +86 -0
- data/lib/generators/csv_generator.rb +1 -4
- data/lib/generators/excel_generator.rb +28 -11
- data/lib/generators/generator_base.rb +12 -0
- data/lib/loaders/csv_loader.rb +9 -3
- data/lib/loaders/excel_loader.rb +14 -6
- data/lib/loaders/loader_base.rb +38 -125
- data/lib/loaders/paperclip/attachment_loader.rb +130 -62
- data/lib/loaders/paperclip/datashift_paperclip.rb +46 -12
- data/lib/loaders/paperclip/image_loading.rb +25 -41
- data/lib/thor/generate.thor +16 -6
- data/lib/thor/paperclip.thor +25 -5
- data/spec/Gemfile +3 -2
- data/spec/MissingAttachmentRecords/DEMO_001_ror_bag.jpeg +0 -0
- data/spec/{fixtures/images/DEMO_002_Powerstation.jpg → MissingAttachmentRecords/DEMO_002_Powerstation.jpeg} +0 -0
- data/spec/MissingAttachmentRecords/DEMO_002_Powerstation.jpg +0 -0
- data/spec/MissingAttachmentRecords/DEMO_003_ror_mug.jpeg +0 -0
- data/spec/MissingAttachmentRecords/DEMO_004_ror_ringer.jpeg +0 -0
- data/spec/excel_generator_spec.rb +28 -0
- data/spec/excel_loader_spec.rb +12 -17
- data/spec/fixtures/config/database.yml +1 -1
- data/spec/fixtures/db/datashift_test_models_db.sqlite +0 -0
- data/spec/fixtures/db/migrate/20121009161700_add_digitals.rb +24 -0
- data/spec/fixtures/images/DEMO_002_Powerstation.jpeg +0 -0
- data/spec/fixtures/models/digital.rb +14 -0
- data/spec/fixtures/models/owner.rb +5 -3
- data/spec/fixtures/test_model_defs.rb +4 -62
- data/spec/loader_spec.rb +42 -50
- data/spec/method_dictionary_spec.rb +3 -10
- data/spec/method_mapper_spec.rb +79 -20
- data/spec/paperclip_loader_spec.rb +95 -0
- data/spec/spec_helper.rb +44 -8
- metadata +236 -224
- data/lib/helpers/rake_utils.rb +0 -42
- data/spec/fixtures/models/test_model_defs.rb +0 -67
data/lib/datashift/populator.rb
CHANGED
@@ -18,6 +18,29 @@ module DataShift
|
|
18
18
|
@insistent_method_list
|
19
19
|
end
|
20
20
|
|
21
|
+
def self.insistent_assignment( record, value, operator )
|
22
|
+
|
23
|
+
#puts "DEBUG: RECORD CLASS #{record.class}"
|
24
|
+
op = operator + '='
|
25
|
+
|
26
|
+
begin
|
27
|
+
record.send(op, value)
|
28
|
+
rescue => e
|
29
|
+
Populator::insistent_method_list.each do |f|
|
30
|
+
begin
|
31
|
+
record.send(op, value.send( f) )
|
32
|
+
break
|
33
|
+
rescue => e
|
34
|
+
#puts "DEBUG: insistent_assignment: #{e.inspect}"
|
35
|
+
if f == Populator::insistent_method_list.last
|
36
|
+
puts "I'm sorry I have failed to assign [#{value}] to #{operator}"
|
37
|
+
raise "I'm sorry I have failed to assign [#{value}] to #{operator}" unless value.nil?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
21
44
|
def assignment( operator, record, value )
|
22
45
|
#puts "DEBUG: RECORD CLASS #{record.class}"
|
23
46
|
op = operator + '=' unless(operator.include?('='))
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
+
# Author :: Tom Statter
|
3
|
+
# Date :: Aug 2010
|
4
|
+
# License:: MIT
|
5
|
+
#
|
6
|
+
# Details:: Base class for loaders, providing a process hook which populates a model,
|
7
|
+
# based on a method map and supplied value from a file - i.e a single column/row's string value.
|
8
|
+
# Note that although a single column, the string can be formatted to contain multiple values.
|
9
|
+
#
|
10
|
+
# Tightly coupled with MethodMapper classes (in lib/engine) which contains full details of
|
11
|
+
# a file's column and it's correlated AR associations.
|
12
|
+
#
|
13
|
+
module DataShift
|
14
|
+
|
15
|
+
require 'datashift/method_mapper'
|
16
|
+
|
17
|
+
module Querying
|
18
|
+
|
19
|
+
def search_for_record(klazz, field, search_term, options = {})
|
20
|
+
|
21
|
+
begin
|
22
|
+
|
23
|
+
if(options[:case_sensitive])
|
24
|
+
return klazz.send("find_by_#{field}", search_term)
|
25
|
+
elsif(options[:use_like])
|
26
|
+
return klazz.where("#{field} like ?", "#{search_term}%").first
|
27
|
+
else
|
28
|
+
return klazz.where("lower(#{field}) = ?", search_term.downcase).first
|
29
|
+
end
|
30
|
+
|
31
|
+
rescue => e
|
32
|
+
puts e.inspect
|
33
|
+
logger.error("Exception attempting to find a record for [#{search_term}] on #{klazz}.#{field}")
|
34
|
+
logger.error e.backtrace
|
35
|
+
logger.error e.inspect
|
36
|
+
end
|
37
|
+
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Find a record for model klazz, looking up on field containing search_terms
|
42
|
+
# Responds to global Options :
|
43
|
+
# :case_sensitive : Default is a case insensitive lookup.
|
44
|
+
# :use_like : Attempts a lookup using ike and x% rather than equality
|
45
|
+
#
|
46
|
+
# Returns nil if no record found
|
47
|
+
def get_record_by(klazz, field, search_term, split_on = ' ', split_on_prefix = nil)
|
48
|
+
|
49
|
+
begin
|
50
|
+
|
51
|
+
record = search_for_record(klazz, field, search_term)
|
52
|
+
|
53
|
+
# try individual portions of search_term, front -> back i.e "A_B_C_D" => A, B, C etc
|
54
|
+
search_term.split(split_on).each do |str|
|
55
|
+
z = (split_on_prefix) ? "#{split_on_prefix}#{str}": str
|
56
|
+
record = search_for_record(klazz, field, z)
|
57
|
+
break if record
|
58
|
+
end unless(record)
|
59
|
+
|
60
|
+
# this time try incrementally scanning i.e "A_B_C_D" => A, A_B, A_B_C etc
|
61
|
+
search_term.split(split_on).inject("") do |str, term|
|
62
|
+
z = (split_on_prefix) ? "#{split_on_prefix}#{str}#{split_on}#{term}": "#{str}#{split_on}#{term}"
|
63
|
+
record = search_for_record(klazz, field, z)
|
64
|
+
break if record
|
65
|
+
term
|
66
|
+
end unless(record)
|
67
|
+
|
68
|
+
return record
|
69
|
+
rescue => e
|
70
|
+
logger.error("Exception attempting to find a record for [#{search_term}] on #{klazz}.#{field}")
|
71
|
+
logger.error e.backtrace
|
72
|
+
logger.error e.inspect
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_record_by!(klazz, field, search_terms, split_on = ' ', split_on_prefix = nil)
|
78
|
+
x = get_record_by(klazz, field, search_terms, split_on, split_on_prefix)
|
79
|
+
|
80
|
+
raise RecordNotFound, "No #{klazz} record found for [#{search_terms}] on #{field}" unless(x)
|
81
|
+
|
82
|
+
x
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -8,20 +8,19 @@
|
|
8
8
|
# TOD : Can we switch between .xls and XSSF (POI implementation of Excel 2007 OOXML (.xlsx) file format.)
|
9
9
|
#
|
10
10
|
#
|
11
|
+
require 'generator_base'
|
12
|
+
require 'excel'
|
13
|
+
|
11
14
|
module DataShift
|
12
|
-
|
13
|
-
require 'generator_base'
|
14
15
|
|
15
|
-
require 'excel'
|
16
|
-
|
17
16
|
class ExcelGenerator < GeneratorBase
|
18
17
|
|
19
18
|
include DataShift::Logging
|
20
19
|
|
21
|
-
attr_accessor :excel
|
20
|
+
attr_accessor :excel
|
22
21
|
|
23
22
|
def initialize(filename)
|
24
|
-
|
23
|
+
super(filename)
|
25
24
|
end
|
26
25
|
|
27
26
|
# Create an Excel file template (header row) representing supplied Model
|
@@ -31,8 +30,14 @@ module DataShift
|
|
31
30
|
def generate(klass, options = {})
|
32
31
|
|
33
32
|
prepare_excel(klass, options)
|
34
|
-
|
35
|
-
|
33
|
+
|
34
|
+
prep_remove_list(options)
|
35
|
+
|
36
|
+
@headers = MethodDictionary.assignments[klass]
|
37
|
+
|
38
|
+
@headers.delete_if{|h| @remove_list.include?( h.to_sym ) }
|
39
|
+
|
40
|
+
@excel.set_headers( @headers )
|
36
41
|
|
37
42
|
logger.info("ExcelGenerator saving generated template #{@filename}")
|
38
43
|
|
@@ -57,6 +62,9 @@ module DataShift
|
|
57
62
|
#
|
58
63
|
# * <tt>:remove</tt> - Association NAME(s) to remove .. :title, :id, :name
|
59
64
|
# .
|
65
|
+
# * <tt>:remove_rails</tt> - Remove Rails DB columns :
|
66
|
+
# :id, :created_at, :created_on, :updated_at, :updated_on
|
67
|
+
#
|
60
68
|
def generate_with_associations(klass, options = {})
|
61
69
|
|
62
70
|
prepare_excel(klass, options)
|
@@ -65,9 +73,9 @@ module DataShift
|
|
65
73
|
|
66
74
|
work_list = MethodDetail::supported_types_enum.to_a - [ *options[:exclude] ]
|
67
75
|
|
68
|
-
|
69
|
-
|
70
|
-
headers = []
|
76
|
+
prep_remove_list(options)
|
77
|
+
|
78
|
+
@headers = []
|
71
79
|
|
72
80
|
details_mgr = MethodDictionary.method_details_mgrs[klass]
|
73
81
|
|
@@ -94,6 +102,15 @@ module DataShift
|
|
94
102
|
|
95
103
|
private
|
96
104
|
|
105
|
+
# Take options and create a list of symbols to remove from headers
|
106
|
+
#
|
107
|
+
def prep_remove_list( options )
|
108
|
+
@remove_list = [ *options[:remove] ].compact.collect{|x| x.to_s.downcase.to_sym }
|
109
|
+
|
110
|
+
@remove_list += GeneratorBase::rails_columns if(options[:remove_rails])
|
111
|
+
end
|
112
|
+
|
113
|
+
|
97
114
|
def prepare_excel(klass, options = {})
|
98
115
|
@filename = options[:filename] if options[:filename]
|
99
116
|
|
@@ -9,6 +9,18 @@ module DataShift
|
|
9
9
|
|
10
10
|
class GeneratorBase
|
11
11
|
|
12
|
+
attr_accessor :filename, :headers, :remove_list
|
13
|
+
|
14
|
+
def initialize(filename)
|
15
|
+
@filename = filename
|
16
|
+
@headers = []
|
17
|
+
@remove_list =[]
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.rails_columns
|
22
|
+
@rails_standard_columns ||= [:id, :created_at, :created_on, :updated_at, :updated_on]
|
23
|
+
end
|
12
24
|
end
|
13
25
|
|
14
26
|
end
|
data/lib/loaders/csv_loader.rb
CHANGED
@@ -16,8 +16,14 @@ module DataShift
|
|
16
16
|
|
17
17
|
include DataShift::Logging
|
18
18
|
|
19
|
-
#
|
20
|
-
#
|
19
|
+
# Assumes header_row is first row i.e row 0
|
20
|
+
#
|
21
|
+
# Options passed through to : populate_method_mapper_from_headers
|
22
|
+
#
|
23
|
+
# [:mandatory] : Array of mandatory column names
|
24
|
+
# [:force_inclusion] : Array of inbound column names to force into mapping
|
25
|
+
# [:include_all] : Include all headers in processing - takes precedence of :force_inclusion
|
26
|
+
# [:strict] : Raise exception when no mapping found for a column heading (non mandatory)
|
21
27
|
|
22
28
|
def perform_csv_load(file_name, options = {})
|
23
29
|
|
@@ -30,7 +36,7 @@ module DataShift
|
|
30
36
|
|
31
37
|
# Create a method_mapper which maps list of headers into suitable calls on the Active Record class
|
32
38
|
# For example if model has an attribute 'price' will map columns called Price, price, PRICE etc to this attribute
|
33
|
-
|
39
|
+
populate_method_mapper_from_headers( @parsed_file.shift, options)
|
34
40
|
|
35
41
|
puts "\n\n\nLoading from CSV file: #{file_name}"
|
36
42
|
puts "Processing #{@parsed_file.size} rows"
|
data/lib/loaders/excel_loader.rb
CHANGED
@@ -24,8 +24,12 @@ module DataShift
|
|
24
24
|
# Options:
|
25
25
|
# [:sheet_number] : Default is 0. The index of the Excel Worksheet to use.
|
26
26
|
# [:header_row] : Default is 0. Use alternative row as header definition.
|
27
|
+
#
|
28
|
+
# Options passed through to : populate_method_mapper_from_headers
|
29
|
+
#
|
27
30
|
# [:mandatory] : Array of mandatory column names
|
28
31
|
# [:force_inclusion] : Array of inbound column names to force into mapping
|
32
|
+
# [:include_all] : Include all headers in processing - takes precedence of :force_inclusion
|
29
33
|
# [:strict] : Raise exception when no mapping found for a column heading (non mandatory)
|
30
34
|
|
31
35
|
def perform_excel_load( file_name, options = {} )
|
@@ -66,7 +70,9 @@ module DataShift
|
|
66
70
|
|
67
71
|
# Create a method_mapper which maps list of headers into suitable calls on the Active Record class
|
68
72
|
# For example if model has an attribute 'price' will map columns called Price, price, PRICE etc to this attribute
|
69
|
-
|
73
|
+
populate_method_mapper_from_headers( @headers, options )
|
74
|
+
|
75
|
+
@method_mapper
|
70
76
|
|
71
77
|
logger.info "Excel Loader processing #{@sheet.num_rows} rows"
|
72
78
|
|
@@ -97,11 +103,13 @@ module DataShift
|
|
97
103
|
# as part of this we also attempt to save early, for example before assigning to
|
98
104
|
# has_and_belongs_to associations which require the load_object has an id for the join table
|
99
105
|
|
100
|
-
# Iterate over
|
101
|
-
|
102
|
-
|
106
|
+
# Iterate over method_details, working on data out of associated Excel column
|
107
|
+
@method_mapper.method_details.each do |method_detail|
|
108
|
+
|
109
|
+
|
110
|
+
next unless method_detail # TODO populate unmapped with a real MethodDetail that is 'null' and create is_nil
|
103
111
|
|
104
|
-
value = row[
|
112
|
+
value = row[method_detail.column_index]
|
105
113
|
|
106
114
|
contains_data = true unless(value.nil? || value.to_s.empty?)
|
107
115
|
|
@@ -118,7 +126,7 @@ module DataShift
|
|
118
126
|
unless(save)
|
119
127
|
failure
|
120
128
|
logger.error "Failed to save row [#{row}]"
|
121
|
-
logger.error load_object.errors.inspect
|
129
|
+
logger.error load_object.errors.inspect if(load_object)
|
122
130
|
else
|
123
131
|
logger.info "Row #{row} succesfully SAVED : ID #{load_object.id}"
|
124
132
|
end
|
data/lib/loaders/loader_base.rb
CHANGED
@@ -13,11 +13,13 @@
|
|
13
13
|
module DataShift
|
14
14
|
|
15
15
|
require 'datashift/method_mapper'
|
16
|
+
require 'datashift/querying'
|
16
17
|
|
17
18
|
class LoaderBase
|
18
19
|
|
19
20
|
include DataShift::Logging
|
20
21
|
include DataShift::Populator
|
22
|
+
include DataShift::Querying
|
21
23
|
|
22
24
|
attr_reader :headers
|
23
25
|
|
@@ -32,61 +34,7 @@ module DataShift
|
|
32
34
|
|
33
35
|
def options() return @config; end
|
34
36
|
|
35
|
-
# Support multiple associations being added to a base object to be specified in a single column.
|
36
|
-
#
|
37
|
-
# Entry represents the association to find via supplied name, value to use in the lookup.
|
38
|
-
# Can contain multiple lookup name/value pairs, separated by multi_assoc_delim ( | )
|
39
|
-
#
|
40
|
-
# Default syntax :
|
41
|
-
#
|
42
|
-
# Name1:value1, value2|Name2:value1, value2, value3|Name3:value1, value2
|
43
|
-
#
|
44
|
-
# E.G.
|
45
|
-
# Association Properties, has a column named Size, and another called Colour,
|
46
|
-
# and this combination could be used to lookup multiple associations to add to the main model Jumper
|
47
|
-
#
|
48
|
-
# Size:small # => generates find_by_size( 'small' )
|
49
|
-
# Size:large # => generates find_by_size( 'large' )
|
50
|
-
# Colour:red,green,blue # => generates find_all_by_colour( ['red','green','blue'] )
|
51
|
-
#
|
52
|
-
# Size:large|Size:medium|Size:large
|
53
|
-
# => Find 3 different associations, perform lookup via column called Size
|
54
|
-
# => Jumper.properties << [ small, medium, large ]
|
55
|
-
#
|
56
|
-
def self.name_value_delim
|
57
|
-
@name_value_delim ||= ':'
|
58
|
-
@name_value_delim
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.set_name_value_delim(x) @name_value_delim = x; end
|
62
|
-
# TODO - support embedded object creation/update via hash (which hopefully we should be able to just forward to AR)
|
63
|
-
#
|
64
|
-
# |Category|
|
65
|
-
# name:new{ :date => '20110102', :owner = > 'blah'}
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
def self.multi_value_delim
|
70
|
-
@multi_value_delim ||= ','
|
71
|
-
@multi_value_delim
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.set_multi_value_delim(x) @multi_value_delim = x; end
|
75
|
-
|
76
|
-
# TODO - support multi embedded object creation/update via hash (which hopefully we should be able to just forward to AR)
|
77
|
-
#
|
78
|
-
# |Category|
|
79
|
-
# name:new{ :a => 1, :b => 2}|name:medium{ :a => 6, :b => 34}|name:old{ :a => 12, :b => 67}
|
80
|
-
#
|
81
|
-
def self.multi_assoc_delim
|
82
|
-
@multi_assoc_delim ||= '|'
|
83
|
-
@multi_assoc_delim
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
|
-
def self.set_multi_assoc_delim(x) @multi_assoc_delim = x; end
|
88
37
|
|
89
|
-
|
90
38
|
# Setup loading
|
91
39
|
#
|
92
40
|
# Options to drive building the method dictionary for a class, enabling headers to be mapped to operators on that class.
|
@@ -109,7 +57,7 @@ module DataShift
|
|
109
57
|
# Create dictionary of data on all possible 'setter' methods which can be used to
|
110
58
|
# populate or integrate an object of type @load_object_class
|
111
59
|
DataShift::MethodDictionary.build_method_details(@load_object_class)
|
112
|
-
end
|
60
|
+
end if(options[:load] || options[:reload])
|
113
61
|
|
114
62
|
@method_mapper = DataShift::MethodMapper.new
|
115
63
|
@config = options.dup # clone can cause issues like 'can't modify frozen hash'
|
@@ -125,7 +73,9 @@ module DataShift
|
|
125
73
|
@prefixes = {}
|
126
74
|
@postfixes = {}
|
127
75
|
|
76
|
+
# TODO - move to own LoadStats or LoadReport class
|
128
77
|
@loaded_objects = []
|
78
|
+
@failed_objects = []
|
129
79
|
|
130
80
|
reset(object)
|
131
81
|
end
|
@@ -163,30 +113,34 @@ module DataShift
|
|
163
113
|
|
164
114
|
|
165
115
|
|
166
|
-
# Core API
|
167
|
-
#
|
116
|
+
# Core API
|
117
|
+
#
|
118
|
+
# Given a list of free text column names from a file,
|
119
|
+
# map all headers to a MethodDetail instance containing details on operator, look ups etc.
|
168
120
|
#
|
169
|
-
#
|
121
|
+
# These are available through @method_mapper.method_details
|
170
122
|
#
|
171
123
|
# Options:
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
124
|
+
# [:strict] : Raise an exception of any headers can't be mapped to an attribute/association
|
125
|
+
# [:ignore] : List of column headers to ignore when building operator map
|
126
|
+
# [:mandatory] : List of columns that must be present in headers
|
175
127
|
#
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
128
|
+
# [:force_inclusion] : List of columns that do not map to any operator but should be includeed in processing.
|
129
|
+
#
|
130
|
+
# This provides the opportunity for loaders to provide specific methods to handle these fields
|
131
|
+
# when no direct operator is available on the modle or it's associations
|
179
132
|
#
|
180
|
-
|
133
|
+
# [:include_all] : Include all headers in processing - takes precedence of :force_inclusion
|
134
|
+
#
|
135
|
+
def populate_method_mapper_from_headers( headers, options = {} )
|
181
136
|
@headers = headers
|
182
137
|
|
183
138
|
mandatory = options[:mandatory] || []
|
184
|
-
|
185
|
-
|
139
|
+
|
186
140
|
strict = (options[:strict] == true)
|
187
141
|
|
188
142
|
begin
|
189
|
-
@method_mapper.
|
143
|
+
@method_mapper.map_inbound_headers_to_methods( load_object_class, @headers, options )
|
190
144
|
rescue => e
|
191
145
|
puts e.inspect, e.backtrace
|
192
146
|
logger.error("Failed to map header row to set of database operators : #{e.inspect}")
|
@@ -194,7 +148,7 @@ module DataShift
|
|
194
148
|
end
|
195
149
|
|
196
150
|
unless(@method_mapper.missing_methods.empty?)
|
197
|
-
puts "WARNING: These headings couldn't be mapped to class #{load_object_class}
|
151
|
+
puts "WARNING: These headings couldn't be mapped to class #{load_object_class} :\n#{@method_mapper.missing_methods.inspect}"
|
198
152
|
raise MappingDefinitionError, "Missing mappings for columns : #{@method_mapper.missing_methods.join(",")}" if(strict)
|
199
153
|
end
|
200
154
|
|
@@ -202,6 +156,8 @@ module DataShift
|
|
202
156
|
@method_mapper.missing_mandatory(mandatory).each { |e| puts "ERROR: Mandatory column missing - expected column '#{e}'" }
|
203
157
|
raise MissingMandatoryError, "Mandatory columns missing - please fix and retry."
|
204
158
|
end unless(mandatory.empty?)
|
159
|
+
|
160
|
+
@method_mapper
|
205
161
|
end
|
206
162
|
|
207
163
|
|
@@ -219,59 +175,20 @@ module DataShift
|
|
219
175
|
#
|
220
176
|
# If suitable association found, process row data and then assign to current load_object
|
221
177
|
def find_and_process(column_name, data)
|
178
|
+
|
179
|
+
puts "WARNING: MethodDictionary empty for class #{load_object_class}" unless(MethodDictionary.for?(load_object_class))
|
180
|
+
|
222
181
|
method_detail = MethodDictionary.find_method_detail( load_object_class, column_name )
|
223
182
|
|
224
183
|
if(method_detail)
|
225
184
|
prepare_data(method_detail, data)
|
226
185
|
process()
|
227
186
|
else
|
228
|
-
@load_object.errors.
|
187
|
+
@load_object.errors.add(:base, "No matching method found for column #{column_name}")
|
229
188
|
end
|
230
189
|
end
|
231
190
|
|
232
191
|
|
233
|
-
# Find a record for model klazz, looking up on field containing search_terms
|
234
|
-
# Responds to global Options :
|
235
|
-
# :case_sensitive : Default is a case insensitive lookup.
|
236
|
-
# :use_like : Attempts a lookup using ike and x% ratehr than equality
|
237
|
-
|
238
|
-
def get_record_by(klazz, field, search_terms, split_on = ' ', split_on_prefix = nil)
|
239
|
-
|
240
|
-
begin
|
241
|
-
record = if(@config[:case_sensitive])
|
242
|
-
klazz.send("find_by_#{field}", search_terms)
|
243
|
-
elsif(@config[:use_like])
|
244
|
-
klazz.where("#{field} like ?", "#{search_terms}%").first
|
245
|
-
else
|
246
|
-
klazz.where("lower(#{field}) = ?", search_terms.downcase).first
|
247
|
-
end
|
248
|
-
|
249
|
-
# try the separate individual portions of the search_terms, front -> back
|
250
|
-
search_terms.split(split_on).each do |x|
|
251
|
-
z = "#{split_on_prefix}#{x}" if(split_on_prefix)
|
252
|
-
|
253
|
-
record = get_record_by(klazz, field, z, split_on, split_on_prefix)
|
254
|
-
break if record
|
255
|
-
end unless(record)
|
256
|
-
|
257
|
-
# this time try sequentially and incrementally scanning
|
258
|
-
search_terms.split(split_on).inject("") do |str, term|
|
259
|
-
z = (split_on_prefix) ? "#{split_on_prefix}#{str}#{term}": "#{str}#{term}"
|
260
|
-
record = get_record_by(klazz, field, z, split_on, split_on_prefix)
|
261
|
-
break if record
|
262
|
-
term
|
263
|
-
end unless(record)
|
264
|
-
|
265
|
-
return record
|
266
|
-
|
267
|
-
rescue => e
|
268
|
-
logger.error("Exception attempting to find a record for [#{search_terms}] on #{klazz}.#{field}")
|
269
|
-
logger.error e.backtrace
|
270
|
-
logger.error e.inspect
|
271
|
-
return nil
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
192
|
# Default values and over rides can be provided in YAML config file.
|
276
193
|
#
|
277
194
|
# Any Config under key 'LoaderBase' is merged over existing options - taking precedence.
|
@@ -377,7 +294,7 @@ module DataShift
|
|
377
294
|
#
|
378
295
|
def get_find_operator_and_rest(inbound_data)
|
379
296
|
|
380
|
-
operator, rest = inbound_data.split(
|
297
|
+
operator, rest = inbound_data.split(Delimiters::name_value_delim)
|
381
298
|
|
382
299
|
#puts "DEBUG inbound_data: #{inbound_data} => #{operator} , #{rest}"
|
383
300
|
|
@@ -415,23 +332,17 @@ module DataShift
|
|
415
332
|
|
416
333
|
# A single column can contain multiple associations delimited by special char
|
417
334
|
# Size:large|Colour:red,green,blue => ['Size:large', 'Colour:red,green,blue']
|
418
|
-
columns = @current_value.to_s.split(
|
335
|
+
columns = @current_value.to_s.split( Delimiters::multi_assoc_delim)
|
419
336
|
|
420
337
|
# Size:large|Colour:red,green,blue => generates find_by_size( 'large' ) and find_all_by_colour( ['red','green','blue'] )
|
421
338
|
|
422
339
|
columns.each do |col_str|
|
423
340
|
|
424
341
|
find_operator, col_values = get_find_operator_and_rest( col_str )
|
425
|
-
|
426
|
-
#if(@current_method_detail.find_by_operator)
|
427
|
-
# find_operator, col_values = @current_method_detail.find_by_operator, col_str
|
428
|
-
# else
|
429
|
-
# find_operator, col_values = col_str.split(LoaderBase::name_value_delim)
|
430
|
-
# end
|
431
|
-
|
342
|
+
|
432
343
|
raise "Cannot perform DB find by #{find_operator}. Expected format key:value" unless(find_operator && col_values)
|
433
344
|
|
434
|
-
find_by_values = col_values.split(
|
345
|
+
find_by_values = col_values.split(Delimiters::multi_value_delim)
|
435
346
|
|
436
347
|
find_by_values << @current_method_detail.find_by_value if(@current_method_detail.find_by_value)
|
437
348
|
|
@@ -471,11 +382,13 @@ module DataShift
|
|
471
382
|
end
|
472
383
|
|
473
384
|
def failure
|
474
|
-
@failed_objects << @load_object unless(
|
385
|
+
@failed_objects << @load_object unless( @load_object.nil? || @load_object.new_record? || @failed_objects.include?(@load_object))
|
475
386
|
end
|
476
387
|
|
477
388
|
def save
|
478
|
-
|
389
|
+
return unless( @load_object )
|
390
|
+
|
391
|
+
puts "DEBUG: SAVING #{@load_object.class} : #{@load_object.inspect}" if(@verbose)
|
479
392
|
begin
|
480
393
|
result = @load_object.save
|
481
394
|
|
@@ -582,7 +495,7 @@ module DataShift
|
|
582
495
|
# Supported Syntax :
|
583
496
|
# assoc_find_name:value | assoc2_find_name:value | etc
|
584
497
|
def get_each_assoc
|
585
|
-
current_value.to_s.split(
|
498
|
+
current_value.to_s.split( Delimiters::multi_assoc_delim )
|
586
499
|
end
|
587
500
|
|
588
501
|
private
|