from_excel 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -0,0 +1,5 @@
1
+ require 'nokogiri'
2
+ require 'roo'
3
+ require 'rubygems'
4
+ require 'active_support/inflector'
5
+ require 'excel_import'
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), '../lib/from_excel')
4
+
5
+ require 'model/user'
6
+ require 'model/adress'
7
+ require 'model/role'
8
+
9
+
10
+ require 'mocha'
@@ -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
@@ -0,0 +1,13 @@
1
+ class Role
2
+ attr_accessor :id, :title
3
+
4
+ def initialize args={}
5
+ @id = args[:id]
6
+ @name = args[:title]
7
+ end
8
+
9
+ def ==(other)
10
+ id == other.id
11
+ end
12
+
13
+ end
@@ -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
@@ -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