from_excel 1.0.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.rdoc +168 -0
- data/lib/excel_import.rb +166 -0
- data/lib/from_excel.rb +5 -0
- data/test/helper.rb +10 -0
- data/test/model/adress.rb +17 -0
- data/test/model/role.rb +13 -0
- data/test/model/user.rb +21 -0
- data/test/test_bounds.rb +68 -0
- data/test/test_from_excel.rb +84 -0
- data/test/test_mapping.rb +55 -0
- metadata +82 -0
data/README.rdoc
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
== Overview
|
2
|
+
|
3
|
+
Import excel spreadsheet rows to ruby objects.
|
4
|
+
For how to use it, see examples below or see unit tests
|
5
|
+
|
6
|
+
== Install
|
7
|
+
|
8
|
+
gem install from_excel
|
9
|
+
|
10
|
+
== Dependencies
|
11
|
+
|
12
|
+
gem 'nokogiri'
|
13
|
+
gem 'roo'
|
14
|
+
gem 'rubyzip'
|
15
|
+
gem 'spreadsheet'
|
16
|
+
gem 'google-spreadsheet-ruby'
|
17
|
+
|
18
|
+
(also require 'active_support/inflector')
|
19
|
+
|
20
|
+
== Usage
|
21
|
+
|
22
|
+
data = Model.from_excel(spreadsheet_file, options)
|
23
|
+
|
24
|
+
Include module in your model : include ExcelImport
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
spreadsheet_file : File object as spreadsheet (ie :File.new ("path_to_spreadsheet"))
|
29
|
+
|
30
|
+
options :
|
31
|
+
:mapping => hash mapping attribute <=> <column name or column index>
|
32
|
+
:title => true|false . Pass false to indicate that columns have no title. True by default.
|
33
|
+
:limits => :start => [first_row, first_column], :end => [last_row, last_column]
|
34
|
+
:rules => Procs rules to convert data in cell sheet to desired data for object attribute.
|
35
|
+
For example, transform a string to association id (see example below)
|
36
|
+
|
37
|
+
|
38
|
+
<b>Note: If columns have title, by default the row of columns titles is supposed to be the first row before the first row
|
39
|
+
of data which will be convert to ruby object.</b>
|
40
|
+
|
41
|
+
== Basic spreadsheet
|
42
|
+
|
43
|
+
Suppose you have this kind of Excel sheet:
|
44
|
+
A B C
|
45
|
+
1 First name Last name Age
|
46
|
+
2 Albert Einstein 131
|
47
|
+
3 Leonard De Vinci 558
|
48
|
+
|
49
|
+
and a class:
|
50
|
+
|
51
|
+
User (:first_name, last_name, :age)
|
52
|
+
|
53
|
+
You can retrieve users, the simple way like that :
|
54
|
+
|
55
|
+
users = User.from_excel(File.new ("path_to_spreadsheet"))
|
56
|
+
|
57
|
+
You get theses users :
|
58
|
+
|
59
|
+
[User.new(:first_name => 'Albert', :last_name => 'Einstein', :age => 131),
|
60
|
+
User.new(:first_name => 'Leonard', :last_name => 'De Vinci', :age => 558),
|
61
|
+
User.new(:first_name => 'Martin', :last_name => 'Heidegger', :age => 121)]
|
62
|
+
|
63
|
+
By default, attributes model are retrieved from columns title with the following simple rule :
|
64
|
+
o spaces are converted to "_"
|
65
|
+
o words are downcased
|
66
|
+
|
67
|
+
First Name => :first_name
|
68
|
+
Last Name => :last_name
|
69
|
+
|
70
|
+
|
71
|
+
== Mapping attribute - column names
|
72
|
+
|
73
|
+
If a column name can't be simply mapped to attribute model, you can pass a map to.
|
74
|
+
If, with the last spreadsheet example, "First name" column had "Prenom" instead as title and you want this column
|
75
|
+
to map :first_name attribute of User model, do like this :
|
76
|
+
|
77
|
+
users = User.from_excel sheet, :mapping => {:first_name => 'Prenom'}
|
78
|
+
|
79
|
+
== Spreadsheet whithout columns title
|
80
|
+
|
81
|
+
Suppose your spreadsheet has no columns title.
|
82
|
+
You can then map columns <=> model attribute via index of column.
|
83
|
+
You can also pass the starting cell of data (the first cell of data (excluded title when there is one))
|
84
|
+
|
85
|
+
users = User.from_excel sheet, :mapping => {:first_name => 1, :last_name => 2, :age => 3}, :limits => {:start => [2, 1]}
|
86
|
+
|
87
|
+
== Limits : Extraction of part of the spreadsheet
|
88
|
+
|
89
|
+
If you want to extract just a part of the spreadsheet, just pass start and end limits.
|
90
|
+
users = User.from_excel(sheet, :limits => {:start => [3, 3], :end => [5, 5] })
|
91
|
+
|
92
|
+
<b>Note</b> : The row which contains the columns titles (for default attributes mapping) is, here, supposed to be begining
|
93
|
+
at [2,3] and end at [2,5] (one row before the first row of data).
|
94
|
+
If not, you must pass a mapping with :attribute => column_index
|
95
|
+
|
96
|
+
== Object associations handling
|
97
|
+
|
98
|
+
Suppose user has one adress of class Adress(:street, :town, :zip_code)
|
99
|
+
And you have this spreadsheet :
|
100
|
+
A B C D E F
|
101
|
+
1 First name Last name Age Street Town Zip Code
|
102
|
+
2 Albert Einstein 131 17 rue de Brest Quimper 29000
|
103
|
+
So, you want user with user.adress = Adress.new(:stret => ..., :town => ... , :zip_code => ....)
|
104
|
+
Just pass the following mapping :
|
105
|
+
|
106
|
+
mapping = {[:adress, :street] => 'Street', [:adress, :town] => 'Town', [:adress, :zip_code] => 'Zip Code'}
|
107
|
+
|
108
|
+
users = User.from_excel sheet, :mapping => mapping
|
109
|
+
|
110
|
+
And you will get :
|
111
|
+
|
112
|
+
users.first.first_name => 'Albert'
|
113
|
+
users.first.adress.street => '17 rue de Brest'
|
114
|
+
|
115
|
+
users.first.adress => Adress.new(:street => '17 rue de Brest', :town => 'Quimper' .....)
|
116
|
+
|
117
|
+
== Rules
|
118
|
+
|
119
|
+
|
120
|
+
Suppose you have to transform value of cells column to value for your object attribute, make a rule!
|
121
|
+
|
122
|
+
For example, in the sheet you have a value that represents an association attribute id:
|
123
|
+
You have roles predifined in your database :
|
124
|
+
Roles :
|
125
|
+
id : 1 , name => 'Admin'
|
126
|
+
id : 2 , name => 'Reader' ....
|
127
|
+
|
128
|
+
Sheet :
|
129
|
+
|
130
|
+
First name Last name Rôle
|
131
|
+
Bobby Lapointe Admin
|
132
|
+
Gaston Lagaffe Reader
|
133
|
+
|
134
|
+
|
135
|
+
'Admin' value must be retrieved as a Role with id 1
|
136
|
+
Make the rules like that :
|
137
|
+
|
138
|
+
rules = {[:role, :id] => rule}
|
139
|
+
|
140
|
+
Where rule is a Proc.
|
141
|
+
|
142
|
+
mapping = {[:role, :id] => 'Rôle'}
|
143
|
+
rules = {[:role, :id] => rule}
|
144
|
+
users = User.from_excel sheet, :mapping => mapping, :rules => rules
|
145
|
+
|
146
|
+
|
147
|
+
def rule
|
148
|
+
lambda do |cell_value|
|
149
|
+
case cell_value
|
150
|
+
when 'Admin' then 1
|
151
|
+
when 'Reader' then 2
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
That's it !
|
158
|
+
You get user.first.role => Role(:id => 1)
|
159
|
+
|
160
|
+
You may use rules for other purpose. Rule proc aim to transform value sheet cell to value you need for
|
161
|
+
your model.
|
162
|
+
|
163
|
+
|
164
|
+
|
165
|
+
== TO DO
|
166
|
+
|
167
|
+
Option for other spreadsheet than Excel (LibreOffice ...)
|
168
|
+
Handle has_many associations
|
data/lib/excel_import.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
module ExcelImport
|
2
|
+
|
3
|
+
class Bounds
|
4
|
+
DEFAULT_OFFSET_FROM_TITLE = 1
|
5
|
+
|
6
|
+
attr_accessor :first_row, :last_row, :first_column, :last_column, :offset_from_title
|
7
|
+
|
8
|
+
def initialize sheet, options={}
|
9
|
+
@options = options
|
10
|
+
@offset_from_title = title_offset
|
11
|
+
bounds(sheet)
|
12
|
+
end
|
13
|
+
|
14
|
+
def bounds sheet
|
15
|
+
limits = @options[:limits] || limits_from(sheet)
|
16
|
+
@first_column = limits[:start] ? limits[:start][1] : sheet.first_column
|
17
|
+
@first_row = limits[:start] ? limits[:start][0] : sheet.first_row + offset_from_title
|
18
|
+
@last_column = limits[:end] ? limits[:end][1] : sheet.last_column
|
19
|
+
@last_row = limits[:end] ? limits[:end][0] : sheet.last_row
|
20
|
+
end
|
21
|
+
|
22
|
+
def limits_from sheet
|
23
|
+
{:start => [sheet.first_row + offset_from_title, sheet.first_column], :end => [sheet.last_row, sheet.last_column]}
|
24
|
+
end
|
25
|
+
|
26
|
+
def title_offset
|
27
|
+
@options[:title] = true if @options[:title].nil?
|
28
|
+
@options[:title] ? @options[:offset_from_title] || DEFAULT_OFFSET_FROM_TITLE : 0
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class Mapping
|
34
|
+
|
35
|
+
attr_accessor :map, :has_one_associations
|
36
|
+
|
37
|
+
def initialize sheet, map, bounds
|
38
|
+
@map = map
|
39
|
+
@sheet = sheet
|
40
|
+
@bounds = bounds
|
41
|
+
@map ? indexation : default
|
42
|
+
@has_one_associations = extract_has_one_associations
|
43
|
+
end
|
44
|
+
|
45
|
+
def extract_has_one_associations
|
46
|
+
@map.inject({}) do |associations, (attribute, index)|
|
47
|
+
next(associations) unless attribute.is_a?(Array)
|
48
|
+
association = attribute.first
|
49
|
+
next(associations) if associations.has_key?(association)
|
50
|
+
associations.merge({association => klass_of(association)})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def attributes_of association
|
55
|
+
@map.select {|attribute,| attribute.is_a?(Array) && attribute.first == association}
|
56
|
+
end
|
57
|
+
|
58
|
+
def indexation
|
59
|
+
return self if @map.all? {|key, value| value.is_a?(Integer)}
|
60
|
+
@bounds.first_column.upto(@bounds.last_column) do |index|
|
61
|
+
@map[attribute column_name_at(index)] = index
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def default
|
67
|
+
@map = {}
|
68
|
+
@bounds.first_column.upto(@bounds.last_column) do |index|
|
69
|
+
@map[Mapping.default_attribute_for column_name_at(index)] = index
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def attribute column
|
74
|
+
@map.each_pair {|key, value| return key if value == column}
|
75
|
+
Mapping.default_attribute_for(column)
|
76
|
+
end
|
77
|
+
|
78
|
+
def column_name_at column_index
|
79
|
+
@sheet.cell(@bounds.first_row - @bounds.offset_from_title, column_index)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.default_attribute_for column_name
|
83
|
+
column_name.downcase.gsub(' ', '_').to_sym
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def klass_of attribute
|
89
|
+
attribute.to_s.camelize.constantize
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
class Rules
|
95
|
+
|
96
|
+
def initialize rules
|
97
|
+
@rules = rules || {}
|
98
|
+
end
|
99
|
+
|
100
|
+
def for attribute
|
101
|
+
@rules[attribute] || default
|
102
|
+
end
|
103
|
+
|
104
|
+
def default
|
105
|
+
lambda {|value| value}
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
class Integrator
|
111
|
+
|
112
|
+
def initialize sheet, klass, options
|
113
|
+
@sheet = sheet
|
114
|
+
@klass = klass
|
115
|
+
@bounds = Bounds.new(sheet, options)
|
116
|
+
@rules = Rules.new(options[:rules])
|
117
|
+
@mapping = Mapping.new(sheet, options[:mapping], @bounds)
|
118
|
+
end
|
119
|
+
|
120
|
+
def cell_value row_index, column_index
|
121
|
+
@rules.for(@mapping.attribute(column_index)).call(@sheet.cell(row_index, column_index))
|
122
|
+
end
|
123
|
+
|
124
|
+
def primary_attributes_hash row_index
|
125
|
+
@mapping.map.select{|attribute,| attribute.is_a? Symbol}.inject({}) do |hash, (attribute, index)|
|
126
|
+
hash[attribute] = cell_value(row_index, index)
|
127
|
+
hash
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def object_from_columns_for_association association, row_index, klass
|
132
|
+
attributes = @mapping.attributes_of(association)
|
133
|
+
attributes.inject(klass.new) do |object, ((association, attribute), index)|
|
134
|
+
object.send(attribute.to_s + '=', cell_value(row_index, index))
|
135
|
+
object
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def has_one_associations_attributes_hash row_index
|
140
|
+
@mapping.has_one_associations.inject({}) do |hash, (association, klass)|
|
141
|
+
hash[association] = object_from_columns_for_association(association, row_index, klass)
|
142
|
+
hash
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def row_to_hash row_index
|
147
|
+
hash = primary_attributes_hash(row_index)
|
148
|
+
hash.merge(has_one_associations_attributes_hash row_index)
|
149
|
+
end
|
150
|
+
|
151
|
+
def rows_to_objects
|
152
|
+
@bounds.first_row.upto(@bounds.last_row).inject([]) {|objects, row_index| objects << @klass.new(row_to_hash(row_index)); objects}
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.included(base)
|
158
|
+
class << base
|
159
|
+
def from_excel spreadsheet, opt={}
|
160
|
+
integrator = Integrator.new(Excel.new(spreadsheet.path), self, opt)
|
161
|
+
integrator.rows_to_objects
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
data/lib/from_excel.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Adress
|
2
|
+
attr_accessor :street, :town, :zip_code
|
3
|
+
|
4
|
+
def initialize args={}
|
5
|
+
@street = args[:street]
|
6
|
+
@town = args[:town]
|
7
|
+
@zip_code = args[:zip_code]
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
return false unless other
|
12
|
+
street == other.street &&
|
13
|
+
town == other.town &&
|
14
|
+
zip_code == other.zip_code
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/test/model/role.rb
ADDED
data/test/model/user.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class User
|
2
|
+
include ExcelImport
|
3
|
+
attr_accessor :first_name, :last_name, :age, :adress, :role
|
4
|
+
|
5
|
+
def initialize args={}
|
6
|
+
@first_name = args[:first_name]
|
7
|
+
@last_name = args[:last_name]
|
8
|
+
@age = args[:age]
|
9
|
+
@adress = args[:adress]
|
10
|
+
@role = args[:role]
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
first_name == other.first_name &&
|
15
|
+
last_name == other.last_name &&
|
16
|
+
age == other.age &&
|
17
|
+
adress == other.adress &&
|
18
|
+
role == other.role
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/test/test_bounds.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestBounds < Test::Unit::TestCase
|
4
|
+
|
5
|
+
|
6
|
+
def test_default_bounds
|
7
|
+
sheet = stubbed_sheet_with_bounds
|
8
|
+
bounds = ExcelImport::Bounds.new(sheet)
|
9
|
+
assert_equal 1, bounds.first_column
|
10
|
+
assert_equal 10, bounds.last_column
|
11
|
+
assert_equal 2, bounds.first_row #have a title line by default so first row is after title line
|
12
|
+
assert_equal 10, bounds.last_row
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_bounds_with_limits_changed
|
16
|
+
sheet = stub
|
17
|
+
bounds = ExcelImport::Bounds.new(sheet, :limits => {:start => [3, 4], :end => [5, 20]})
|
18
|
+
assert_equal 4, bounds.first_column
|
19
|
+
assert_equal 20, bounds.last_column
|
20
|
+
assert_equal 3, bounds.first_row
|
21
|
+
assert_equal 5, bounds.last_row
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_bounds_without_title_for_columns
|
25
|
+
sheet = stubbed_sheet_with_bounds
|
26
|
+
bounds = ExcelImport::Bounds.new(sheet, :title => false)
|
27
|
+
assert_equal 1, bounds.first_column
|
28
|
+
assert_equal 10, bounds.last_column
|
29
|
+
assert_equal 1, bounds.first_row # No title so first row is the first row of sheet
|
30
|
+
assert_equal 10, bounds.last_row
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_bounds_with_only_start_key_should_take_sheet_end_cell_coordinates
|
34
|
+
sheet = stubbed_sheet_with_bounds
|
35
|
+
bounds = ExcelImport::Bounds.new(sheet, :limits => {:start => [3, 4]})
|
36
|
+
assert_equal 4, bounds.first_column
|
37
|
+
assert_equal 10, bounds.last_column
|
38
|
+
assert_equal 3, bounds.first_row
|
39
|
+
assert_equal 10, bounds.last_row
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_bounds_with_only_end_key_should_take_sheet_start_cell_coordinates_with_1_added_to_first_row_if_title
|
43
|
+
sheet = stubbed_sheet_with_bounds
|
44
|
+
bounds = ExcelImport::Bounds.new(sheet, :limits => {:end => [10, 5]})
|
45
|
+
assert_equal 1, bounds.first_column
|
46
|
+
assert_equal 5, bounds.last_column
|
47
|
+
assert_equal 2, bounds.first_row
|
48
|
+
assert_equal 10, bounds.last_row
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_bounds_with_offset_from_title
|
52
|
+
sheet = stubbed_sheet_with_bounds
|
53
|
+
bounds = ExcelImport::Bounds.new(sheet, :offset_from_title => 4)
|
54
|
+
assert_equal 1, bounds.first_column
|
55
|
+
assert_equal 10, bounds.last_column
|
56
|
+
assert_equal 5, bounds.first_row
|
57
|
+
assert_equal 10, bounds.last_row
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def stubbed_sheet_with_bounds
|
65
|
+
stub(:first_column => 1, :last_column => 10, :first_row => 1, :last_row => 10)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestFromExcel < Test::Unit::TestCase
|
4
|
+
SIMPLE_SHEET= File.join(File.dirname(__FILE__), 'spreadsheets/simple_sheet.xls')
|
5
|
+
SHEET_WITH_COLUMN_NAME_DIFFERENT_FROM_ATTRIBUTE = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_with_column_name_different_from_attribute.xls')
|
6
|
+
SHEET_WITHOUT_COLUMNS_NAMES = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_without_columns_names.xls')
|
7
|
+
SHEET_WITH_START_END_COLUMNS_ROWS = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_with_start_end_columns_rows.xls')
|
8
|
+
SHEET_WITH_COLUMNS_FROM_ASSOCIATION = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_with_columns_from_association.xls')
|
9
|
+
SHEET_WITH_COLUMN_VALUE_TO_ASSOCIATION_ID = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_with_column_value_to_association_id.xls')
|
10
|
+
|
11
|
+
def test_import_with_simple_sheet
|
12
|
+
assert users = User.from_excel(File.new SIMPLE_SHEET)
|
13
|
+
assert_equal sheet_users, users
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_import_with_simple_sheet_with_some_attributes_mapped_with_columns_names
|
17
|
+
sheet = File.new(SHEET_WITH_COLUMN_NAME_DIFFERENT_FROM_ATTRIBUTE)
|
18
|
+
users = User.from_excel sheet, :mapping => {:first_name => 'Prenom'}
|
19
|
+
assert_equal sheet_users, users
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_should_be_able_to_import_with_attributes_mapped_with_columns_index
|
23
|
+
sheet = File.new(SHEET_WITHOUT_COLUMNS_NAMES)
|
24
|
+
users = User.from_excel sheet, :mapping => {:first_name => 1, :last_name => 2, :age => 3},
|
25
|
+
:limits => {:start => [2, 1]}
|
26
|
+
assert_equal sheet_users, users
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_should_be_able_to_import_simple_sheet_with_given_start_and_end_columns_and_rows
|
30
|
+
assert users = User.from_excel(File.new(SHEET_WITH_START_END_COLUMNS_ROWS), :limits => {:start => [3, 3], :end => [5, 5] })
|
31
|
+
assert_equal sheet_users, users
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_should_import_columns_on_associations_attributes_with_mapping_on_columns_names
|
35
|
+
sheet = File.new(SHEET_WITH_COLUMNS_FROM_ASSOCIATION)
|
36
|
+
mapping = {[:adress, :street] => 'Street', [:adress, :town] => 'Town', [:adress, :zip_code] => 'Zip Code'}
|
37
|
+
users = User.from_excel sheet, :mapping => mapping
|
38
|
+
|
39
|
+
assert_equal users_with_adresses, users
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_should_be_able_to_apply_rule_on_column_cells_to_transform_data_from_sheet_to_object_attribute_value
|
43
|
+
sheet = File.new(SHEET_WITH_COLUMN_VALUE_TO_ASSOCIATION_ID)
|
44
|
+
mapping = {[:role, :id] => 'Rôle'}
|
45
|
+
rules = {[:role, :id] => rule_to_retrieve_id}
|
46
|
+
users = User.from_excel sheet, :mapping => mapping, :rules => rules
|
47
|
+
|
48
|
+
assert_equal users_with_role, users
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def rule_to_retrieve_id
|
54
|
+
lambda do |cell_value|
|
55
|
+
case cell_value
|
56
|
+
when 'Admin' then 1
|
57
|
+
when 'Reader' then 2
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def sheet_users
|
63
|
+
[User.new(:first_name => 'Albert', :last_name => 'Einstein', :age => 131),
|
64
|
+
User.new(:first_name => 'Leonard', :last_name => 'De Vinci', :age => 558),
|
65
|
+
User.new(:first_name => 'Martin', :last_name => 'Heidegger', :age => 121)]
|
66
|
+
end
|
67
|
+
|
68
|
+
def users_with_adresses
|
69
|
+
adresses = [ Adress.new(:street => '17 rue de Brest', :town => 'Quimper', :zip_code => 29000),
|
70
|
+
Adress.new(:street => '26 rue des Lilas', :town => 'Paris', :zip_code => 75017),
|
71
|
+
Adress.new(:street => '13 rue du port', :town => 'Vannes', :zip_code => 56000)]
|
72
|
+
users = sheet_users
|
73
|
+
users.each_with_index {|user, index| user.adress = adresses[index]}
|
74
|
+
users
|
75
|
+
end
|
76
|
+
|
77
|
+
def users_with_role
|
78
|
+
[ User.new(:first_name => 'Bobby', :last_name => 'Lapointe', :role => Role.new(:id => 1)),
|
79
|
+
User.new(:first_name => 'Gaston', :last_name => 'Lagaffe', :role => Role.new(:id => 2)) ]
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestMapping < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_mapping_should_make_default_attribute_name_from_columns_title
|
6
|
+
sheet = stubbed_sheet
|
7
|
+
bounds = ExcelImport::Bounds.new(sheet)
|
8
|
+
mapping = ExcelImport::Mapping.new(sheet, {}, bounds )
|
9
|
+
|
10
|
+
assert_equal :first_name, mapping.attribute('First Name')
|
11
|
+
assert_equal :last_name, mapping.attribute('Last Name')
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_mapping_should__attribute_name
|
15
|
+
sheet = stubbed_sheet
|
16
|
+
bounds = ExcelImport::Bounds.new(sheet)
|
17
|
+
mapping = ExcelImport::Mapping.new(sheet, {:name => 'First Name'}, bounds )
|
18
|
+
|
19
|
+
assert_equal :name, mapping.attribute(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_should_handle_association_attributes
|
23
|
+
sheet = stubbed_sheet_with_adress_columns
|
24
|
+
mapping = {[:adress, :street] => 'Street', [:adress, :town] => 'Town'}
|
25
|
+
bounds = ExcelImport::Bounds.new(sheet)
|
26
|
+
mapping = ExcelImport::Mapping.new(sheet, mapping, bounds )
|
27
|
+
|
28
|
+
assert_equal [:adress, :street], mapping.attribute(4)
|
29
|
+
assert_equal [:adress, :town], mapping.attribute(5)
|
30
|
+
assert_equal 5, mapping.map[[:adress, :town]]
|
31
|
+
assert_equal ({:adress => Adress}), mapping.has_one_associations
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def stubbed_sheet
|
38
|
+
sheet = stub(:first_column => 1, :last_column => 3, :first_row => 1, :last_row => 2)
|
39
|
+
sheet.stubs(:cell).with(1,1).returns('First Name')
|
40
|
+
sheet.stubs(:cell).with(1,2).returns('Last Name')
|
41
|
+
sheet.stubs(:cell).with(1,3).returns('Age')
|
42
|
+
sheet
|
43
|
+
end
|
44
|
+
|
45
|
+
def stubbed_sheet_with_adress_columns
|
46
|
+
sheet = stub(:first_column => 1, :last_column => 5, :first_row => 1, :last_row => 2)
|
47
|
+
sheet.stubs(:cell).with(1,1).returns('First Name')
|
48
|
+
sheet.stubs(:cell).with(1,2).returns('Last Name')
|
49
|
+
sheet.stubs(:cell).with(1,3).returns('Age')
|
50
|
+
sheet.stubs(:cell).with(1,4).returns('Street')
|
51
|
+
sheet.stubs(:cell).with(1,5).returns('Town')
|
52
|
+
sheet
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: from_excel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Philippe Cantin
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-11-08 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Import Excel spreadsheet rows to ruby objects
|
23
|
+
email:
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.rdoc
|
30
|
+
files:
|
31
|
+
- lib/excel_import.rb
|
32
|
+
- lib/from_excel.rb
|
33
|
+
- README.rdoc
|
34
|
+
- test/helper.rb
|
35
|
+
- test/model/adress.rb
|
36
|
+
- test/model/role.rb
|
37
|
+
- test/model/user.rb
|
38
|
+
- test/test_bounds.rb
|
39
|
+
- test/test_from_excel.rb
|
40
|
+
- test/test_mapping.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/anoiaque/from_excel
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.3.7
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Import Excel spreadsheet rows to ruby objects
|
75
|
+
test_files:
|
76
|
+
- test/helper.rb
|
77
|
+
- test/model/adress.rb
|
78
|
+
- test/model/role.rb
|
79
|
+
- test/model/user.rb
|
80
|
+
- test/test_bounds.rb
|
81
|
+
- test/test_from_excel.rb
|
82
|
+
- test/test_mapping.rb
|