datashift 0.0.2 → 0.1.0
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/README.markdown +13 -12
- data/Rakefile +8 -8
- data/VERSION +1 -5
- data/datashift.gemspec +12 -38
- data/lib/applications/jruby/jexcel_file.rb +23 -11
- data/lib/datashift/method_detail.rb +44 -5
- data/lib/datashift/method_dictionary.rb +210 -0
- data/lib/datashift/method_mapper.rb +25 -191
- data/lib/generators/excel_generator.rb +12 -11
- data/lib/helpers/spree_helper.rb +36 -12
- data/lib/loaders/excel_loader.rb +2 -1
- data/lib/loaders/loader_base.rb +37 -20
- data/lib/loaders/spree/image_loader.rb +35 -16
- data/lib/loaders/spree/product_loader.rb +27 -1
- data/spec/csv_loader_spec.rb +3 -3
- data/spec/excel_exporter_spec.rb +79 -0
- data/spec/excel_generator_spec.rb +3 -3
- data/spec/excel_loader_spec.rb +35 -16
- data/spec/fixtures/ProjectsMultiCategoriesHeaderLookup.xls +0 -0
- data/spec/fixtures/images/DEMO_001_ror_bag.jpeg +0 -0
- data/spec/fixtures/images/DEMO_002_Powerstation.jpg +0 -0
- data/spec/fixtures/images/DEMO_003_ror_mug.jpeg +0 -0
- data/spec/fixtures/images/DEMO_004_ror_ringer.jpeg +0 -0
- data/spec/fixtures/interact_models_db.sqlite +0 -0
- data/spec/fixtures/interact_spree_db.sqlite +0 -0
- data/spec/fixtures/spree/SpreeProductsWithImages.xls +0 -0
- data/spec/loader_spec.rb +4 -4
- data/spec/method_dictionary_spec.rb +243 -0
- data/spec/method_mapper_spec.rb +17 -213
- data/spec/spec_helper.rb +1 -0
- data/spec/spree_loader_spec.rb +18 -1
- data/spec/spree_method_mapping_spec.rb +4 -4
- metadata +14 -130
- data/Gemfile +0 -28
- data/spec/fixtures/.~lock.ProjectsSingleCategories.xls# +0 -1
data/README.markdown
CHANGED
@@ -54,7 +54,11 @@ Provides high level rake tasks for importing data via ActiveRecord models into a
|
|
54
54
|
from various sources, currently csv or .xls files (Excel/Open Office)
|
55
55
|
|
56
56
|
|
57
|
-
|
57
|
+
jruby -S rake datashift:import:excel model=BlogPost input=BlogPostImport.xls verbose=true
|
58
|
+
|
59
|
+
Provides high level rake tasks for exporting data to various sources, currently .xls files (Excel/Open Office)
|
60
|
+
|
61
|
+
jruby -S rake datashift:export:excel model=BlogPost result=BlogExport.xls
|
58
62
|
|
59
63
|
|
60
64
|
The library can be easily extended with Loaders to deal with non trivial cases required to exchange data between
|
@@ -63,19 +67,19 @@ The library can be easily extended with Loaders to deal with non trivial cases r
|
|
63
67
|
Spree loaders are an example, these illustrate over riding processing for specific columns with
|
64
68
|
complicated lookup requirements.
|
65
69
|
|
66
|
-
A core feature of DataShift is the MethodMapper, which provides features for collecting
|
70
|
+
A core feature of DataShift is the MethodDictionary and MethodMapper, which provides features for collecting
|
67
71
|
reflection information from ActiveRecord models (all different associations, including join tables with many-to-many relationships).
|
68
72
|
|
69
73
|
A full picture of all possible operations on a class can be created very easily, for example ona Blog model :
|
70
74
|
|
71
|
-
|
75
|
+
MethodDictionary.find_operators( Blog )
|
72
76
|
|
73
|
-
This then allows Import/Export to be achieved, by mapping the file's header and column data to
|
74
|
-
|
77
|
+
This then allows Import/Export to be achieved, by mapping the file's header and column data to the found operators
|
78
|
+
i.e. the methods to set data on model's attributes and associations.
|
75
79
|
|
76
80
|
Here we retrieve the method details for a column name from a file, "Blog Date"
|
77
81
|
|
78
|
-
|
82
|
+
MethodDictionary.find_method_detail( Blog, "Blog Date" )
|
79
83
|
|
80
84
|
Loaders can use this method lookup functionality, to find the correct association for a column heading,
|
81
85
|
and populate AR object with row data.
|
@@ -88,7 +92,7 @@ and whenever possible, under the bonnet the data will be cast to correct DB type
|
|
88
92
|
|
89
93
|
Here we show how a column name from a file, "Blog Date", can be mapped to Assign a stringified date, to the blog_date column, on a new Blog object :
|
90
94
|
|
91
|
-
|
95
|
+
MethodDictionary.find_method_detail( Blog, "Blog Date" ).assign( Blog.new, "Sat Jul 23 2011" )
|
92
96
|
|
93
97
|
Because it's based on reflection against the model, can build complex relationships between tables during import/export,
|
94
98
|
and extending data files with new columns need not require any additional Ruby coding.
|
@@ -173,15 +177,12 @@ This data can be exported directly to CSV or Excel/OpenOffice spreadsheets.
|
|
173
177
|
|
174
178
|
- *Spree Rake Tasks*
|
175
179
|
|
176
|
-
|
177
|
-
and Image loading.
|
180
|
+
Spree's product associations are non trivial so specific Rake tasks are also provided for loading Spree Producta
|
181
|
+
with all associations and Image loading.
|
178
182
|
|
179
183
|
jruby -S rake datashift:spree:products input=C:\MyProducts.xls
|
180
184
|
|
181
185
|
|
182
|
-
**Product loading from Excel files specifically requires JRuby (But not Excel or OLE)**.
|
183
|
-
|
184
|
-
|
185
186
|
- *Seamless Spree Image loading can be achieved by ensuring SKU or class Name features in Image filename.
|
186
187
|
|
187
188
|
Lookup is performed either via the SKU being prepended to the image name, or by the image name being equal to the **name attribute** of the klass in question.
|
data/Rakefile
CHANGED
@@ -22,14 +22,14 @@
|
|
22
22
|
## encoding: utf-8
|
23
23
|
|
24
24
|
require 'rubygems'
|
25
|
-
require 'bundler'
|
26
|
-
begin
|
27
|
-
Bundler.setup(:default, :development)
|
28
|
-
rescue Bundler::BundlerError => e
|
29
|
-
$stderr.puts e.message
|
30
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
31
|
-
exit e.status_code
|
32
|
-
end
|
25
|
+
#require 'bundler'
|
26
|
+
#begin
|
27
|
+
# Bundler.setup(:default, :development)
|
28
|
+
#rescue Bundler::BundlerError => e
|
29
|
+
# $stderr.puts e.message
|
30
|
+
# $stderr.puts "Run `bundle install` to install missing gems"
|
31
|
+
# exit e.status_code
|
32
|
+
#end
|
33
33
|
require 'rake'
|
34
34
|
|
35
35
|
require 'lib/datashift'
|
data/VERSION
CHANGED
data/datashift.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "datashift"
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Thomas Statter"]
|
12
|
-
s.date = "2012-02-
|
12
|
+
s.date = "2012-02-27"
|
13
13
|
s.description = "A suite of tools to move data between ActiveRecord models,databases,applications like Excel/Open Office, files and projects including Spree"
|
14
14
|
s.email = "rubygems@autotelik.co.uk"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -19,13 +19,12 @@ Gem::Specification.new do |s|
|
|
19
19
|
]
|
20
20
|
s.files = [
|
21
21
|
".document",
|
22
|
-
"Gemfile",
|
23
22
|
"LICENSE.txt",
|
24
23
|
"README.markdown",
|
25
24
|
"README.rdoc",
|
26
25
|
"Rakefile",
|
27
26
|
"VERSION",
|
28
|
-
"datashift-0.0.
|
27
|
+
"datashift-0.1.0.gem",
|
29
28
|
"datashift.gemspec",
|
30
29
|
"lib/applications/jruby/jexcel_file.rb",
|
31
30
|
"lib/applications/jruby/word.rb",
|
@@ -34,6 +33,7 @@ Gem::Specification.new do |s|
|
|
34
33
|
"lib/datashift/file_definitions.rb",
|
35
34
|
"lib/datashift/mapping_file_definitions.rb",
|
36
35
|
"lib/datashift/method_detail.rb",
|
36
|
+
"lib/datashift/method_dictionary.rb",
|
37
37
|
"lib/datashift/method_mapper.rb",
|
38
38
|
"lib/generators/csv_generator.rb",
|
39
39
|
"lib/generators/excel_generator.rb",
|
@@ -64,17 +64,22 @@ Gem::Specification.new do |s|
|
|
64
64
|
"spec/csv_loader_spec.rb",
|
65
65
|
"spec/datashift_spec.rb",
|
66
66
|
"spec/db/migrate/20110803201325_create_test_bed.rb",
|
67
|
+
"spec/excel_exporter_spec.rb",
|
67
68
|
"spec/excel_generator_spec.rb",
|
68
69
|
"spec/excel_loader_spec.rb",
|
69
70
|
"spec/file_definitions.rb",
|
70
|
-
"spec/fixtures/.~lock.ProjectsSingleCategories.xls#",
|
71
71
|
"spec/fixtures/BadAssociationName.xls",
|
72
72
|
"spec/fixtures/DemoNegativeTesting.xls",
|
73
73
|
"spec/fixtures/ProjectsDefaults.yml",
|
74
74
|
"spec/fixtures/ProjectsMultiCategories.xls",
|
75
|
+
"spec/fixtures/ProjectsMultiCategoriesHeaderLookup.xls",
|
75
76
|
"spec/fixtures/ProjectsSingleCategories.xls",
|
76
77
|
"spec/fixtures/SimpleProjects.xls",
|
77
78
|
"spec/fixtures/config/database.yml",
|
79
|
+
"spec/fixtures/images/DEMO_001_ror_bag.jpeg",
|
80
|
+
"spec/fixtures/images/DEMO_002_Powerstation.jpg",
|
81
|
+
"spec/fixtures/images/DEMO_003_ror_mug.jpeg",
|
82
|
+
"spec/fixtures/images/DEMO_004_ror_ringer.jpeg",
|
78
83
|
"spec/fixtures/interact_models_db.sqlite",
|
79
84
|
"spec/fixtures/interact_spree_db.sqlite",
|
80
85
|
"spec/fixtures/negative/SpreeProdMiss1Mandatory.csv",
|
@@ -89,10 +94,12 @@ Gem::Specification.new do |s|
|
|
89
94
|
"spec/fixtures/spree/SpreeProductsMultiColumn.xls",
|
90
95
|
"spec/fixtures/spree/SpreeProductsSimple.csv",
|
91
96
|
"spec/fixtures/spree/SpreeProductsSimple.xls",
|
97
|
+
"spec/fixtures/spree/SpreeProductsWithImages.xls",
|
92
98
|
"spec/fixtures/spree/SpreeZoneExample.csv",
|
93
99
|
"spec/fixtures/spree/SpreeZoneExample.xls",
|
94
100
|
"spec/fixtures/test_model_defs.rb",
|
95
101
|
"spec/loader_spec.rb",
|
102
|
+
"spec/method_dictionary_spec.rb",
|
96
103
|
"spec/method_mapper_spec.rb",
|
97
104
|
"spec/spec_helper.rb",
|
98
105
|
"spec/spree_generator_spec.rb",
|
@@ -121,42 +128,9 @@ Gem::Specification.new do |s|
|
|
121
128
|
s.specification_version = 3
|
122
129
|
|
123
130
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
124
|
-
s.add_development_dependency(%q<rails>, ["= 3.1.3"])
|
125
|
-
s.add_development_dependency(%q<activerecord>, ["= 3.1.3"])
|
126
|
-
s.add_development_dependency(%q<activesupport>, ["= 3.1.3"])
|
127
|
-
s.add_development_dependency(%q<jruby-openssl>, [">= 0"])
|
128
|
-
s.add_development_dependency(%q<activerecord-jdbcsqlite3-adapter>, [">= 0"])
|
129
|
-
s.add_development_dependency(%q<spree>, ["= 0.70.3"])
|
130
|
-
s.add_development_dependency(%q<rspec>, [">= 0"])
|
131
|
-
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
132
|
-
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
133
|
-
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
134
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
135
131
|
else
|
136
|
-
s.add_dependency(%q<rails>, ["= 3.1.3"])
|
137
|
-
s.add_dependency(%q<activerecord>, ["= 3.1.3"])
|
138
|
-
s.add_dependency(%q<activesupport>, ["= 3.1.3"])
|
139
|
-
s.add_dependency(%q<jruby-openssl>, [">= 0"])
|
140
|
-
s.add_dependency(%q<activerecord-jdbcsqlite3-adapter>, [">= 0"])
|
141
|
-
s.add_dependency(%q<spree>, ["= 0.70.3"])
|
142
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
143
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
144
|
-
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
145
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
146
|
-
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
147
132
|
end
|
148
133
|
else
|
149
|
-
s.add_dependency(%q<rails>, ["= 3.1.3"])
|
150
|
-
s.add_dependency(%q<activerecord>, ["= 3.1.3"])
|
151
|
-
s.add_dependency(%q<activesupport>, ["= 3.1.3"])
|
152
|
-
s.add_dependency(%q<jruby-openssl>, [">= 0"])
|
153
|
-
s.add_dependency(%q<activerecord-jdbcsqlite3-adapter>, [">= 0"])
|
154
|
-
s.add_dependency(%q<spree>, ["= 0.70.3"])
|
155
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
156
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
157
|
-
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
158
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
159
|
-
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
160
134
|
end
|
161
135
|
end
|
162
136
|
|
@@ -212,25 +212,37 @@ if(DataShift::Guards::jruby?)
|
|
212
212
|
end
|
213
213
|
|
214
214
|
# Save data from an AR record to the current row, based on the record's columns [c1,c2,c3]
|
215
|
-
|
215
|
+
# Returns the number of the final column written to
|
216
216
|
def ar_to_xls_row(start_column, record)
|
217
217
|
return unless( record.is_a?(ActiveRecord::Base))
|
218
218
|
|
219
|
+
column = start_column
|
219
220
|
record.class.columns.each do |connection_column|
|
220
|
-
ar_to_xls_cell(
|
221
|
-
|
221
|
+
ar_to_xls_cell(column, record, connection_column)
|
222
|
+
column += 1
|
222
223
|
end
|
224
|
+
column
|
223
225
|
end
|
224
226
|
|
225
227
|
def ar_to_xls_cell(column, record, connection_column)
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
228
|
+
begin
|
229
|
+
datum = record.send(connection_column.name)
|
230
|
+
|
231
|
+
if(connection_column.sql_type =~ /date/)
|
232
|
+
@row.createCell(column - 1, HSSFCell::CELL_TYPE_STRING).setCellValue(datum.to_s)
|
233
|
+
|
234
|
+
elsif(connection_column.type == :boolean || connection_column.sql_type =~ /tinyint/)
|
235
|
+
@row.createCell(column - 1, HSSFCell::CELL_TYPE_BOOLEAN).setCellValue(datum)
|
236
|
+
|
237
|
+
elsif(connection_column.sql_type =~ /int/)
|
238
|
+
@row.createCell(column - 1, HSSFCell::CELL_TYPE_NUMERIC).setCellValue(datum.to_i)
|
239
|
+
else
|
240
|
+
@row.createCell(column - 1, HSSFCell::CELL_TYPE_STRING).setCellValue( datum.to_s )
|
241
|
+
end
|
242
|
+
|
243
|
+
rescue => e
|
244
|
+
puts "Failed to export #{datum} from #{connection_column.inspect} to column #{column}"
|
245
|
+
puts e
|
234
246
|
end
|
235
247
|
end
|
236
248
|
|
@@ -14,6 +14,38 @@ require 'to_b'
|
|
14
14
|
|
15
15
|
module DataShift
|
16
16
|
|
17
|
+
# Stores MethodDetails for a class mapped by type
|
18
|
+
class MethodDetailsManager
|
19
|
+
|
20
|
+
attr_reader :method_details
|
21
|
+
|
22
|
+
def initialize( klass )
|
23
|
+
@parent_class = klass
|
24
|
+
@method_details = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(method_details)
|
28
|
+
@method_details[method_details.operator_type] ||= {}
|
29
|
+
@method_details[method_details.operator_type][method_details.name] = method_details
|
30
|
+
end
|
31
|
+
|
32
|
+
def <<(method_details)
|
33
|
+
add(method_details)
|
34
|
+
end
|
35
|
+
|
36
|
+
def find(name, type)
|
37
|
+
method_details = get(type)
|
38
|
+
|
39
|
+
method_details ? method_details[name] : nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# type is expected to be one of MethodDetail::type_enum
|
43
|
+
def get( type )
|
44
|
+
@method_details[type]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
17
49
|
class MethodDetail
|
18
50
|
|
19
51
|
def self.type_enum
|
@@ -30,14 +62,21 @@ module DataShift
|
|
30
62
|
|
31
63
|
attr_reader :operator, :operator_type
|
32
64
|
|
33
|
-
#
|
65
|
+
# TODO make it a list/primary keys
|
66
|
+
attr_accessor :find_by_operator
|
67
|
+
|
68
|
+
# Store the raw (client supplied) name against the active record klass(model).
|
69
|
+
# Operator is the associated method call on klass,
|
70
|
+
# so client name maybe Price but true operator is price
|
71
|
+
#
|
34
72
|
# col_types can typically be derived from klass.columns - set of ActiveRecord::ConnectionAdapters::Column
|
35
73
|
|
36
|
-
def initialize(client_name, klass, operator, type, col_types = {} )
|
74
|
+
def initialize(client_name, klass, operator, type, col_types = {}, find_by_operator = nil )
|
37
75
|
@klass, @name = klass, client_name
|
76
|
+
@find_by_operator = find_by_operator
|
38
77
|
|
39
78
|
if( MethodDetail::type_enum.member?(type.to_sym) )
|
40
|
-
@operator_type = type
|
79
|
+
@operator_type = type.to_sym
|
41
80
|
else
|
42
81
|
raise "Bad operator Type #{type} passed to Method Detail"
|
43
82
|
end
|
@@ -57,7 +96,7 @@ module DataShift
|
|
57
96
|
# Return the actual operator's name for supplied method type
|
58
97
|
# where type one of :assignment, :has_one, :belongs_to, :has_many etc
|
59
98
|
def operator_for( type )
|
60
|
-
return operator if(@operator_type == type)
|
99
|
+
return operator if(@operator_type == type.to_sym)
|
61
100
|
nil
|
62
101
|
end
|
63
102
|
|
@@ -105,7 +144,7 @@ module DataShift
|
|
105
144
|
|
106
145
|
@current_value = value
|
107
146
|
|
108
|
-
|
147
|
+
# logger.info("WARNING nil value supplied for Column [#{@name}]") if(@current_value.nil?)
|
109
148
|
|
110
149
|
if( operator_for(:belongs_to) )
|
111
150
|
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# Copyright:: (c) Autotelik Media Ltd 2012
|
2
|
+
# Author :: Tom Statter
|
3
|
+
# Date :: Aug 2010
|
4
|
+
# License:: MIT
|
5
|
+
#
|
6
|
+
# Details:: A cache type class that stores details of all possible associations on AR classes.
|
7
|
+
#
|
8
|
+
require 'method_detail'
|
9
|
+
|
10
|
+
module DataShift
|
11
|
+
|
12
|
+
class MethodDictionary
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
# Create simple picture of all the operator names for assignment available on an AR model,
|
19
|
+
# grouped by type of association (includes belongs_to and has_many which provides both << and = )
|
20
|
+
# Options:
|
21
|
+
# :reload => clear caches and re-perform lookup
|
22
|
+
# :instance_methods => if true include instance method type assignment operators as well as model's pure columns
|
23
|
+
#
|
24
|
+
def self.find_operators(klass, options = {} )
|
25
|
+
|
26
|
+
# Find the has_many associations which can be populated via <<
|
27
|
+
if( options[:reload] || has_many[klass].nil? )
|
28
|
+
has_many[klass] = klass.reflect_on_all_associations(:has_many).map { |i| i.name.to_s }
|
29
|
+
klass.reflect_on_all_associations(:has_and_belongs_to_many).inject(has_many[klass]) { |x,i| x << i.name.to_s }
|
30
|
+
end
|
31
|
+
# puts "DEBUG: Has Many Associations:", has_many[klass].inspect
|
32
|
+
|
33
|
+
# Find the belongs_to associations which can be populated via Model.belongs_to_name = OtherArModelObject
|
34
|
+
if( options[:reload] || belongs_to[klass].nil? )
|
35
|
+
belongs_to[klass] = klass.reflect_on_all_associations(:belongs_to).map { |i| i.name.to_s }
|
36
|
+
end
|
37
|
+
|
38
|
+
#puts "Belongs To Associations:", belongs_to[klass].inspect
|
39
|
+
|
40
|
+
# Find the has_one associations which can be populated via Model.has_one_name = OtherArModelObject
|
41
|
+
if( options[:reload] || self.has_one[klass].nil? )
|
42
|
+
has_one[klass] = klass.reflect_on_all_associations(:has_one).map { |i| i.name.to_s }
|
43
|
+
end
|
44
|
+
|
45
|
+
#puts "has_one Associations:", self.has_one[klass].inspect
|
46
|
+
|
47
|
+
# Find the model's column associations which can be populated via xxxxxx= value
|
48
|
+
# Note, not all reflections return method names in same style so we convert all to
|
49
|
+
# the raw form i.e without the '=' for consistency
|
50
|
+
if( options[:reload] || assignments[klass].nil? )
|
51
|
+
|
52
|
+
assignments[klass] = klass.column_names
|
53
|
+
|
54
|
+
if(options[:instance_methods] == true)
|
55
|
+
setters = klass.instance_methods.grep(/\w+=/).collect {|x| x.to_s }
|
56
|
+
|
57
|
+
if(klass.respond_to? :defined_activerecord_methods)
|
58
|
+
setters = setters - klass.defined_activerecord_methods.to_a
|
59
|
+
end
|
60
|
+
|
61
|
+
# get into same format as other names
|
62
|
+
assignments[klass] += setters.map{|i| i.gsub(/=/, '')}
|
63
|
+
end
|
64
|
+
|
65
|
+
assignments[klass] -= has_many[klass] if(has_many[klass])
|
66
|
+
assignments[klass] -= belongs_to[klass] if(belongs_to[klass])
|
67
|
+
assignments[klass] -= self.has_one[klass] if(self.has_one[klass])
|
68
|
+
|
69
|
+
assignments[klass].uniq!
|
70
|
+
|
71
|
+
assignments[klass].each do |assign|
|
72
|
+
column_types[klass] ||= {}
|
73
|
+
column_def = klass.columns.find{ |col| col.name == assign }
|
74
|
+
column_types[klass].merge!( assign => column_def) if column_def
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# Build a thorough and usable picture of the operators by building dictionary of our MethodDetail
|
81
|
+
# objects which can be used to import/export data to objects of type 'klass'
|
82
|
+
#
|
83
|
+
def self.build_method_details( klass )
|
84
|
+
method_details_mgr = MethodDetailsManager.new( klass )
|
85
|
+
|
86
|
+
assignments_for(klass).each do |n|
|
87
|
+
method_details_mgr << MethodDetail.new(n, klass, n, :assignment, column_types[klass])
|
88
|
+
end
|
89
|
+
|
90
|
+
has_one_for(klass).each do |n|
|
91
|
+
method_details_mgr << MethodDetail.new(n, klass, n, :has_one)
|
92
|
+
end
|
93
|
+
|
94
|
+
has_many_for(klass).each do |n|
|
95
|
+
method_details_mgr << MethodDetail.new(n, klass, n, :has_many)
|
96
|
+
end
|
97
|
+
|
98
|
+
belongs_to_for(klass).each do |n|
|
99
|
+
method_details_mgr << MethodDetail.new(n, klass, n, :belongs_to)
|
100
|
+
end
|
101
|
+
|
102
|
+
method_details_mgrs[klass] = method_details_mgr
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# Find the proper format of name, appropriate call + column type for a given name.
|
107
|
+
# e.g Given users entry in spread sheet check for pluralization, missing underscores etc
|
108
|
+
#
|
109
|
+
# If not nil, returned method can be used directly in for example klass.new.send( call, .... )
|
110
|
+
#
|
111
|
+
def self.find_method_detail( klass, external_name )
|
112
|
+
operator = nil
|
113
|
+
|
114
|
+
# TODO - should we raise error to warn find_operators never called ?
|
115
|
+
md_mgr = method_details_mgrs[klass] || MethodDetailsManager.new( klass )
|
116
|
+
|
117
|
+
name = external_name.to_s
|
118
|
+
|
119
|
+
# TODO - check out regexp to do this work better plus Inflections ??
|
120
|
+
# Want to be able to handle any of ["Count On hand", 'count_on_hand', "Count OnHand", "COUNT ONHand" etc]
|
121
|
+
[
|
122
|
+
name,
|
123
|
+
name.tableize,
|
124
|
+
name.gsub(' ', '_'),
|
125
|
+
name.gsub(' ', '_').downcase,
|
126
|
+
name.gsub(/(\s+)/, '_').downcase,
|
127
|
+
name.gsub(' ', ''),
|
128
|
+
name.gsub(' ', '').downcase,
|
129
|
+
name.gsub(' ', '_').underscore].each do |n|
|
130
|
+
|
131
|
+
# Try each association type, returning first that contains matching operator with name n
|
132
|
+
|
133
|
+
MethodDetail::type_enum.each do |t|
|
134
|
+
method_detail = md_mgr.find(n, t)
|
135
|
+
return method_detail if(method_detail)
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.clear
|
144
|
+
belongs_to.clear
|
145
|
+
has_many.clear
|
146
|
+
assignments.clear
|
147
|
+
column_types.clear
|
148
|
+
has_one.clear
|
149
|
+
method_details_mgrs.clear
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.column_key(klass, column)
|
153
|
+
"#{klass.name}:#{column}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.method_details_mgrs
|
157
|
+
@method_details_mgrs ||= {}
|
158
|
+
@method_details_mgrs
|
159
|
+
end
|
160
|
+
|
161
|
+
# TODO - remove use of class variables - not good Ruby design
|
162
|
+
def self.belongs_to
|
163
|
+
@belongs_to ||={}
|
164
|
+
@belongs_to
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.has_many
|
168
|
+
@has_many ||= {}
|
169
|
+
@has_many
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.has_one
|
173
|
+
@has_one ||= {}
|
174
|
+
@has_one
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.assignments
|
178
|
+
@assignments ||= {}
|
179
|
+
@assignments
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.column_types
|
183
|
+
@column_types ||= {}
|
184
|
+
@column_types
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def self.belongs_to_for(klass)
|
189
|
+
belongs_to[klass] || []
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.has_many_for(klass)
|
193
|
+
has_many[klass] || []
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.has_one_for(klass)
|
197
|
+
has_one[klass] || []
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.assignments_for(klass)
|
201
|
+
assignments[klass] || []
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.column_type_for(klass, column)
|
205
|
+
column_types[klass] ? column_types[klass][column] : []
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|