ar_loader 0.0.4
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 -0
- data/README.markdown +211 -0
- data/Rakefile +76 -0
- data/lib/VERSION +1 -0
- data/lib/ar_loader.rb +53 -0
- data/lib/engine/file_definitions.rb +353 -0
- data/lib/engine/jruby/jexcel_file.rb +182 -0
- data/lib/engine/jruby/method_mapper_excel.rb +44 -0
- data/lib/engine/mapping_file_definitions.rb +88 -0
- data/lib/engine/method_detail.rb +139 -0
- data/lib/engine/method_mapper.rb +157 -0
- data/lib/engine/method_mapper_csv.rb +28 -0
- data/lib/engine/word.rb +70 -0
- data/lib/java/poi-3.2-FINAL-20081019.jar +0 -0
- data/lib/java/poi-3.6.jar +0 -0
- data/lib/java/poi-contrib-3.6-20091214.jar +0 -0
- data/lib/java/poi-examples-3.6-20091214.jar +0 -0
- data/lib/java/poi-ooxml-3.6-20091214.jar +0 -0
- data/lib/java/poi-ooxml-schemas-3.6-20091214.jar +0 -0
- data/lib/java/poi-scratchpad-3.6-20091214.jar +0 -0
- data/lib/loaders/loader_base.rb +61 -0
- data/lib/loaders/spree/image_loader.rb +47 -0
- data/lib/loaders/spree/product_loader.rb +93 -0
- data/lib/to_b.rb +24 -0
- data/spec/excel_loader_spec.rb +138 -0
- data/spec/spec_helper.rb +37 -0
- data/tasks/db_tasks.rake +65 -0
- data/tasks/excel_loader.rake +101 -0
- data/tasks/file_tasks.rake +38 -0
- data/tasks/seed_fu_product_template.erb +15 -0
- data/tasks/spree/image_load.rake +103 -0
- data/tasks/spree/product_loader.rake +107 -0
- data/tasks/tidy_config.txt +13 -0
- data/tasks/word_to_seedfu.rake +167 -0
- metadata +90 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
+
# Author :: Tom Statter
|
3
|
+
# Date :: Aug 2010
|
4
|
+
# License:: MIT
|
5
|
+
#
|
6
|
+
# An Excel file helper. Create and populate XSL files
|
7
|
+
#
|
8
|
+
# The maximum number of columns and rows in an Excel file is fixed at 256 Columns and 65536 Rows
|
9
|
+
#
|
10
|
+
# POI jar location needs to be added to class path.
|
11
|
+
#
|
12
|
+
# TODO - Check out http://poi.apache.org/poi-ruby.html
|
13
|
+
#
|
14
|
+
class Object
|
15
|
+
def add_to_classpath(path)
|
16
|
+
$CLASSPATH << File.join( ArLoader.root_path, 'lib', path.gsub("\\", "/") )
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'java'
|
21
|
+
require 'rubygems'
|
22
|
+
|
23
|
+
add_to_classpath 'java/poi-3.6.jar'
|
24
|
+
|
25
|
+
class JExcelFile
|
26
|
+
include_class 'org.apache.poi.poifs.filesystem.POIFSFileSystem'
|
27
|
+
include_class 'org.apache.poi.hssf.usermodel.HSSFCell'
|
28
|
+
include_class 'org.apache.poi.hssf.usermodel.HSSFWorkbook'
|
29
|
+
include_class 'org.apache.poi.hssf.usermodel.HSSFCellStyle'
|
30
|
+
include_class 'org.apache.poi.hssf.usermodel.HSSFDataFormat'
|
31
|
+
|
32
|
+
include_class 'java.io.ByteArrayOutputStream'
|
33
|
+
include_class 'java.util.Date'
|
34
|
+
include_class 'java.io.FileInputStream'
|
35
|
+
|
36
|
+
attr_accessor :book, :row, :current_sheet
|
37
|
+
|
38
|
+
attr_reader :sheet
|
39
|
+
|
40
|
+
MAX_COLUMNS = 256.freeze
|
41
|
+
MAX_ROWS = 65536.freeze
|
42
|
+
|
43
|
+
# The HSSFWorkbook uses 0 based indexes
|
44
|
+
|
45
|
+
def initialize()
|
46
|
+
@book = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def open(filename)
|
50
|
+
inp = FileInputStream.new(filename)
|
51
|
+
|
52
|
+
@book = HSSFWorkbook.new(inp)
|
53
|
+
|
54
|
+
sheet(0) # also sets @current_sheet
|
55
|
+
end
|
56
|
+
|
57
|
+
def create(sheet_name)
|
58
|
+
@book = HSSFWorkbook.new()
|
59
|
+
@sheet = @book.createSheet(sheet_name.gsub(" ", ''))
|
60
|
+
date_style = @book.createCellStyle()
|
61
|
+
date_style.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy h:mm"))
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return the current or specified HSSFSheet
|
65
|
+
def sheet(i = nil)
|
66
|
+
@current_sheet = i if i
|
67
|
+
@sheet = @book.getSheetAt(@current_sheet)
|
68
|
+
@sheet
|
69
|
+
end
|
70
|
+
|
71
|
+
def num_rows
|
72
|
+
@sheet.getPhysicalNumberOfRows
|
73
|
+
end
|
74
|
+
|
75
|
+
# Process each row. (type is org.apache.poi.hssf.usermodel.HSSFRow)
|
76
|
+
|
77
|
+
def each_row
|
78
|
+
@sheet.rowIterator.each { |row| yield row }
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Create new row, bring index in line with POI usage (our 1 is their 0)
|
83
|
+
def create_row(index)
|
84
|
+
@row = @sheet.createRow(index)
|
85
|
+
@row
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_cell(row, column, data)
|
89
|
+
@row = @sheet.getRow(row) || create_row(row)
|
90
|
+
@row.createCell(column).setCellValue(data)
|
91
|
+
end
|
92
|
+
|
93
|
+
def value(row, column)
|
94
|
+
raise TypeError, "Expect row argument of type HSSFRow" unless row.is_a?(Java::OrgApachePoiHssfUsermodel::HSSFRow)
|
95
|
+
#puts "DEBUG - CELL VALUE : #{column} => #{ cell_value( row.getCell(column) ).inspect}"
|
96
|
+
cell_value( row.getCell(column) )
|
97
|
+
end
|
98
|
+
|
99
|
+
def cell_value(cell)
|
100
|
+
return nil unless cell
|
101
|
+
#puts "DEBUG CELL TYPE : #{cell} => #{cell.getCellType().inspect}"
|
102
|
+
case (cell.getCellType())
|
103
|
+
when HSSFCell::CELL_TYPE_FORMULA then return cell.getCellFormula()
|
104
|
+
when HSSFCell::CELL_TYPE_NUMERIC then return cell.getNumericCellValue()
|
105
|
+
when HSSFCell::CELL_TYPE_STRING then return cell.getStringCellValue()
|
106
|
+
when HSSFCell::CELL_TYPE_BOOLEAN then return cell.getBooleanCellValue()
|
107
|
+
when HSSFCell::CELL_TYPE_BLANK then return ""
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def save( filename )
|
112
|
+
File.open( filename, 'w') {|f| f.write(to_s) }
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# The internal representation of a Excel File
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
outs = ByteArrayOutputStream.new
|
120
|
+
@book.write(outs);
|
121
|
+
outs.close();
|
122
|
+
String.from_java_bytes(outs.toByteArray)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
module ExcelHelper
|
128
|
+
require 'java'
|
129
|
+
|
130
|
+
include_class 'org.apache.poi.poifs.filesystem.POIFSFileSystem'
|
131
|
+
include_class 'org.apache.poi.hssf.usermodel.HSSFCell'
|
132
|
+
include_class 'org.apache.poi.hssf.usermodel.HSSFWorkbook'
|
133
|
+
include_class 'org.apache.poi.hssf.usermodel.HSSFCellStyle'
|
134
|
+
include_class 'org.apache.poi.hssf.usermodel.HSSFDataFormat'
|
135
|
+
include_class 'java.io.ByteArrayOutputStream'
|
136
|
+
include_class 'java.util.Date'
|
137
|
+
|
138
|
+
# ActiveRecord Helper - Export model data to XLS file format
|
139
|
+
#
|
140
|
+
def to_xls(items=[])
|
141
|
+
|
142
|
+
@excel = ExcelFile.new(items[0].class.name)
|
143
|
+
|
144
|
+
@excel.create_row(0)
|
145
|
+
|
146
|
+
sheet = @excel.sheet
|
147
|
+
|
148
|
+
# header row
|
149
|
+
if !items.empty?
|
150
|
+
row = sheet.createRow(0)
|
151
|
+
cell_index = 0
|
152
|
+
items[0].class.columns.each do |column|
|
153
|
+
row.createCell(cell_index).setCellValue(column.name)
|
154
|
+
cell_index += 1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# value rows
|
159
|
+
row_index = 1
|
160
|
+
items.each do |item|
|
161
|
+
row = sheet.createRow(row_index);
|
162
|
+
|
163
|
+
cell_index = 0
|
164
|
+
item.class.columns.each do |column|
|
165
|
+
cell = row.createCell(cell_index)
|
166
|
+
if column.sql_type =~ /date/ then
|
167
|
+
millis = item.send(column.name).to_f * 1000
|
168
|
+
cell.setCellValue(Date.new(millis))
|
169
|
+
cell.setCellStyle(dateStyle);
|
170
|
+
elsif column.sql_type =~ /int/ then
|
171
|
+
cell.setCellValue(item.send(column.name).to_i)
|
172
|
+
else
|
173
|
+
value = item.send(column.name)
|
174
|
+
cell.setCellValue(item.send(column.name)) unless value.nil?
|
175
|
+
end
|
176
|
+
cell_index += 1
|
177
|
+
end
|
178
|
+
row_index += 1
|
179
|
+
end
|
180
|
+
@excel.to_s
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
+
# Author :: Tom Statter
|
3
|
+
# Date :: Jan 2011
|
4
|
+
# License:: MIT
|
5
|
+
#
|
6
|
+
# JAVA SPECIFIC LOAD
|
7
|
+
require 'java'
|
8
|
+
require 'rubygems'
|
9
|
+
require 'jexcel_file'
|
10
|
+
require 'method_mapper'
|
11
|
+
|
12
|
+
class MethodMapperExcel < MethodMapper
|
13
|
+
|
14
|
+
attr_accessor :excel, :sheet
|
15
|
+
|
16
|
+
# Read the headers from a spreadsheet and map to ActiveRecord members/associations
|
17
|
+
|
18
|
+
def initialize( file_name, klass, sheet_number = 0 )
|
19
|
+
super()
|
20
|
+
|
21
|
+
@excel = JExcelFile.new
|
22
|
+
|
23
|
+
@excel.open(file_name)
|
24
|
+
|
25
|
+
@sheet = @excel.sheet( sheet_number )
|
26
|
+
|
27
|
+
@header_row = @sheet.getRow(0)
|
28
|
+
|
29
|
+
raise "ERROR: No headers found - Check Sheet #{@sheet} is completed sheet and Row 1 contains headers" unless @header_row
|
30
|
+
|
31
|
+
@headers = []
|
32
|
+
(0..JExcelFile::MAX_COLUMNS).each do |i|
|
33
|
+
cell = @header_row.getCell(i)
|
34
|
+
break unless cell
|
35
|
+
@headers << "#{@excel.cell_value(cell).to_s}".strip
|
36
|
+
end
|
37
|
+
|
38
|
+
# Gather list of all possible 'setter' methods on AR class (instance variables and associations)
|
39
|
+
MethodMapperExcel.find_operators( klass )
|
40
|
+
|
41
|
+
# Convert the list of headers into suitable calls on the Active Record class
|
42
|
+
find_method_details( klass, @headers )
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +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
|
+
|
88
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
+
# Author :: Tom Statter
|
3
|
+
# Date :: Aug 2010
|
4
|
+
# License:: MIT
|
5
|
+
#
|
6
|
+
# Details:: This class provides information and access to the individual methods
|
7
|
+
# on an AR model. Populated by, and coupled with MethodMapper,
|
8
|
+
# which does the model interrogation work.
|
9
|
+
# Enables 'loaders' to iterate over the MethodMapper results set,
|
10
|
+
# and assign values to AR object, without knowing anything about that receiving object.
|
11
|
+
#
|
12
|
+
# =>
|
13
|
+
require 'to_b'
|
14
|
+
|
15
|
+
class MethodDetail
|
16
|
+
|
17
|
+
# When looking up an association, try each of these in turn till a match
|
18
|
+
# i.e find_by_name .. find_by_title and so on
|
19
|
+
@@insistent_find_by_list ||= [:id, :name, :title]
|
20
|
+
|
21
|
+
attr_accessor :klass, :name, :assignment, :col_type
|
22
|
+
attr_accessor :has_many, :has_many_class_name, :has_many_class
|
23
|
+
attr_accessor :belongs_to, :belongs_to_class_name, :belongs_to_class
|
24
|
+
|
25
|
+
@@default_values = {}
|
26
|
+
@@prefixes = {}
|
27
|
+
|
28
|
+
|
29
|
+
def initialize(klass, name, assignment, belongs_to, has_many, col_type = nil)
|
30
|
+
@klass, @name, @assignment, @has_many, @belongs_to, @col_type = klass, name, assignment, has_many, belongs_to, col_type
|
31
|
+
|
32
|
+
if(@has_many)
|
33
|
+
begin
|
34
|
+
@has_many_class = Kernel.const_get(@has_many.classify)
|
35
|
+
@has_many_class_name = @has_many.classify
|
36
|
+
rescue
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if(@belongs_to)
|
41
|
+
begin
|
42
|
+
@belongs_to_class = Kernel.const_get(@belongs_to.classify)
|
43
|
+
@belongs_to_class_name = @belongs_to.classify
|
44
|
+
rescue
|
45
|
+
# TODO - try other forms of the name, set to nil, or bomb out ?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def assign( record, value )
|
51
|
+
#puts "DEBUG: assign: [#{@name}]"
|
52
|
+
|
53
|
+
data = value
|
54
|
+
|
55
|
+
if(@@default_values[@name])
|
56
|
+
puts "WARNING nil value supplied for [#{@name}] - Using default : [#{@@default_values[@name]}]"
|
57
|
+
data = @@default_values[@name]
|
58
|
+
else
|
59
|
+
puts "WARNING nil value supplied for [#{@name}] - No default"
|
60
|
+
end if(data.nil?)
|
61
|
+
|
62
|
+
data = "#{@@prefixes[@name]}#{data}" if(@@prefixes[@name])
|
63
|
+
|
64
|
+
if( @belongs_to )
|
65
|
+
|
66
|
+
#puts "DEBUG : BELONGS_TO #{@belongs_to} - Lookup #{data} in DB"
|
67
|
+
insistent_belongs_to(record, data)
|
68
|
+
|
69
|
+
elsif( @assignment && @col_type )
|
70
|
+
#puts "DEBUG : COl TYPE defined for #{@name} : #{@assignment} => #{data} #{@col_type.inspect}"
|
71
|
+
record.send( @assignment, @col_type.type_cast( data ) )
|
72
|
+
|
73
|
+
elsif( @assignment )
|
74
|
+
#puts "DEBUG : No COL TYPE found for #{@name} : #{@assignment} => #{data}"
|
75
|
+
insistent_assignment(record, data)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Attempt to find the associated object via id, name, title ....
|
80
|
+
def insistent_belongs_to( record, value )
|
81
|
+
|
82
|
+
@@insistent_find_by_list.each do |x|
|
83
|
+
begin
|
84
|
+
item = @belongs_to_class.send( "find_by_#{x}", value)
|
85
|
+
if(item)
|
86
|
+
record.send("#{@belongs_to}=", item)
|
87
|
+
break
|
88
|
+
end
|
89
|
+
rescue => e
|
90
|
+
puts e.inspect
|
91
|
+
if(x == @@insistent_method_list.last)
|
92
|
+
puts "I'm sorry I have failed to assign [#{value}] to #{@assignment}"
|
93
|
+
raise "I'm sorry I have failed to assign [#{value}] to #{@assignment}" unless value.nil?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def insistent_assignment( record, value )
|
100
|
+
@@insistent_method_list ||= [:to_i, :to_f, :to_b]
|
101
|
+
begin
|
102
|
+
record.send(@assignment, value)
|
103
|
+
rescue => e
|
104
|
+
puts e.inspect
|
105
|
+
@@insistent_method_list.each do |f|
|
106
|
+
begin
|
107
|
+
record.send(@assignment, value.send( f) )
|
108
|
+
break
|
109
|
+
rescue => e
|
110
|
+
#puts "DEBUG: insistent_assignment: #{e.inspect}"
|
111
|
+
if f == @@insistent_method_list.last
|
112
|
+
puts "I'm sorry I have failed to assign [#{value}] to #{@assignment}"
|
113
|
+
raise "I'm sorry I have failed to assign [#{value}] to #{@assignment}" unless value.nil?
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.set_default_value( name, value )
|
121
|
+
@@default_values[name] = value
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.default_value(name)
|
125
|
+
@@default_values[name]
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.set_prefix( name, value )
|
129
|
+
@@prefixes[name] = value
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.default_value(name)
|
133
|
+
@@prefixes[name]
|
134
|
+
end
|
135
|
+
|
136
|
+
def pp
|
137
|
+
"#{@name} => #{@assignment} : #{@has_many}"
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,157 @@
|
|
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
|
+
class MethodMapper
|
21
|
+
|
22
|
+
attr_accessor :header_row, :headers
|
23
|
+
attr_accessor :methods
|
24
|
+
|
25
|
+
@@has_many = Hash.new
|
26
|
+
@@belongs_to = Hash.new
|
27
|
+
@@assignments = Hash.new
|
28
|
+
@@column_types = Hash.new
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@methods = []
|
32
|
+
@headers = []
|
33
|
+
end
|
34
|
+
|
35
|
+
# Build complete picture of the methods whose names listed in method_list
|
36
|
+
# Handles method names as defined by a user or in file headers where names may
|
37
|
+
# not be exactly as required e.g handles capitalisation, white space, _ etc
|
38
|
+
|
39
|
+
def find_method_details( klass, method_list )
|
40
|
+
@methods = method_list.collect { |x| MethodMapper::find_method_detail( klass, x ) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_names()
|
44
|
+
@methods.collect( &:name )
|
45
|
+
end
|
46
|
+
|
47
|
+
def check_mandatory( mandatory_list )
|
48
|
+
method_list = method_names()
|
49
|
+
|
50
|
+
mandatory_list.each { |x| raise "Mandatory column missing - need a '#{x}' column" unless(method_list.index(x)) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create picture of the operators for assignment available on an AR model,
|
54
|
+
# including via associations (which provide both << and = )
|
55
|
+
#
|
56
|
+
def self.find_operators(klass, options = {} )
|
57
|
+
|
58
|
+
if( options[:reload] || @@has_many[klass].nil? )
|
59
|
+
@@has_many[klass] = klass.reflect_on_all_associations(:has_many).map { |i| i.name.to_s }
|
60
|
+
klass.reflect_on_all_associations(:has_and_belongs_to_many).inject(@@has_many[klass]) { |x,i| x << i.name.to_s }
|
61
|
+
end
|
62
|
+
|
63
|
+
# puts "DEBUG: Has Many Associations:", @@has_many[klass].inspect
|
64
|
+
|
65
|
+
if( options[:reload] || @@belongs_to[klass].nil? )
|
66
|
+
@@belongs_to[klass] = klass.reflect_on_all_associations(:belongs_to).map { |i| i.name.to_s }
|
67
|
+
end
|
68
|
+
|
69
|
+
# puts "DEBUG: Belongs To Associations:", @@belongs_to[klass].inspect
|
70
|
+
|
71
|
+
if( options[:reload] || @@assignments[klass].nil? )
|
72
|
+
@@assignments[klass] = (klass.column_names + klass.instance_methods.grep(/=/).map{|i| i.gsub(/=/, '')})
|
73
|
+
@@assignments[klass] = @@assignments[klass] - @@has_many[klass] if(@@has_many[klass])
|
74
|
+
@@assignments[klass] = @@assignments[klass] - @@belongs_to[klass] if(@@belongs_to[klass])
|
75
|
+
|
76
|
+
@@assignments[klass].uniq!
|
77
|
+
|
78
|
+
@@assignments[klass].each do |assign|
|
79
|
+
found = klass.columns.find{ |col| col.name == assign }
|
80
|
+
@@column_types[column_key(klass, assign)] = found if found
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Find the proper format of name, appropriate call + column type for a given name.
|
86
|
+
# e.g Given users entry in spread sheet check for pluralization, missing underscores etc
|
87
|
+
#
|
88
|
+
# If not nil returned method can be used directly in for example klass.new.send( call, .... )
|
89
|
+
#
|
90
|
+
def self.find_method_detail( klass, name )
|
91
|
+
true_name, assign, belongs_to, has_many = nil, nil, nil, nil
|
92
|
+
|
93
|
+
# TODO - check out regexp to do this work better plus Inflections ??
|
94
|
+
[
|
95
|
+
name,
|
96
|
+
name.gsub(' ', '_'),
|
97
|
+
name.gsub(' ', ''),
|
98
|
+
name.gsub(' ', '_').downcase,
|
99
|
+
name.gsub(' ', '').downcase,
|
100
|
+
name.gsub(' ', '_').underscore
|
101
|
+
|
102
|
+
].each do |n|
|
103
|
+
has_many = (@@has_many[klass] && @@has_many[klass].include?(n)) ? n : nil
|
104
|
+
belongs_to = (@@belongs_to[klass] && @@belongs_to[klass].include?(n)) ? n : nil
|
105
|
+
assign = (@@assignments[klass] && @@assignments[klass].include?(n))? n + '=' : nil
|
106
|
+
|
107
|
+
if(assign || has_many || belongs_to)
|
108
|
+
true_name = n
|
109
|
+
break
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
return MethodDetail.new(klass, true_name, assign, belongs_to, has_many, @@column_types[column_key(klass, true_name)])
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.clear
|
117
|
+
@@has_many.clear
|
118
|
+
@@assignments.clear
|
119
|
+
@@column_types.clear
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.column_key(klass, column)
|
123
|
+
"#{klass.name}:#{column}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.has_many
|
127
|
+
@@has_many
|
128
|
+
end
|
129
|
+
def self.assignments
|
130
|
+
@@assignments
|
131
|
+
end
|
132
|
+
def self.column_types
|
133
|
+
@@column_types
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.has_many_for(klass)
|
137
|
+
@@has_many[klass]
|
138
|
+
end
|
139
|
+
def self.assignments_for(klass)
|
140
|
+
@@assignments[klass]
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.column_type_for(klass, column)
|
144
|
+
@@column_types[column_key(klass, column)]
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
def find_or_new( klass, condition_hash = {} )
|
149
|
+
@records[klass] = klass.find(:all, :conditions => condition_hash)
|
150
|
+
if @records[klass].any?
|
151
|
+
return @records[klass].first
|
152
|
+
else
|
153
|
+
return klass.new
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Copyright:: (c) Autotelik Media Ltd 2011
|
2
|
+
# Author :: Tom Statter
|
3
|
+
# Date :: Jan 2011
|
4
|
+
# License:: MIT
|
5
|
+
#
|
6
|
+
# Details:: Extract the headings from a user supplied CSV file, and map heading names
|
7
|
+
# to the attributes and/or assocaiitons of an AR Model defined by supplied klass.
|
8
|
+
#
|
9
|
+
require 'method_mapper'
|
10
|
+
|
11
|
+
class MethodMapperCsv < MethodMapper
|
12
|
+
|
13
|
+
# Read the headers from CSV file and map to ActiveRecord members/associations
|
14
|
+
|
15
|
+
def initialize( file_name, klass, sheet_number = 0 )
|
16
|
+
super
|
17
|
+
|
18
|
+
File.open(file_name) do
|
19
|
+
@headers = @header_row.split(/,/)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Gather list of all possible 'setter' methods on AR class (instance variables and associations)
|
23
|
+
self.find_operators( klass )
|
24
|
+
|
25
|
+
# Convert the list of headers into suitable calls on the Active Record class
|
26
|
+
find_method_details( klass, @headers )
|
27
|
+
end
|
28
|
+
end
|
data/lib/engine/word.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Author:: Tom Statter
|
2
|
+
# License:: MIT ?
|
3
|
+
#
|
4
|
+
# NOTES ON INVESTIGATING OLE METHODS in irb
|
5
|
+
#
|
6
|
+
# visible = @word_app.ole_method_help( 'Visible' ) # Get a Method Object
|
7
|
+
|
8
|
+
# log( visible.return_type_detail.to_s ) # => ["BOOL"]
|
9
|
+
# log( visible.invoke_kind.to_s ) # => "PROPERTYGET"
|
10
|
+
# log( visible.params.to_s ) # => []
|
11
|
+
|
12
|
+
# @fc.ole_method_help( 'Report' ).params[1].ole_type_detail
|
13
|
+
#
|
14
|
+
# prefs = @word_app.Preferences.Strings.ole_method_help( 'Set' ).params
|
15
|
+
# => [index, newVal]
|
16
|
+
#
|
17
|
+
# WORD_OLE_CONST.constants
|
18
|
+
#
|
19
|
+
# WORD_OLE_CONST.constants.sort.grep /CR/
|
20
|
+
# => ["ClHideCRLF", "LesCR", "LesCRLF"]
|
21
|
+
#
|
22
|
+
# WORD_OLE_CONST.const_get( 'LesCR' ) or WORD_OLE_CONST::LesCR
|
23
|
+
# => 1
|
24
|
+
require 'win32ole'
|
25
|
+
|
26
|
+
# Module for constants to be loaded int
|
27
|
+
|
28
|
+
module WORD_OLE_CONST
|
29
|
+
end
|
30
|
+
|
31
|
+
class Word
|
32
|
+
|
33
|
+
attr_reader :wd, :doc
|
34
|
+
|
35
|
+
def initialize( visible )
|
36
|
+
@wd = WIN32OLE.new('Word.Application')
|
37
|
+
|
38
|
+
WIN32OLE.const_load(@wd, WORD_OLE_CONST) if WORD_OLE_CONST.constants.empty?
|
39
|
+
|
40
|
+
@wd.Visible = visible
|
41
|
+
end
|
42
|
+
|
43
|
+
def open(file)
|
44
|
+
@doc = @wd.Documents.Open(file)
|
45
|
+
@doc
|
46
|
+
end
|
47
|
+
|
48
|
+
def save()
|
49
|
+
@doc.Save()
|
50
|
+
@doc
|
51
|
+
end
|
52
|
+
|
53
|
+
# Format : From WORD_OLE_CONST e.g WORD_OLE_CONST::WdFormatHTML
|
54
|
+
#
|
55
|
+
def save_as(name, format)
|
56
|
+
@doc.SaveAs(name, format)
|
57
|
+
return @doc
|
58
|
+
end
|
59
|
+
|
60
|
+
# WdFormatFilteredHTML
|
61
|
+
# WdFormatHTML
|
62
|
+
def save_as_html(name)
|
63
|
+
@doc.SaveAs(name, WORD_OLE_CONST::WdFormatHTML)
|
64
|
+
return @doc
|
65
|
+
end
|
66
|
+
|
67
|
+
def quit
|
68
|
+
@wd.quit()
|
69
|
+
end
|
70
|
+
end
|
Binary file
|