goodsheet 0.2.1.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +68 -5
- data/lib/goodsheet/spreadsheet.rb +128 -48
- data/lib/goodsheet/version.rb +1 -1
- data/test/fixtures/ss_01.xls +0 -0
- data/test/test_spreadsheet_01.rb +21 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a61094f40016b3c32594b97b5d3e134c4b59016
|
4
|
+
data.tar.gz: 5fc1f3d8bbbdeaf1efafbceeb2610296004bce86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ff578441474684622d97d9e7481abb007783c6ee0fb302689377c081eff0662dda889766140dbe0d51e7b99d0e9ba0e0846deb7463c0df85994fe6a8c5a3d07
|
7
|
+
data.tar.gz: 50dcf983da1e79799d313366c47369d0b8569aad10ff6a9312ceae7c4eeae8d5b0229ef0b49dc30dda941434e315214450349a8c2723feb699272e5eb98c21dc
|
data/README.md
CHANGED
@@ -42,24 +42,87 @@ By default:
|
|
42
42
|
|
43
43
|
Pass your validation rules into the block passed to the read method, together with the column_names method that define the position (or index) and the name of the columns you want to read.
|
44
44
|
|
45
|
-
### More
|
45
|
+
### More on usage
|
46
46
|
|
47
47
|
You can select the desired sheet by index (starting from zero) or by name:
|
48
48
|
```ruby
|
49
49
|
ss = Goodsheet::Spreadsheet.new("my_data.xlsx")
|
50
50
|
ss.sheet(2) # select the third sheet
|
51
|
-
ss.sheet("
|
52
|
-
ss.sheets # get the sheet names array
|
51
|
+
ss.sheet("Sheet4") # select the sheet named "Sheet4"
|
53
52
|
```
|
54
|
-
Get the
|
55
53
|
|
54
|
+
Get the number of sheets, and their names:
|
55
|
+
```ruby
|
56
|
+
ss.size # => 4
|
57
|
+
ss.sheets # => ["Sheet1", "Sheet2", "Sheet3", "Sheet4"]
|
58
|
+
```
|
56
59
|
|
60
|
+
When you init a new spreadsheet, you select a sheet or you invoke `validate` or `read` method, you can pass an hash of options.
|
61
|
+
```ruby
|
62
|
+
ss = Goodsheet::Spreadsheet.new("my_data.xlsx", :skip => 1, :header_row => 0, :max_errors => 0, :row_limit => 0, :force_nil => nil )
|
63
|
+
```
|
64
|
+
These are the valid options with their default values:
|
65
|
+
- `:skip` allow to skip a desired number of lines when you read or validate a sheet
|
66
|
+
- with `:header_row` you define the index of the header row
|
67
|
+
- with `:max_errors` you define the maximum number of errors after the validation break
|
68
|
+
- with `:row_limit` you define the maximum number of row you wanto to read or validate
|
69
|
+
- with `:force_nil` you can specify the value to set when a cell hold a nil value (is empty)
|
70
|
+
|
71
|
+
As said you can use the same option when selecting a sheet
|
72
|
+
```ruby
|
73
|
+
ss.sheet(0, :skip => 2)
|
74
|
+
```
|
75
|
+
or read or validate a sheet:
|
57
76
|
```ruby
|
77
|
+
ss.validate(0, :force_nil => 0.0) do
|
78
|
+
# ...
|
79
|
+
end
|
80
|
+
```
|
58
81
|
|
82
|
+
Get the content of header row:
|
83
|
+
```ruby
|
84
|
+
ss.header_row # => ["year", "month", "day"]
|
85
|
+
```
|
59
86
|
|
87
|
+
Get the number of rows:
|
88
|
+
```ruby
|
89
|
+
ss.total_rows # => all instanced rows
|
90
|
+
ss.rows_wo_skipped # => except the skipped ones, aliased by `rows` method
|
91
|
+
```
|
92
|
+
|
93
|
+
#### Reading and validate
|
94
|
+
|
95
|
+
Use the `validate` and `read` methods to perform validation and reading. Note that the reading function include a validation call.
|
96
|
+
Pass the previously seen `options` hash and a block to `validate`/`read` method.
|
97
|
+
Inside the block you define columns names and indexes you want to validate/read using the `column_names` method. You can use one of these 3 forms:
|
98
|
+
- `column_names :a => 0, :b => 1, :c => 3`
|
99
|
+
- `column_names 0 => :a, 1 => :b, 3 => :c`
|
100
|
+
- `column_names [:a, :b, nil, :c]`
|
101
|
+
|
102
|
+
Aside from define the columns settings, into block you define the validation rules.
|
103
|
+
Refer to the [official guide](http://guides.rubyonrails.org/active_record_validations.html) and [ROR Api](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html)
|
104
|
+
|
105
|
+
|
106
|
+
Another example:
|
107
|
+
```ruby
|
108
|
+
ss = Goodsheet::Spreadsheet.new("my_data.xlsx")
|
109
|
+
ss.sheet(1, :max_errors => 50, :force_nil => 0.0)
|
110
|
+
res = ss.read do
|
111
|
+
column_names :item => 0, :qty => 1, :price => 2, :prod => 3
|
112
|
+
# same as: [:item, :qty, :price, :prod]
|
113
|
+
validates :item, :presence => true
|
114
|
+
validates :qty, :presence => true, :numericality => { :greater_than => 0.0}
|
115
|
+
validates :price, :presence => true, :numericality => { :greater_than => 0.0}
|
116
|
+
validate :product
|
117
|
+
|
118
|
+
def :product
|
119
|
+
if qty * price != prod
|
120
|
+
errors.add(:prod, "must be the product of qty and price")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
60
124
|
```
|
61
125
|
|
62
|
-
The +read+ method include the +validate+ method, that can be invoke
|
63
126
|
|
64
127
|
|
65
128
|
|
@@ -3,86 +3,151 @@ require 'roo'
|
|
3
3
|
module Goodsheet
|
4
4
|
|
5
5
|
class Spreadsheet < Roo::Spreadsheet
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :skip, :header_row, :max_errors, :row_limit
|
7
|
+
attr_reader :s_opts
|
7
8
|
|
8
|
-
# Initialize a Goodsheet object
|
9
|
+
# Initialize a Goodsheet object. The first sheet will be selected.
|
9
10
|
#
|
10
|
-
# @param filename [String]
|
11
|
-
# @param [
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
11
|
+
# @param filename [String] The spreadsheet filename you want to read
|
12
|
+
# @param [Hash] options Options to define the behaviour on reading and validation the sheets. These options are applied to all sheets, but can be overwritten by similar options when selecting the sheet or calling +read+ or +validate+ method
|
13
|
+
# @option options [Fixnum] :skip (1) Number of rows to skip
|
14
|
+
# @option options [Fixnum] :header_row (0) The header row index (0-based)
|
15
|
+
# @option options [Fixnum] :max_errors (0) Max number of error until stop validation
|
16
|
+
# @option options [Fixnum] :row_limit (0) Max number of row to read
|
17
|
+
# @option options [Object] :force_nil (nil) Force nils found to this value
|
16
18
|
def initialize(filename, options={})
|
17
|
-
|
19
|
+
# set_book_options(options)
|
18
20
|
@filename = filename
|
19
21
|
@ss = Roo::Spreadsheet.open(filename, options)
|
22
|
+
@s_opts = Array.new(size, {})
|
23
|
+
size.times do |i|
|
24
|
+
set_sheet_options(i, options)
|
25
|
+
end
|
20
26
|
end
|
21
27
|
|
22
28
|
|
23
|
-
#
|
29
|
+
# Select the desidered sheet.
|
30
|
+
#
|
31
|
+
# @param idx [Fixnum, String] The index (0-based) or the name of the sheet to select
|
32
|
+
# @param [Hash] options Options to define the behaviour on reading and validation the sheet. These options are applied only to the current sheet, but can be overwritten by similar options when calling +read+ or +validate+ method
|
33
|
+
# @option options [Fixnum] :skip (1) Number of rows to skip
|
34
|
+
# @option options [Fixnum] :header_row (0) The header row index (0-based)
|
35
|
+
# @option options [Fixnum] :max_errors (0) Max number of error until stop validation
|
36
|
+
# @option options [Fixnum] :row_limit (0) Max number of row to read
|
37
|
+
# @option options [Object] :force_nil (nil) Force nils found to this value
|
24
38
|
def sheet(idx, options={})
|
25
|
-
|
39
|
+
check_sheet_exists(idx)
|
26
40
|
@ss.sheet(idx)
|
27
|
-
|
41
|
+
set_sheet_options(idx, options)
|
28
42
|
end
|
29
43
|
|
44
|
+
# Get the sheet names list
|
45
|
+
#
|
46
|
+
# @return [Array<String>] An array with sheet names.
|
30
47
|
def sheets
|
31
48
|
@ss.sheets
|
32
49
|
end
|
33
50
|
|
51
|
+
# Get the number of sheets
|
52
|
+
#
|
53
|
+
# @return [Fixnum] Number of sheets.
|
54
|
+
def size
|
55
|
+
@ss.sheets.size
|
56
|
+
end
|
34
57
|
|
58
|
+
# Get the options of current sheet
|
59
|
+
#
|
60
|
+
# @return [Fixnum] Number of sheets.
|
61
|
+
def options
|
62
|
+
@s_opts[index]
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Get the header row of the currently selected sheet
|
67
|
+
#
|
68
|
+
# @return [Array<Object>] An array cell content objects (String, Float, ...)
|
35
69
|
def get_header
|
36
|
-
@ss.row(@header_row+1) # because roo in 1-based
|
70
|
+
@ss.row(@s_opts[index][:header_row]+1) # because roo in 1-based
|
37
71
|
end
|
38
72
|
|
39
|
-
# Get the
|
73
|
+
# Get the name of current (default) sheet
|
74
|
+
#
|
75
|
+
# @return [String] The sheet name
|
40
76
|
def name
|
41
77
|
@ss.default_sheet
|
42
78
|
end
|
43
79
|
|
80
|
+
# Get the index of current (default) sheet
|
81
|
+
#
|
82
|
+
# @return [Fixnum] The sheet index
|
83
|
+
def index
|
84
|
+
@ss.sheets.index(@ss.default_sheet)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get the total number of rows (of the currently selected sheet)
|
88
|
+
#
|
89
|
+
# @return [Fixnum] The number of rows
|
44
90
|
def total_rows
|
45
91
|
@ss.parse.size
|
46
92
|
end
|
47
93
|
|
48
|
-
|
49
|
-
|
94
|
+
# Get the number of all rows minus the skipped ones (of the currently selected sheet)
|
95
|
+
#
|
96
|
+
# @return [Fixnum] The number of rows
|
97
|
+
def rows_wo_skipped
|
98
|
+
@ss.parse.size - @s_opts[index][:skip]
|
50
99
|
end
|
51
|
-
alias :rows :
|
52
|
-
|
53
|
-
# Valid options:
|
54
|
-
# :max_errors : The validation will be stopped if the number of errors exceed max_errors (default: 0 or don't stop)
|
55
|
-
# :limit : Max number of rows to validate (default: 0 or validate all rows)
|
100
|
+
alias :rows :rows_wo_skipped
|
56
101
|
|
57
|
-
#
|
102
|
+
# Validate the current sheet.
|
103
|
+
#
|
104
|
+
# @param [Hash] options Validation options for the current sheet.
|
105
|
+
# @option options [Fixnum] :skip (1) Number of rows to skip
|
106
|
+
# @option options [Fixnum] :header_row (0) The header row index (0-based)
|
107
|
+
# @option options [Fixnum] :max_errors (0) Max number of error until stop validation
|
108
|
+
# @option options [Fixnum] :row_limit (0) Max number of row to read
|
109
|
+
# @option options [Object] :force_nil (nil) Force nils found to this value
|
110
|
+
# @yield Column settings and validation rules
|
111
|
+
# @return [ValidationErrors] Validation errors
|
58
112
|
def validate(options={}, &block)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
113
|
+
# set_current_sheet_options(options)
|
114
|
+
skip = options[:skip] || @s_opts[index][:skip]
|
115
|
+
header_row = options[:header_row] || @s_opts[index][:header_row]
|
116
|
+
max_errors = options[:max_errors] || @s_opts[index][:max_errors]
|
117
|
+
row_limit = options[:row_limit] || @s_opts[index][:row_limit]
|
118
|
+
force_nil = options[:force_nil] || @s_opts[index][:force_nil]
|
63
119
|
|
64
120
|
validation_errors = ValidationErrors.new
|
65
121
|
|
66
122
|
my_class = options[:my_custom_row_class] || build_my_class(block)
|
67
123
|
|
68
|
-
line = skip # 0-based, from the top
|
69
|
-
@ss.parse[skip..-1].each do |row| # row is an array of elements
|
124
|
+
line = @s_opts[index][:skip] # 0-based, from the top
|
125
|
+
@ss.parse[@s_opts[index][:skip]..-1].each do |row| # row is an array of elements
|
70
126
|
validation_errors.add(line, my_class.new(row))
|
71
127
|
break if max_errors>0 && validation_errors.size >= max_errors
|
72
|
-
break if row_limit && row_limit>0 && line>=(row_limit
|
128
|
+
break if row_limit && row_limit>0 && line>=(row_limit+@s_opts[index][:skip]-1)
|
73
129
|
line +=1
|
74
130
|
end
|
75
131
|
validation_errors
|
76
132
|
end
|
77
133
|
|
78
134
|
|
79
|
-
#
|
135
|
+
# Validate and, if successful, read the current sheet.
|
136
|
+
#
|
137
|
+
# @param [Hash] options Reading and validation options for the current sheet.
|
138
|
+
# @option options [Fixnum] :skip (1) Number of rows to skip
|
139
|
+
# @option options [Fixnum] :header_row (0) The header row index (0-based)
|
140
|
+
# @option options [Fixnum] :max_errors (0) Max number of error until stop validation
|
141
|
+
# @option options [Fixnum] :row_limit (0) Max number of row to read
|
142
|
+
# @option options [Object] :force_nil (nil) Force nils found to this value
|
143
|
+
# @yield Column settings and validation rules
|
144
|
+
# @return [ReadResult] The result
|
80
145
|
def read(options={}, &block)
|
81
|
-
skip = options[:skip] || @skip
|
82
|
-
header_row = options[:header_row] || @header_row
|
83
|
-
max_errors = options[:max_errors] || @max_errors
|
84
|
-
row_limit = options[:row_limit] || @row_limit
|
85
|
-
force_nil = options[:force_nil]
|
146
|
+
skip = options[:skip] || @s_opts[index][:skip]
|
147
|
+
header_row = options[:header_row] || @s_opts[index][:header_row]
|
148
|
+
max_errors = options[:max_errors] || @s_opts[index][:max_errors]
|
149
|
+
row_limit = options[:row_limit] || @s_opts[index][:row_limit]
|
150
|
+
force_nil = options[:force_nil] || @s_opts[index][:force_nil]
|
86
151
|
|
87
152
|
my_class = build_my_class(block)
|
88
153
|
options[:my_custom_row_class] = my_class
|
@@ -107,13 +172,21 @@ module Goodsheet
|
|
107
172
|
Object.const_set get_custom_row_class_name, Row.inherit(block)
|
108
173
|
end
|
109
174
|
|
110
|
-
def
|
111
|
-
|
112
|
-
@
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
175
|
+
def select_sheet_options(idx)
|
176
|
+
if idx.is_a? Integer
|
177
|
+
@s_opts[idx]
|
178
|
+
elsif idx.is_a? String
|
179
|
+
@s_opts[@ss.sheets.index(idx)]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def check_sheet_exists(idx)
|
184
|
+
if idx.is_a? Integer
|
185
|
+
raise Goodsheet::SheetNotFoundError if idx < 0 || idx > (size-1)
|
186
|
+
elsif idx.is_a? String
|
187
|
+
raise Goodsheet::SheetNotFoundError if !@ss.sheets.include?(idx)
|
188
|
+
else
|
189
|
+
raise ArgumentError, "idx must be an Integer or a String"
|
117
190
|
end
|
118
191
|
end
|
119
192
|
|
@@ -121,12 +194,19 @@ module Goodsheet
|
|
121
194
|
"CustRow_#{(Time.now.to_f*(10**10)).to_i}"
|
122
195
|
end
|
123
196
|
|
124
|
-
def
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
197
|
+
def set_current_sheet_options(options)
|
198
|
+
set_sheet_options(index, options)
|
199
|
+
end
|
200
|
+
|
201
|
+
def set_sheet_options(idx, options)
|
202
|
+
i = idx.is_a?(Integer) ? idx : @ss.sheets.index(idx)
|
203
|
+
@s_opts[i] = {
|
204
|
+
:skip => options[:skip] || @s_opts[i][:skip] || 1,
|
205
|
+
:header_row => options[:header_row] || @s_opts[i][:header_row] || 0,
|
206
|
+
:max_errors => options[:max_errors] || @s_opts[i][:max_errors] || 0,
|
207
|
+
:row_limit => options[:row_limit] || @s_opts[i][:row_limit] || 0,
|
208
|
+
:force_nil => options[:force_nil] || @s_opts[i][:force_nil] || nil
|
209
|
+
}
|
130
210
|
end
|
131
211
|
end
|
132
212
|
end
|
data/lib/goodsheet/version.rb
CHANGED
data/test/fixtures/ss_01.xls
CHANGED
Binary file
|
data/test/test_spreadsheet_01.rb
CHANGED
@@ -104,6 +104,27 @@ class TestSpreadsheet_01 < Test::Unit::TestCase
|
|
104
104
|
assert_equal(3, validation_errors.size)
|
105
105
|
end
|
106
106
|
|
107
|
+
def test_size
|
108
|
+
assert_equal(4, @ss.size)
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_options_precedence
|
112
|
+
assert_equal({:skip=>1, :header_row=>0, :max_errors=>0, :row_limit=>0, :force_nil=>nil}, @ss.options)
|
113
|
+
|
114
|
+
# select "Sheet4" and change option
|
115
|
+
@ss.sheet(3, :force_nil => 0.0)
|
116
|
+
assert_equal({:skip=>1, :header_row=>0, :max_errors=>0, :row_limit=>0, :force_nil=>0.0}, @ss.options)
|
117
|
+
|
118
|
+
# reselect "Sheet1" and change option
|
119
|
+
@ss.sheet(0)
|
120
|
+
assert_equal({:skip=>1, :header_row=>0, :max_errors=>0, :row_limit=>0, :force_nil=>nil}, @ss.options)
|
121
|
+
|
122
|
+
# now validate with new options, but the sheet option must be unchanged
|
123
|
+
result = @ss.validate(:force_nil => 0.0, :skip => 6) do
|
124
|
+
# none
|
125
|
+
end
|
126
|
+
assert_equal({:skip=>1, :header_row=>0, :max_errors=>0, :row_limit=>0, :force_nil=>nil}, @ss.options)
|
127
|
+
end
|
107
128
|
|
108
129
|
def test_read_sheet4
|
109
130
|
@ss.sheet(3)
|