goodsheet 0.3.0 → 0.3.1
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.
- 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
|
+

|
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
|