goodsheet 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 176ca490a132450e22497d73f364833ab25d20b2
4
+ data.tar.gz: adae5acdbcd029f8568633694f9461735978b4ab
5
+ SHA512:
6
+ metadata.gz: f94f54370420e381699a918c4ac19fab8842296a5c1dbfde3851f7906ef5d760f22f528525feef61a4ade1eec51eeb16399cf0fd0a2a2aac7a9ef2dfc4fd0d2d
7
+ data.tar.gz: 404ce30742bf7d5745b45e37515f8fc6a74f4a4f44a394ca3466166f806552dfc60e1e36f650918f13a8a053f12d3bf5a5b271e8cef93327fb6c7bf64721db14
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ lib/goodsheet/spreadsheet_v1.rb
19
+ notex.txt
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in goodsheet.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Iwan Buetti
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # Goodsheet
2
+
3
+ Read and validate the content of a spreadsheet.
4
+ The gem take advantage of wonderful validation methods available in Rails ActiveModel library and the methods of Roo gem to read and validate a spreadsheet.
5
+ Refer to the [official guide](http://guides.rubyonrails.org/active_record_validations.html) for the validation rules.
6
+ Thanks to [Roo gem](https://github.com/Empact/roo) Goodsheet can handle OpenOffice, LibreOffice, Excel (both '.xls' and '.xlsx') and Google spreadsheets.
7
+
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'goodsheet'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install goodsheet
22
+
23
+ ## Usage
24
+
25
+ ### Getting started
26
+
27
+ ```ruby
28
+ ss = Goodsheet::Spreadsheet.new("my_data.xlsx")
29
+ res = ss.read do
30
+ column_names :a => 0, :b => 1
31
+ validates :a, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 10 }
32
+ validates :b, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 100 }
33
+ end
34
+
35
+ res.valid? # => true
36
+ res.values # => {:a => [1.0, 1.0, 1.4], :b => []}
37
+ ```
38
+
39
+ By default:
40
+ * the first sheet is selected
41
+ * one line (the first) is skipped (i'm expeting that is the header line)
42
+
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
+
45
+ ### Advanced usage
46
+
47
+ to do
48
+
49
+
50
+
51
+ Warning:
52
+ * integer numbers are converted to float numbers. Also don't pretend to obtain an integer in validation. This undesired behaviour depend on Roo gem
53
+ * if you import data from a CSV spreadsheet keep in mind that numbers are readed as strings
54
+
55
+
56
+
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
data/goodsheet.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'goodsheet/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "goodsheet"
8
+ spec.version = Goodsheet::VERSION
9
+ spec.authors = ["Iwan Buetti"]
10
+ spec.email = ["iwan.buetti@gmail.com"]
11
+ spec.description = "Little gem that take advantage of Roo gem and Rails ActiveModel validation methods to read and validate the content of a spreadsheet"
12
+ spec.summary = "Extract and validate data from a spreadsheet"
13
+ spec.homepage = "https://github.com/iwan/goodsheet"
14
+ spec.license = "MIT"
15
+ spec.date = '2013-07-19'
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", ">= 1.3.5"
22
+ # spec.add_development_dependency "bundler", "~> 1.4"
23
+ spec.add_development_dependency "rake"
24
+
25
+ spec.add_dependency('roo', '>= 1.12.1') # https://github.com/Empact/roo
26
+ spec.add_dependency('activemodel', '>= 3.2.14')
27
+ spec.add_dependency('google_drive')
28
+ end
@@ -0,0 +1,5 @@
1
+ module Goodsheet
2
+
3
+ class SheetNotFoundError < StandardError; end
4
+
5
+ end
@@ -0,0 +1,28 @@
1
+ module Goodsheet
2
+
3
+ class ReadResult
4
+ attr_reader :values
5
+
6
+ def initialize(errors=ValidationErrors.new)
7
+ @errors = errors
8
+ @values = {}
9
+ end
10
+
11
+ def valid?
12
+ @errors.empty?
13
+ end
14
+
15
+ def invalid?
16
+ !valid?
17
+ end
18
+
19
+ def add(attribute, row, force_nil=nil)
20
+ attribute = attribute.to_sym
21
+ (@values[attribute] ||= []) << (row.send(attribute) || force_nil)
22
+ end
23
+
24
+ def errors
25
+ @errors.array
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,76 @@
1
+ require 'active_model'
2
+
3
+ module Goodsheet
4
+
5
+ class Row
6
+ include ActiveModel::Validations
7
+ include ActiveModel::Conversion
8
+ extend ActiveModel::Naming
9
+
10
+ class << self
11
+ attr_accessor :keys
12
+ end
13
+ @keys = {} # idx => key
14
+
15
+ def initialize(arr)
16
+ arr.each_with_index do |v, idx|
17
+ if k = self.class.keys[idx]
18
+ send("#{k}=", v)
19
+ end
20
+ end
21
+ super()
22
+ end
23
+
24
+ def self.inherit(block)
25
+ c = Class.new(self) do
26
+ @keys = {} # idx => key
27
+ end
28
+ c.class_eval(&block)
29
+ c
30
+ end
31
+
32
+ # Define the position (or index) and the name of columns.
33
+ # There are available three mode to define them:
34
+ # using an hash index to name (like { 0 => :year, 2 => :day })
35
+ # or name to index (like { :year => 0, :day => 2 }) or using an array
36
+ # with the names at desired positions (like [:year, nil, :day]), put a nil
37
+ # at the position
38
+ # The positions are 0-based.
39
+ def self.column_names(param)
40
+ @keys = {}
41
+ if param.is_a? Hash
42
+ if param.first[0].is_a? Integer
43
+ param.each do |idx, name|
44
+ self.keys[idx] = name
45
+ attr_accessor name
46
+ end
47
+ else
48
+ param.each do |name, idx|
49
+ self.keys[idx] = name
50
+ attr_accessor name
51
+ end
52
+ end
53
+ elsif param.is_a? Array
54
+ param.each_with_index do |name, idx|
55
+ if name
56
+ self.keys[idx] = name
57
+ attr_accessor name
58
+ end
59
+ end
60
+
61
+ else
62
+ raise "parameter non valid"
63
+ end
64
+ end
65
+
66
+
67
+ def persisted?
68
+ false
69
+ end
70
+
71
+ # Get the list of attributes (the columns to import)
72
+ def self.row_attributes
73
+ @keys.values
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,130 @@
1
+ require 'roo'
2
+
3
+ module Goodsheet
4
+
5
+ class Spreadsheet < Roo::Spreadsheet
6
+ attr_reader :time_zone, :skip, :header_row, :max_errors, :row_limit
7
+
8
+ # Valid options:
9
+ # :skip : number of rows to skip (default: 1)
10
+ # :header_row : header's row index (0 based, default: 0)
11
+ # :time_zone : time zone string
12
+ def initialize(filename, options={})
13
+ set_options(options)
14
+ @filename = filename
15
+ @ss = Roo::Spreadsheet.open(filename, options)
16
+ end
17
+
18
+
19
+ # idx can be a number or a string
20
+ def sheet(idx, options={})
21
+ set_options(options)
22
+ @ss.sheet(idx)
23
+ check_sheet_exists
24
+ end
25
+
26
+ def sheets
27
+ @ss.sheets
28
+ end
29
+
30
+
31
+ def get_header
32
+ @ss.row(@header_row+1) # because roo in 1-based
33
+ end
34
+
35
+ # Get the currently selected sheet's name
36
+ def name
37
+ @ss.default_sheet
38
+ end
39
+
40
+ def total_rows
41
+ @ss.parse.size
42
+ end
43
+
44
+ def rows_wo_header
45
+ @ss.parse.size - @skip
46
+ end
47
+ alias :rows :rows_wo_header
48
+
49
+ # Valid options:
50
+ # :max_errors : The validation will be stopped if the number of errors exceed max_errors (default: 0 or don't stop)
51
+ # :limit : Max number of rows to validate (default: 0 or validate all rows)
52
+
53
+ #
54
+ def validate(options={}, &block)
55
+ skip = options[:skip] || @skip
56
+ header_row = options[:header_row] || @header_row
57
+ max_errors = options[:max_errors] || @max_errors
58
+ row_limit = options[:row_limit] || @row_limit
59
+
60
+ validation_errors = ValidationErrors.new
61
+
62
+ my_class = options[:my_custom_row_class] || build_my_class(block)
63
+
64
+ line = skip # 0-based, from the top
65
+ @ss.parse[skip..-1].each do |row| # row is an array of elements
66
+ validation_errors.add(line, my_class.new(row))
67
+ break if max_errors>0 && validation_errors.size >= max_errors
68
+ break if row_limit && row_limit>0 && line>=(row_limit+skip-1)
69
+ line +=1
70
+ end
71
+ validation_errors
72
+ end
73
+
74
+
75
+ # Columns must be an hash: labe for values and the column index like {:price => 5}
76
+ def read(options={}, &block)
77
+ skip = options[:skip] || @skip
78
+ header_row = options[:header_row] || @header_row
79
+ max_errors = options[:max_errors] || @max_errors
80
+ row_limit = options[:row_limit] || @row_limit
81
+ force_nil = options[:force_nil]
82
+
83
+ my_class = build_my_class(block)
84
+ options[:my_custom_row_class] = my_class
85
+ read_result = ReadResult.new(validate(options){ block })
86
+ return read_result if read_result.invalid?
87
+
88
+ line = skip # 0-based, from the top
89
+ @ss.parse[skip..-1].each do |row| # row is an array of elements
90
+ my_class.row_attributes.each do |attribute|
91
+ read_result.add(attribute, my_class.new(row), force_nil)
92
+ end
93
+ break if row_limit && row_limit>0 && line>=(row_limit + skip - 1)
94
+ line +=1
95
+ end
96
+ read_result
97
+ end
98
+
99
+
100
+ private
101
+
102
+ def build_my_class(block)
103
+ Object.const_set get_custom_row_class_name, Row.inherit(block)
104
+ end
105
+
106
+ def check_sheet_exists
107
+ begin
108
+ @ss.cell(1,1)
109
+ rescue ArgumentError => e
110
+ raise Goodsheet::SheetNotFoundError
111
+ rescue RangeError => e
112
+ raise Goodsheet::SheetNotFoundError
113
+ end
114
+ end
115
+
116
+ def get_custom_row_class_name
117
+ "CustRow_#{(Time.now.to_f*(10**10)).to_i}"
118
+ end
119
+
120
+ def set_options(options)
121
+ @time_zone = options.delete(:zone) || "Rome"
122
+ @skip = options.delete(:skip) || 1
123
+ @header_row = options.delete(:header_row) || 0
124
+ @max_errors = options.delete(:max_errors) || 0
125
+ @row_limit = options.delete(:row_limit) || 0
126
+ end
127
+ end
128
+ end
129
+
130
+
@@ -0,0 +1,13 @@
1
+ module Goodsheet
2
+
3
+ class ValidationError
4
+ def initialize(line, val_err)
5
+ @line = line
6
+ @val_err = val_err
7
+ end
8
+
9
+ def to_s
10
+ "line #{@line} is invalid for the following reason(s): #{@val_err.full_messages.join(', ')}"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module Goodsheet
2
+
3
+ class ValidationErrors
4
+ attr_reader :array
5
+
6
+ def initialize
7
+ @array = []
8
+ end
9
+
10
+ def add(line_number, row)
11
+ @array << ValidationError.new(line_number+1, row.errors) if row.invalid?
12
+ end
13
+
14
+ def empty?
15
+ @array.empty?
16
+ end
17
+
18
+ def size
19
+ @array.size
20
+ end
21
+
22
+ def to_s
23
+ @array.to_s
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Goodsheet
2
+ VERSION = "0.2.1"
3
+ end
data/lib/goodsheet.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "goodsheet/version"
2
+
3
+ module Goodsheet
4
+ autoload :Row, 'goodsheet/row'
5
+ autoload :SheetNotFoundError, 'goodsheet/exceptions'
6
+ autoload :ReadResult, 'goodsheet/read_result'
7
+ autoload :Row, 'goodsheet/row'
8
+ autoload :Spreadsheet, 'goodsheet/spreadsheet'
9
+ autoload :ValidationError, 'goodsheet/validation_error'
10
+ autoload :ValidationErrors, 'goodsheet/validation_errors'
11
+ autoload :Version, 'goodsheet/version'
12
+ end
data/notes.txt ADDED
@@ -0,0 +1,68 @@
1
+
2
+ Usage:
3
+
4
+ ss = Goodsheet::Spreadsheet.new("data.xls")
5
+ ss.sheet(0) # selecting the first sheet
6
+ result = ss.read(:skip => 1) do
7
+ column_at 0, :year, :presence => true, :numericality => { :only_integer => false, :greater_than_or_equal_to => 2000, :less_than_or_equal_to => 2100 }
8
+ end
9
+ result.values # => { :year => [2012, 2012, 2012, 2013] }
10
+
11
+ # when the validation fails:
12
+ ss.sheet(1) # selecting the second sheet
13
+ result = ss.read(:skip => 1) do
14
+ column_at 0, :year, :presence => true, :numericality => { :only_integer => false, :greater_than_or_equal_to => 2000, :less_than_or_equal_to => 2100 }
15
+ end
16
+
17
+ result.values # => {}
18
+ result.valid? # => false
19
+ result.errors # => return an array of ValidationError objects
20
+
21
+ # --------------------
22
+
23
+ ss = Goodsheet::Spreadsheet.new("data.xls")
24
+ ss.sheets # => ["Sheet1", "Sheet2"]
25
+ ss.sheet(0) # selecting the first sheet
26
+ ss.name # => "Sheet1"
27
+ ss.sheet("Sheet1") # alternative way to select a sheet (by name)
28
+ ss.sheet("Sheet1", :header_row => 1) # you can specify the row index (0-based) used for header
29
+
30
+
31
+ Validation
32
+
33
+
34
+ class MyRow < Row
35
+ attr_accessor :year
36
+ validates :year, :presence => true, :numericality => { :only_integer => false, :greater_than_or_equal_to => 2000, :less_than_or_equal_to => 2100 }
37
+ end
38
+
39
+ The validation method will return an array of validation errors:
40
+ validation_errors = ss.validate do |row|
41
+ MyRow.new(:year => row[0])
42
+ end
43
+
44
+ You can limit the validation errors using :max_errors option:
45
+ ss.validate(:max_errors => 100) do |row|
46
+ # ...
47
+ end
48
+
49
+ or limit the numbers of rows to read:
50
+ ss.validate(:limit => 50) do |row|
51
+ # ...
52
+ end
53
+
54
+
55
+ Reading
56
+
57
+ The read method allow to validate and put the content in an hash:
58
+ values = @ss.read do |row|
59
+ MyRow.new(:year => row[0])
60
+ end
61
+
62
+ # =>
63
+
64
+ The :force_nil option allow to force empties cells to a value, and the :skip to skip a desired number of rows:
65
+ values = @ss.read(:force_nil => 0.0, :skip => 6) do |row|
66
+ MyRow.new(:year => row[0])
67
+ end
68
+
Binary file
@@ -0,0 +1 @@
1
+ year,month,day,wday,1 or 2 or 3,value
Binary file
Binary file
Binary file
Binary file
data/test/test_row.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'test/unit'
2
+ require 'goodsheet'
3
+
4
+ class TestRow < Test::Unit::TestCase
5
+
6
+ def test_column_names
7
+ assert_raise RuntimeError do
8
+ Goodsheet::Row.column_names(6)
9
+ end
10
+
11
+ Goodsheet::Row.column_names([:a, nil, :b, :c])
12
+ assert_equal(Goodsheet::Row.keys, {0 => :a, 2 => :b, 3 => :c})
13
+
14
+ Goodsheet::Row.column_names(:a => 0, :b => 2, :c => 3)
15
+ assert_equal(Goodsheet::Row.keys, {0 => :a, 2 => :b, 3 => :c})
16
+
17
+
18
+ Goodsheet::Row.column_names(0 => :a, 2 => :b, 3 => :c)
19
+ assert_equal(Goodsheet::Row.keys, {0 => :a, 2 => :b, 3 => :c})
20
+ end
21
+
22
+
23
+ end
@@ -0,0 +1,150 @@
1
+ require 'test/unit'
2
+ require 'goodsheet'
3
+
4
+ class TestSpreadsheet_01 < Test::Unit::TestCase
5
+
6
+ def setup
7
+ filepath = File.dirname(__FILE__) + "/fixtures/ss_01.xls"
8
+ @ss = Goodsheet::Spreadsheet.new(filepath)
9
+ end
10
+
11
+ def test_sheets
12
+ assert_equal(%w(Sheet1 Sheet2 Sheet3 Sheet4), @ss.sheets)
13
+ end
14
+
15
+ def test_failed_sheet_selection
16
+ assert_raise Goodsheet::SheetNotFoundError do
17
+ @ss.sheet(4)
18
+ end
19
+ assert_raise Goodsheet::SheetNotFoundError do
20
+ @ss.sheet("Sheet999")
21
+ end
22
+ end
23
+
24
+ def test_sheet_selection_and_name
25
+ # by default the first sheet will be selected
26
+ assert_equal("Sheet1", @ss.name)
27
+
28
+ @ss.sheet(0)
29
+ assert_equal("Sheet1", @ss.name)
30
+
31
+ @ss.sheet(1)
32
+ assert_equal("Sheet2", @ss.name)
33
+
34
+ @ss.sheet(2)
35
+ assert_equal("Sheet3", @ss.name)
36
+
37
+ @ss.sheet(3)
38
+ assert_equal("Sheet4", @ss.name)
39
+ end
40
+
41
+ def test_get_header_wo_options
42
+ @ss.sheet(0)
43
+ assert_equal(%w(A B C D), @ss.get_header)
44
+ end
45
+
46
+ def test_get_header_w_options
47
+ @ss.sheet("Sheet3", :header_row => 1)
48
+ assert_equal(%w(K J), @ss.get_header)
49
+ end
50
+
51
+ def test_rows
52
+ assert_equal(5, @ss.total_rows)
53
+ assert_equal(4, @ss.rows)
54
+
55
+ @ss.sheet(0, :skip => 0)
56
+ assert_equal(5, @ss.total_rows)
57
+ assert_equal(5, @ss.rows)
58
+
59
+ @ss.sheet(0, :skip => 1)
60
+ assert_equal(5, @ss.total_rows)
61
+ assert_equal(4, @ss.rows)
62
+ end
63
+
64
+ def test_validate_no_errors
65
+ validation_errors = @ss.validate do
66
+ column_names :year => 0
67
+ validates :year, :presence => true, :numericality => { :only_integer => false, :greater_than_or_equal_to => 2000, :less_than_or_equal_to => 2100 }
68
+ end
69
+ assert(validation_errors.empty?)
70
+ end
71
+
72
+
73
+ def test_validate_four_errors
74
+ @ss.sheet(1)
75
+ validation_errors = @ss.validate do
76
+ column_names 0 => :a1, 1 => :a2, 2 => :sum, 3 => :str
77
+ validates :a1, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
78
+ validates :a2, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
79
+ validates :sum, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
80
+ validates :str, :presence => true, :inclusion => { :in => %w(A B D) }
81
+ end
82
+ assert_equal(4, validation_errors.size)
83
+
84
+
85
+ # limit the validation errors to 2
86
+ validation_errors = @ss.validate(:max_errors => 2) do
87
+ column_names 0 => :a1, 1 => :a2, 2 => :sum, 3 => :str
88
+ validates :a1, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
89
+ validates :a2, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
90
+ validates :sum, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
91
+ validates :str, :presence => true, :inclusion => { :in => %w(A B D) }
92
+ end
93
+ assert_equal(2, validation_errors.size)
94
+
95
+
96
+ # read only 3 rows
97
+ validation_errors = @ss.validate(:max_errors => 0, :row_limit => 3) do
98
+ column_names 0 => :a1, 1 => :a2, 2 => :sum, 3 => :str
99
+ validates :a1, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
100
+ validates :a2, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
101
+ validates :sum, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 6.0 }
102
+ validates :str, :presence => true, :inclusion => { :in => %w(A B D) }
103
+ end
104
+ assert_equal(3, validation_errors.size)
105
+ end
106
+
107
+
108
+ def test_read_sheet4
109
+ @ss.sheet(3)
110
+
111
+ result = @ss.read(:row_limit => 5) do
112
+ column_names 0 => :qty, 1 => :price, 2 => :tot
113
+ validates :qty, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0 }
114
+ validates :price, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0 }
115
+ validates :tot, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0 }
116
+ end
117
+ assert_equal(3, result.values.size)
118
+ result.values.each do |k, vv|
119
+ assert_equal(5, vv.size)
120
+ end
121
+ assert_equal([:qty, :price, :tot], result.values.keys)
122
+
123
+
124
+
125
+ result = @ss.read(:force_nil => 0.0) do
126
+ column_names 0 => :qty, 1 => :price, 2 => :tot
127
+ validates :qty, :allow_nil => true, :numericality => { :greater_than_or_equal_to => 0.0 }
128
+ validates :price, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0 }
129
+ validates :tot, :allow_nil => true, :numericality => { :greater_than_or_equal_to => 0.0 }
130
+ end
131
+ assert_equal(3, result.values.size)
132
+ result.values.each do |k, vv|
133
+ assert_equal(6, vv.size)
134
+ end
135
+
136
+ result = @ss.read(:force_nil => 0.0, :skip => 6) do
137
+ column_names 0 => :qty, 1 => :price, 2 => :tot
138
+ validates :qty, :allow_nil => true, :numericality => { :greater_than_or_equal_to => 0.0 }
139
+ validates :price, :presence => true, :numericality => { :greater_than_or_equal_to => 0.0 }
140
+ validates :tot, :allow_nil => true, :numericality => { :greater_than_or_equal_to => 0.0 }
141
+ end
142
+ assert_equal(3, result.values.size)
143
+ result.values.each do |k, vv|
144
+ assert_equal(1, vv.size)
145
+ end
146
+
147
+
148
+ end
149
+
150
+ end
@@ -0,0 +1,111 @@
1
+ require 'test/unit'
2
+ require 'goodsheet'
3
+
4
+ class TestSpreadsheet_02 < Test::Unit::TestCase
5
+
6
+ def get_filepath(filename)
7
+ File.dirname(__FILE__) + "/fixtures/#{filename}"
8
+ end
9
+
10
+ def test_xls_validation
11
+ validate(get_filepath("ss_02.xls"))
12
+ end
13
+
14
+ def test_xls_reading
15
+ read(get_filepath("ss_02.xls"))
16
+ end
17
+
18
+ def test_xlsx_validation
19
+ validate(get_filepath("ss_02.xlsx"))
20
+ end
21
+
22
+ def test_xlsx_reading
23
+ read(get_filepath("ss_02.xlsx"))
24
+ end
25
+
26
+ # in CSV files all numbers are converted to strings, so the validation will not pass...
27
+ # def test_csv_validation
28
+ # validate(get_filepath("ss_02.csv"))
29
+ # end
30
+
31
+ # def test_csv_reading
32
+ # read(get_filepath("ss_02.csv"))
33
+ # end
34
+
35
+ # parsing of '.ods' file is very slow for "large" files (a spredsheet with 366 lines take 65'' to be parsing on my computer...)
36
+ # def test_ods_validation
37
+ # validate(get_filepath("ss_02.ods"))
38
+ # end
39
+
40
+ # parsing of '.ods' file is very slow for "large" files (a spredsheet with 366 lines take 65'' to be parsing on my computer...)
41
+ # def test_ods_reading
42
+ # read(get_filepath("ss_02.ods"))
43
+ # end
44
+
45
+ # def test_google_ss_validation
46
+ # validate("0Ao3aUE9UFTaPdHBsYVhpU1FCaEVKMndkN1AzOVFYUUE")
47
+ # end
48
+
49
+ # def test_google_ss_reading
50
+ # read("0Ao3aUE9UFTaPdHBsYVhpU1FCaEVKMndkN1AzOVFYUUE")
51
+ # end
52
+
53
+
54
+ def validate(filepath)
55
+ ss = Goodsheet::Spreadsheet.new(filepath)
56
+ ss.sheet(0)
57
+ errors = ss.validate(:skip => 1) do
58
+ column_names 0 => :year, 1 => :month, 2 => :day, 3 => :wday, 4 => :num, 5 => :v
59
+ validates :year, :allow_nil => false, :numericality => { :greater_than_or_equal_to => 2000, :less_than_or_equal_to => 2020 }
60
+ validates :month, :allow_nil => false, :numericality => { :greater_than_or_equal_to => 1, :less_than_or_equal_to => 12 }
61
+ validates :day, :allow_nil => false, :numericality => { :greater_than_or_equal_to => 1, :less_than_or_equal_to => 31 }
62
+ validates :wday, inclusion: { in: %w(Mon Tue Wed Thu Fri Sat Sun) }
63
+ validates :num, inclusion: { in: [1, 2, 3] }
64
+ validates :v, :allow_nil => false, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 100.0 }
65
+ end
66
+
67
+ assert_equal(0, errors.size)
68
+ end
69
+
70
+ def read(filepath)
71
+ ss = Goodsheet::Spreadsheet.new(filepath)
72
+ ss.sheet(0)
73
+ result = ss.read(:skip => 1) do
74
+ column_names 0 => :year, 1 => :month, 2 => :day, 3 => :wday, 4 => :num, 5 => :v
75
+ validates :year, :allow_nil => false, :numericality => { :greater_than_or_equal_to => 2000, :less_than_or_equal_to => 2020 }
76
+ validates :month, :allow_nil => false, :numericality => { :greater_than_or_equal_to => 1, :less_than_or_equal_to => 12 }
77
+ validates :day, :allow_nil => false, :numericality => { :greater_than_or_equal_to => 1, :less_than_or_equal_to => 31 }
78
+ validates :wday, inclusion: { in: %w(Mon Tue Wed Thu Fri Sat Sun) }
79
+ validates :num, inclusion: { in: [1, 2, 3] }
80
+ validates :v, :allow_nil => false, :numericality => { :greater_than_or_equal_to => 0.0, :less_than_or_equal_to => 100.0 }
81
+ end
82
+
83
+ assert_equal(0, result.errors.size)
84
+ assert_equal(6, result.values.size)
85
+ result.values.each do |k, v|
86
+ assert_equal(365, v.size)
87
+ end
88
+
89
+ result.values[:year].each do |y|
90
+ assert_equal(2013, y) # y is 2013.0
91
+ end
92
+ result.values[:month].each do |v|
93
+ assert(v.between?(1,12))
94
+ end
95
+ result.values[:day].each do |v|
96
+ assert(v.between?(1,31))
97
+ end
98
+ wdays = %w(Mon Tue Wed Thu Fri Sat Sun)
99
+ result.values[:wday].each do |v|
100
+ assert wdays.include? v
101
+ end
102
+ result.values[:num].each do |v|
103
+ assert [1,2,3].include? v
104
+ end
105
+ result.values[:v].each do |v|
106
+ assert(v.between?(0, 100.0))
107
+ end
108
+ end
109
+
110
+
111
+ end
@@ -0,0 +1,9 @@
1
+ # test_spreadsheet_01.rb
2
+
3
+ require 'test/unit'
4
+ require 'goodsheet'
5
+
6
+ class TestSpreadsheet_03 < Test::Unit::TestCase
7
+
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: goodsheet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Iwan Buetti
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.3.5
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.3.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: roo
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.12.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.12.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: activemodel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 3.2.14
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 3.2.14
69
+ - !ruby/object:Gem::Dependency
70
+ name: google_drive
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Little gem that take advantage of Roo gem and Rails ActiveModel validation
84
+ methods to read and validate the content of a spreadsheet
85
+ email:
86
+ - iwan.buetti@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - goodsheet.gemspec
97
+ - lib/goodsheet.rb
98
+ - lib/goodsheet/exceptions.rb
99
+ - lib/goodsheet/read_result.rb
100
+ - lib/goodsheet/row.rb
101
+ - lib/goodsheet/spreadsheet.rb
102
+ - lib/goodsheet/validation_error.rb
103
+ - lib/goodsheet/validation_errors.rb
104
+ - lib/goodsheet/version.rb
105
+ - notes.txt
106
+ - test/fixtures/fixtures_template.xlsx
107
+ - test/fixtures/ss_01.xls
108
+ - test/fixtures/ss_02.csv
109
+ - test/fixtures/ss_02.ods
110
+ - test/fixtures/ss_02.xls
111
+ - test/fixtures/ss_02.xlsx
112
+ - test/fixtures/ss_04.xlsx
113
+ - test/test_row.rb
114
+ - test/test_spreadsheet_01.rb
115
+ - test/test_spreadsheet_02.rb
116
+ - test/test_spreadsheet_03.rb
117
+ homepage: https://github.com/iwan/goodsheet
118
+ licenses:
119
+ - MIT
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.0.3
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Extract and validate data from a spreadsheet
141
+ test_files:
142
+ - test/fixtures/fixtures_template.xlsx
143
+ - test/fixtures/ss_01.xls
144
+ - test/fixtures/ss_02.csv
145
+ - test/fixtures/ss_02.ods
146
+ - test/fixtures/ss_02.xls
147
+ - test/fixtures/ss_02.xlsx
148
+ - test/fixtures/ss_04.xlsx
149
+ - test/test_row.rb
150
+ - test/test_spreadsheet_01.rb
151
+ - test/test_spreadsheet_02.rb
152
+ - test/test_spreadsheet_03.rb
153
+ has_rdoc: