goodsheet 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +47 -14
- data/img/img_01.png +0 -0
- data/lib/goodsheet.rb +1 -1
- data/lib/goodsheet/read_result.rb +4 -3
- data/lib/goodsheet/row.rb +2 -2
- data/lib/goodsheet/spreadsheet.rb +4 -9
- data/lib/goodsheet/validation_error.rb +2 -1
- data/lib/goodsheet/validation_errors.rb +4 -0
- data/lib/goodsheet/version.rb +1 -1
- data/test/fixtures/example.xls +0 -0
- data/test/fixtures/ss_01.xls +0 -0
- data/test/test_spreadsheet_01.rb +9 -4
- data/test/test_spreadsheet_03.rb +37 -1
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba007929170fe7d6e80af781d6a20132018238f0
|
4
|
+
data.tar.gz: 97ae8e3fd6e1c83656d138a532cfa8177486da57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d10c109430d18557c4fbd823755290ab1fc6361f2e9305de87e10896aa9bda9c5322e954969ec0991243e7921a45dc46d44f34485811ccc87506888921f006fd
|
7
|
+
data.tar.gz: 9ec851827a5c6a67e83dfadeb5036626b4f29aef8e5953a841860f5216275a453e690a81196d37e3dc10fe6eeb26e49a993e71bd43fe02aa0387c67e4a545e47
|
data/README.md
CHANGED
@@ -24,24 +24,56 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
### Getting started
|
26
26
|
|
27
|
+
Given a spreadsheet:
|
28
|
+
![Spreadsheet](https://raw.github.com/iwan/goodsheet/master/img/img_01.png)
|
29
|
+
|
27
30
|
```ruby
|
28
|
-
ss = Goodsheet::Spreadsheet.new("
|
31
|
+
ss = Goodsheet::Spreadsheet.new("example.xls")
|
29
32
|
res = ss.read do
|
30
|
-
column_names :
|
31
|
-
validates :
|
32
|
-
validates :
|
33
|
+
column_names :filename => 0, :size => 1, :created_at => 3, :updated_at => 4 # ignore 'description' column
|
34
|
+
validates :filename, :presence => true
|
35
|
+
validates :size, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0 }
|
36
|
+
validate :order_of_dates
|
37
|
+
|
38
|
+
def order_of_dates
|
39
|
+
if created_at > updated_at
|
40
|
+
errors.add(:updated_at, "cannot be before creation date")
|
41
|
+
end
|
42
|
+
end
|
33
43
|
end
|
44
|
+
```
|
34
45
|
|
46
|
+
If validation is successfull the spreadsheet will be readed and his content retrieved:
|
47
|
+
```ruby
|
35
48
|
res.valid? # => true
|
36
|
-
res.values # => {:
|
49
|
+
res.values # => {:filename=>["img_01.jpg", "img_17.jpg", "img_56.jpg"], :size=>[123854.0, 278333.0, 529639.0], :created_at=>[#<Date: 2013-03-03 ((2456355j,0s,0n),+0s,2299161j)>, ...], ...}
|
50
|
+
```
|
51
|
+
|
52
|
+
Alternatively you can get the result values by rows:
|
53
|
+
```ruby
|
54
|
+
res.values(:rows_array) # => [["img_01.jpg", 123854.0, #<Date: 2013-03-03 ((2456355j,0s,0n),+0s,2299161j)>, #<Date: 2013-03-31 ((2456383j,0s,0n),+0s,2299161j)>], ["img_17.jpg", 278333.0, #<Date: 2013-05-03 ...], ...]
|
55
|
+
|
56
|
+
res.values(:rows_hash) # => [{:filename=>"img_01.jpg", :size=>123854.0, :created_at=>#<Date: 2013-03-03 ((2456355j,0s,0n),+0s,2299161j)>, :updated_at=>#<Date: 2013-03-31 ((2456383j,0s,0n),+0s,2299161j)>}, {:filename=>"img_17.jpg", :size=>278333.0, ...}, ... ]
|
57
|
+
```
|
58
|
+
|
59
|
+
If validation fails the spreadsheet will not be readed:
|
60
|
+
```ruby
|
61
|
+
res.valid? # => false
|
62
|
+
res.errors.size # => 1
|
63
|
+
res.errors.to_a.first # => "Row 3 is invalid: Filename can't be blank"
|
64
|
+
res.values # => {}
|
37
65
|
```
|
38
66
|
|
39
67
|
By default:
|
40
68
|
* the first sheet is selected
|
41
69
|
* one line (the first) is skipped (i'm expeting that is the header line)
|
42
|
-
* the content is returned as an hash of columns
|
43
70
|
|
44
|
-
|
71
|
+
You can also invoke `validate` instead of `read` method (see below).
|
72
|
+
A validation will be always executed before the extraction (reading) of data. If validation fails the reading will not be performed.
|
73
|
+
|
74
|
+
The definition of columns you want to read (through the `column_names` method) and the rules for validation are defined inside the block you pass to `read` or `validate` method.
|
75
|
+
|
76
|
+
|
45
77
|
|
46
78
|
### More on usage
|
47
79
|
|
@@ -58,16 +90,17 @@ ss.size # => 4
|
|
58
90
|
ss.sheets # => ["Sheet1", "Sheet2", "Sheet3", "Sheet4"]
|
59
91
|
```
|
60
92
|
|
61
|
-
|
93
|
+
An hash of options can be passed to spreadsheet initializer `Goodsheet::Spreadsheet.new`, to `sheet` selection, to `validate` or `read` method. The first one define validation/reading rules for all sheets, but these can be overwritten by sheet and `validate`/`read`.
|
94
|
+
|
62
95
|
```ruby
|
63
96
|
ss = Goodsheet::Spreadsheet.new("my_data.xlsx", :skip => 1, :header_row => 0, :max_errors => 0, :row_limit => 0, :force_nil => nil )
|
64
97
|
```
|
65
98
|
These are the valid options with their default values:
|
66
|
-
- `:skip` allow to skip a desired number of lines when you read or validate a sheet
|
67
|
-
- with `:header_row` you define the index of the header row
|
68
|
-
- with `:max_errors` you define the maximum number of errors after the validation break
|
69
|
-
- with `:row_limit` you define the maximum number of row you wanto to read or validate
|
70
|
-
- with `:force_nil` you can specify the value to set when a cell hold a nil value (is empty)
|
99
|
+
- `:skip` allow to skip a desired number of lines when you read or validate a sheet (1)
|
100
|
+
- with `:header_row` you define the index of the header row (0)
|
101
|
+
- with `:max_errors` you define the maximum number of errors after the validation break (0: no limit)
|
102
|
+
- with `:row_limit` you define the maximum number of row you wanto to read or validate (0: no limit)
|
103
|
+
- with `:force_nil` you can specify the value to set when a cell hold a nil value (is empty) (still nil)
|
71
104
|
|
72
105
|
As said you can use the same option when selecting a sheet
|
73
106
|
```ruby
|
@@ -95,7 +128,7 @@ ss.rows_wo_skipped # => except the skipped ones, aliased by `rows` method
|
|
95
128
|
|
96
129
|
Use the `validate` and `read` methods to perform validation and reading. Note that the reading function include a validation call.
|
97
130
|
Pass the previously seen `options` hash and a block to `validate`/`read` method.
|
98
|
-
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 4 forms:
|
131
|
+
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 4 forms (and their effect is the same):
|
99
132
|
- `column_names :a => 0, :b => 1, :c => 3`
|
100
133
|
- `column_names 0 => :a, 1 => :b, 3 => :c`
|
101
134
|
- `column_names [:a, :b, nil, :c]`
|
data/img/img_01.png
ADDED
Binary file
|
data/lib/goodsheet.rb
CHANGED
@@ -4,9 +4,9 @@ module Goodsheet
|
|
4
4
|
autoload :Row, 'goodsheet/row'
|
5
5
|
autoload :SheetNotFoundError, 'goodsheet/exceptions'
|
6
6
|
autoload :ReadResult, 'goodsheet/read_result'
|
7
|
-
autoload :Row, 'goodsheet/row'
|
8
7
|
autoload :Spreadsheet, 'goodsheet/spreadsheet'
|
9
8
|
autoload :ValidationError, 'goodsheet/validation_error'
|
10
9
|
autoload :ValidationErrors, 'goodsheet/validation_errors'
|
11
10
|
autoload :Version, 'goodsheet/version'
|
12
11
|
end
|
12
|
+
|
@@ -16,13 +16,14 @@ module Goodsheet
|
|
16
16
|
!valid?
|
17
17
|
end
|
18
18
|
|
19
|
-
def add(attribute, row
|
19
|
+
def add(attribute, row)
|
20
20
|
attribute = attribute.to_sym
|
21
|
-
(@vv[attribute] ||= []) <<
|
21
|
+
(@vv[attribute] ||= []) << row.send(attribute)
|
22
22
|
end
|
23
23
|
|
24
24
|
def values(format=:columns)
|
25
|
-
values_size =
|
25
|
+
values_size = 0
|
26
|
+
values_size = @vv.values.first.size if @vv.values.first
|
26
27
|
|
27
28
|
case format
|
28
29
|
when :columns
|
data/lib/goodsheet/row.rb
CHANGED
@@ -12,10 +12,10 @@ module Goodsheet
|
|
12
12
|
end
|
13
13
|
@keys = {} # idx => key
|
14
14
|
|
15
|
-
def initialize(arr)
|
15
|
+
def initialize(arr, nil_value=nil)
|
16
16
|
arr.each_with_index do |v, idx|
|
17
17
|
if k = self.class.keys[idx]
|
18
|
-
send("#{k}=", v)
|
18
|
+
send("#{k}=", v || nil_value)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
super()
|
@@ -110,20 +110,18 @@ module Goodsheet
|
|
110
110
|
# @yield Column settings and validation rules
|
111
111
|
# @return [ValidationErrors] Validation errors
|
112
112
|
def validate(options={}, &block)
|
113
|
-
# set_current_sheet_options(options)
|
114
113
|
skip = options[:skip] || @s_opts[index][:skip]
|
115
114
|
header_row = options[:header_row] || @s_opts[index][:header_row]
|
116
115
|
max_errors = options[:max_errors] || @s_opts[index][:max_errors]
|
117
116
|
row_limit = options[:row_limit] || @s_opts[index][:row_limit]
|
118
117
|
force_nil = options[:force_nil] || @s_opts[index][:force_nil]
|
119
|
-
|
120
118
|
validation_errors = ValidationErrors.new
|
121
119
|
|
122
120
|
my_class = options[:my_custom_row_class] || build_my_class(block)
|
123
121
|
|
124
122
|
line = @s_opts[index][:skip] # 0-based, from the top
|
125
123
|
@ss.parse[@s_opts[index][:skip]..-1].each do |row| # row is an array of elements
|
126
|
-
validation_errors.add(line, my_class.new(row))
|
124
|
+
validation_errors.add(line, my_class.new(row, force_nil))
|
127
125
|
break if max_errors>0 && validation_errors.size >= max_errors
|
128
126
|
break if row_limit && row_limit>0 && line>=(row_limit+@s_opts[index][:skip]-1)
|
129
127
|
line +=1
|
@@ -157,7 +155,7 @@ module Goodsheet
|
|
157
155
|
line = skip # 0-based, from the top
|
158
156
|
@ss.parse[skip..-1].each do |row| # row is an array of elements
|
159
157
|
my_class.row_attributes.each do |attribute|
|
160
|
-
read_result.add(attribute, my_class.new(row
|
158
|
+
read_result.add(attribute, my_class.new(row, force_nil))
|
161
159
|
end
|
162
160
|
break if row_limit && row_limit>0 && line>=(row_limit + skip - 1)
|
163
161
|
line +=1
|
@@ -169,7 +167,8 @@ module Goodsheet
|
|
169
167
|
private
|
170
168
|
|
171
169
|
def build_my_class(block)
|
172
|
-
|
170
|
+
n = get_custom_row_class_name
|
171
|
+
Object.const_set n, Row.inherit(block)
|
173
172
|
end
|
174
173
|
|
175
174
|
def select_sheet_options(idx)
|
@@ -194,10 +193,6 @@ module Goodsheet
|
|
194
193
|
"CustRow_#{(Time.now.to_f*(10**10)).to_i}"
|
195
194
|
end
|
196
195
|
|
197
|
-
def set_current_sheet_options(options)
|
198
|
-
set_sheet_options(index, options)
|
199
|
-
end
|
200
|
-
|
201
196
|
def set_sheet_options(idx, options)
|
202
197
|
i = idx.is_a?(Integer) ? idx : @ss.sheets.index(idx)
|
203
198
|
@s_opts[i] = {
|
@@ -7,7 +7,8 @@ module Goodsheet
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def to_s
|
10
|
-
"Row #{@line} is invalid for the following reason(s): #{@val_err.full_messages.join(', ')}"
|
10
|
+
# "Row #{@line} is invalid for the following reason(s): #{@val_err.full_messages.join(', ')}"
|
11
|
+
"Row #{@line} is invalid: #{@val_err.full_messages.join(', ')}"
|
11
12
|
end
|
12
13
|
end
|
13
14
|
end
|
data/lib/goodsheet/version.rb
CHANGED
Binary file
|
data/test/fixtures/ss_01.xls
CHANGED
Binary file
|
data/test/test_spreadsheet_01.rb
CHANGED
@@ -9,12 +9,12 @@ class TestSpreadsheet_01 < Test::Unit::TestCase
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def test_sheets
|
12
|
-
assert_equal(%w(Sheet1 Sheet2 Sheet3 Sheet4), @ss.sheets)
|
12
|
+
assert_equal(%w(Sheet1 Sheet2 Sheet3 Sheet4 Sheet5), @ss.sheets)
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_failed_sheet_selection
|
16
16
|
assert_raise Goodsheet::SheetNotFoundError do
|
17
|
-
@ss.sheet(
|
17
|
+
@ss.sheet(14)
|
18
18
|
end
|
19
19
|
assert_raise Goodsheet::SheetNotFoundError do
|
20
20
|
@ss.sheet("Sheet999")
|
@@ -36,6 +36,9 @@ class TestSpreadsheet_01 < Test::Unit::TestCase
|
|
36
36
|
|
37
37
|
@ss.sheet(3)
|
38
38
|
assert_equal("Sheet4", @ss.name)
|
39
|
+
|
40
|
+
@ss.sheet(4)
|
41
|
+
assert_equal("Sheet5", @ss.name)
|
39
42
|
end
|
40
43
|
|
41
44
|
def test_get_header_wo_options
|
@@ -105,7 +108,7 @@ class TestSpreadsheet_01 < Test::Unit::TestCase
|
|
105
108
|
end
|
106
109
|
|
107
110
|
def test_size
|
108
|
-
assert_equal(
|
111
|
+
assert_equal(5, @ss.size)
|
109
112
|
end
|
110
113
|
|
111
114
|
def test_options_precedence
|
@@ -126,6 +129,8 @@ class TestSpreadsheet_01 < Test::Unit::TestCase
|
|
126
129
|
assert_equal({:skip=>1, :header_row=>0, :max_errors=>0, :row_limit=>0, :force_nil=>nil}, @ss.options)
|
127
130
|
end
|
128
131
|
|
132
|
+
|
133
|
+
|
129
134
|
def test_read_sheet4
|
130
135
|
@ss.sheet(3)
|
131
136
|
|
@@ -135,11 +140,11 @@ class TestSpreadsheet_01 < Test::Unit::TestCase
|
|
135
140
|
validates :price, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0 }
|
136
141
|
validates :tot, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0 }
|
137
142
|
end
|
143
|
+
assert_equal([:qty, :price, :tot], result.values.keys)
|
138
144
|
assert_equal(3, result.values.size)
|
139
145
|
result.values.each do |k, vv|
|
140
146
|
assert_equal(5, vv.size)
|
141
147
|
end
|
142
|
-
assert_equal([:qty, :price, :tot], result.values.keys)
|
143
148
|
|
144
149
|
|
145
150
|
|
data/test/test_spreadsheet_03.rb
CHANGED
@@ -1,9 +1,45 @@
|
|
1
|
-
#
|
1
|
+
# test_spreadsheet_03.rb
|
2
|
+
|
3
|
+
# ruby -I lib test/test_spreadsheet_03.rb
|
2
4
|
|
3
5
|
require 'test/unit'
|
4
6
|
require 'goodsheet'
|
5
7
|
|
6
8
|
class TestSpreadsheet_03 < Test::Unit::TestCase
|
7
9
|
|
10
|
+
def setup
|
11
|
+
filepath = File.dirname(__FILE__) + "/fixtures/ss_01.xls"
|
12
|
+
@ss = Goodsheet::Spreadsheet.new(filepath)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_validation_with_nil_values
|
16
|
+
@ss.sheet(4) # "Sheet5"
|
17
|
+
result = @ss.validate do
|
18
|
+
column_names 0 => :qty, 1 => :price, 2 => :tot
|
19
|
+
validates :qty, :price, :tot, numericality: true
|
20
|
+
end
|
21
|
+
assert(!result.valid?)
|
22
|
+
|
23
|
+
result = @ss.validate(:force_nil => 0.0) do
|
24
|
+
column_names 0 => :qty, 1 => :price, 2 => :tot
|
25
|
+
validates :qty, :price, :tot, numericality: true
|
26
|
+
end
|
27
|
+
assert(result.valid?)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_reading_with_nil_values
|
31
|
+
@ss.sheet(4) # "Sheet5"
|
32
|
+
result = @ss.read do
|
33
|
+
column_names 0 => :qty, 1 => :price, 2 => :tot
|
34
|
+
validates :qty, :price, :tot, numericality: true
|
35
|
+
end
|
36
|
+
assert(!result.valid?)
|
37
|
+
|
38
|
+
result = @ss.read(:force_nil => 0.0) do
|
39
|
+
column_names 0 => :qty, 1 => :price, 2 => :tot
|
40
|
+
validates :qty, :price, :tot, numericality: true
|
41
|
+
end
|
42
|
+
assert(result.valid?)
|
43
|
+
end
|
8
44
|
|
9
45
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: goodsheet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Iwan Buetti
|
@@ -94,6 +94,7 @@ files:
|
|
94
94
|
- README.md
|
95
95
|
- Rakefile
|
96
96
|
- goodsheet.gemspec
|
97
|
+
- img/img_01.png
|
97
98
|
- lib/goodsheet.rb
|
98
99
|
- lib/goodsheet/exceptions.rb
|
99
100
|
- lib/goodsheet/read_result.rb
|
@@ -103,6 +104,7 @@ files:
|
|
103
104
|
- lib/goodsheet/validation_errors.rb
|
104
105
|
- lib/goodsheet/version.rb
|
105
106
|
- notes.txt
|
107
|
+
- test/fixtures/example.xls
|
106
108
|
- test/fixtures/fixtures_template.xlsx
|
107
109
|
- test/fixtures/ss_01.xls
|
108
110
|
- test/fixtures/ss_02.csv
|
@@ -139,6 +141,7 @@ signing_key:
|
|
139
141
|
specification_version: 4
|
140
142
|
summary: Extract and validate data from a spreadsheet
|
141
143
|
test_files:
|
144
|
+
- test/fixtures/example.xls
|
142
145
|
- test/fixtures/fixtures_template.xlsx
|
143
146
|
- test/fixtures/ss_01.xls
|
144
147
|
- test/fixtures/ss_02.csv
|