goodsheet 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/goodsheet.rb +1 -0
- data/lib/goodsheet/read_result.rb +88 -21
- data/lib/goodsheet/row.rb +78 -19
- data/lib/goodsheet/spreadsheet.rb +27 -40
- data/lib/goodsheet/validation_error.rb +3 -3
- data/lib/goodsheet/validation_errors.rb +9 -31
- data/lib/goodsheet/version.rb +1 -1
- data/notes.txt +13 -0
- data/test/fixtures/example.xls +0 -0
- data/test/test_row.rb +6 -4
- 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: aef27ae57a44343de4b43b8118c3a4dba7940021
|
4
|
+
data.tar.gz: ccd478aad49271a61e1e4278cbfd95ea9a226b17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf0b9d857e4eb5a66cbf72a6c205e1ab4f517fd93dbd40e42382e1b1f34d0b0e74813bdf8bb5e3e48c554933297ccc31160d39e83e2d4b311825ddf73c32d897
|
7
|
+
data.tar.gz: f866ccb2f33ffa9618f12c597a1ab4c9d88282eaf6ffd0485d0e262f78cc506c424a58cc97f2de569e385aa88e2f517b3ee85fd443a955c35e9fe4c808bd7e6e
|
data/lib/goodsheet.rb
CHANGED
@@ -1,11 +1,30 @@
|
|
1
1
|
module Goodsheet
|
2
|
-
|
3
2
|
class ReadResult
|
4
|
-
attr_reader :
|
3
|
+
attr_reader :errors
|
5
4
|
|
6
|
-
def initialize(
|
7
|
-
@
|
8
|
-
|
5
|
+
def initialize(row_attributes, max_errors, collector=:a_arr)
|
6
|
+
@row_attributes = row_attributes
|
7
|
+
case collector
|
8
|
+
when :hash
|
9
|
+
@hash = {}
|
10
|
+
when :a_arr
|
11
|
+
@a_arr = []
|
12
|
+
when :h_arr
|
13
|
+
@h_arr = []
|
14
|
+
end
|
15
|
+
@errors = ValidationErrors.new(max_errors)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def add(row_number, row)
|
20
|
+
if defined? @hash
|
21
|
+
row.to_hash.each_pair do |k, v|
|
22
|
+
(@hash[k]||=[]) << v # @hash = {:a => ["dog, "cat", ...], :b => [3, 7, ...]}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
@a_arr << row.to_a if defined? @a_arr # @a_arr = [["dog", 3], ["cat", 7], ...]
|
26
|
+
@h_arr << row.to_hash if defined? @h_arr # @h_arr = [{:a => "dog", :b => 3}, {:a => "cat", :b => 7}, ...]
|
27
|
+
@errors.add(row_number, row)
|
9
28
|
end
|
10
29
|
|
11
30
|
def valid?
|
@@ -16,33 +35,81 @@ module Goodsheet
|
|
16
35
|
!valid?
|
17
36
|
end
|
18
37
|
|
19
|
-
def add(attribute, row)
|
20
|
-
attribute = attribute.to_sym
|
21
|
-
(@vv[attribute] ||= []) << row.send(attribute)
|
22
|
-
end
|
23
|
-
|
24
38
|
def values(format=:columns)
|
25
|
-
|
26
|
-
|
39
|
+
if defined? @hash
|
40
|
+
return [] if @hash.empty?
|
41
|
+
values_for_hash(format)
|
42
|
+
|
43
|
+
elsif defined? @h_arr
|
44
|
+
return [] if @h_arr.empty?
|
45
|
+
values_for_h_arr(format)
|
27
46
|
|
47
|
+
elsif defined? @a_arr # @a_arr = [["dog", 3], ["cat", 7], ...]
|
48
|
+
return [] if @a_arr.empty?
|
49
|
+
values_for_a_arr(format)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def values_for_hash(format)
|
28
54
|
case format
|
29
55
|
when :columns
|
30
|
-
@
|
56
|
+
@hash
|
31
57
|
|
32
58
|
when :rows_array
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
59
|
+
a1 = []
|
60
|
+
@hash.values.first.size.times do |i|
|
61
|
+
a1 << @row_attributes.collect{|a| @hash[a][i]}
|
62
|
+
end
|
63
|
+
a1
|
64
|
+
|
65
|
+
when :rows_hash
|
66
|
+
a1 = []
|
67
|
+
@hash.values.first.size.times do |i|
|
68
|
+
a2 = {}
|
69
|
+
@row_attributes.each{|a| a2[a] = @hash[a][i]}
|
70
|
+
a1 << a2
|
71
|
+
end
|
72
|
+
a1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def values_for_a_arr(format)
|
77
|
+
case format
|
78
|
+
when :columns
|
79
|
+
h = {}
|
80
|
+
@row_attributes.each_with_index do |attrib, i|
|
81
|
+
h[attrib] = @a_arr.map{|e| e[i]}
|
37
82
|
end
|
83
|
+
h
|
84
|
+
|
85
|
+
when :rows_array
|
86
|
+
@a_arr
|
38
87
|
|
39
88
|
when :rows_hash
|
40
|
-
|
41
|
-
|
42
|
-
@vv.keys.each{|k| hh[k] = @vv[k][i1] }
|
43
|
-
hh
|
89
|
+
@a_arr.map do |arr|
|
90
|
+
Hash[@row_attributes.map.with_index{|attrib,i| [attrib, arr[i]]}]
|
44
91
|
end
|
45
92
|
end
|
46
93
|
end
|
94
|
+
|
95
|
+
def values_for_h_arr(format)
|
96
|
+
case format
|
97
|
+
when :columns
|
98
|
+
keys = @h_arr.first.keys
|
99
|
+
h = {}
|
100
|
+
@h_arr.each do |e|
|
101
|
+
keys.each do |key|
|
102
|
+
(h[key] ||= []) << e[key]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
h
|
106
|
+
|
107
|
+
when :rows_array
|
108
|
+
@h_arr.map(&:values)
|
109
|
+
|
110
|
+
when :rows_hash
|
111
|
+
@h_arr
|
112
|
+
end
|
113
|
+
end
|
47
114
|
end
|
48
115
|
end
|
data/lib/goodsheet/row.rb
CHANGED
@@ -4,18 +4,21 @@ module Goodsheet
|
|
4
4
|
|
5
5
|
class Row
|
6
6
|
include ActiveModel::Validations
|
7
|
-
include ActiveModel::Conversion
|
8
|
-
extend ActiveModel::Naming
|
9
7
|
|
10
8
|
class << self
|
11
|
-
attr_accessor :keys
|
9
|
+
attr_accessor :keys, :defaults
|
12
10
|
end
|
13
|
-
@keys = {} # idx => key
|
11
|
+
# @keys = {} # idx => key: {0=>:name, 1=>:quantity, 2=>:price, 3=>:total, 6=>:password}
|
12
|
+
# @defaults = {} # name => default_value
|
14
13
|
|
15
14
|
def initialize(arr, nil_value=nil)
|
15
|
+
# puts "--- arr: #{arr.inspect}"
|
16
|
+
if (diff=self.class.keys.size-arr.size)>0
|
17
|
+
arr = arr + Array.new(diff, nil)
|
18
|
+
end
|
16
19
|
arr.each_with_index do |v, idx|
|
17
20
|
if k = self.class.keys[idx]
|
18
|
-
send
|
21
|
+
send "#{k}=", v || self.class.defaults[k] || nil_value
|
19
22
|
end
|
20
23
|
end
|
21
24
|
super()
|
@@ -23,12 +26,30 @@ module Goodsheet
|
|
23
26
|
|
24
27
|
def self.inherit(block)
|
25
28
|
c = Class.new(self) do
|
26
|
-
@keys = {} # idx => key
|
29
|
+
@keys = {} # idx => key: {0=>:name, 1=>:quantity, 2=>:price, 3=>:total, 6=>:password}
|
30
|
+
@defaults = {} # name => default_value
|
27
31
|
end
|
28
32
|
c.class_eval(&block)
|
29
33
|
c
|
30
34
|
end
|
31
35
|
|
36
|
+
# using indexes: defaults 1 => 0.0, 2 => ""
|
37
|
+
# using names: defaults :qty => 0.0, :name => ""
|
38
|
+
|
39
|
+
def self.column_defaults(*attr)
|
40
|
+
raise ArgumentError, 'You have to pass at least one attribute' if attr.empty?
|
41
|
+
if attr[0].is_a? Array
|
42
|
+
@defaults = Hash[attr[0].map.with_index{|v, i| [self.keys[i], v]}]
|
43
|
+
|
44
|
+
elsif attr[0].is_a? Hash
|
45
|
+
@defaults = Hash[attr[0].to_a.collect{|a| [(a[0].is_a?(Fixnum)) ? (self.keys[a[0]]) : a[0], a[1]]}]
|
46
|
+
|
47
|
+
else
|
48
|
+
@defaults = Hash[attr.map.with_index{|v, i| [self.keys[i], v]}]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
32
53
|
# Define the position (or index) and the name of columns.
|
33
54
|
# You have four ways to define them:
|
34
55
|
# using an hash index-to-name (like { 0 => :year, 2 => :day })
|
@@ -42,22 +63,25 @@ module Goodsheet
|
|
42
63
|
raise ArgumentError, 'You have to pass at least one attribute' if attr.empty?
|
43
64
|
if attr[0].is_a? Array
|
44
65
|
attr[0].each_with_index do |name, idx|
|
45
|
-
if name
|
46
|
-
|
47
|
-
|
48
|
-
|
66
|
+
self.set_key_pair(idx, name) if name
|
67
|
+
# if name
|
68
|
+
# self.keys[idx] = name
|
69
|
+
# attr_accessor name
|
70
|
+
# end
|
49
71
|
end
|
50
72
|
|
51
73
|
elsif attr[0].is_a? Hash
|
52
74
|
if attr[0].first[0].is_a? Integer
|
53
75
|
attr[0].each do |idx, name|
|
54
|
-
self.
|
55
|
-
|
76
|
+
self.set_key_pair(idx, name)
|
77
|
+
# self.keys[idx] = name
|
78
|
+
# attr_accessor name
|
56
79
|
end
|
57
80
|
else
|
58
81
|
attr[0].each do |name, idx|
|
59
|
-
self.
|
60
|
-
|
82
|
+
self.set_key_pair(idx, name)
|
83
|
+
# self.keys[idx] = name
|
84
|
+
# attr_accessor name
|
61
85
|
end
|
62
86
|
end
|
63
87
|
|
@@ -65,8 +89,9 @@ module Goodsheet
|
|
65
89
|
attr.each_with_index do |name, idx|
|
66
90
|
if name
|
67
91
|
name = name.to_s.gsub(" ", "_").to_sym unless name.is_a? Symbol
|
68
|
-
self.
|
69
|
-
|
92
|
+
self.set_key_pair(idx, name)
|
93
|
+
# self.keys[idx] = name
|
94
|
+
# attr_accessor name
|
70
95
|
end
|
71
96
|
end
|
72
97
|
end
|
@@ -74,13 +99,47 @@ module Goodsheet
|
|
74
99
|
end
|
75
100
|
|
76
101
|
|
77
|
-
def
|
78
|
-
|
102
|
+
def self.set_key_pair(idx, name)
|
103
|
+
self.keys[idx] = name
|
104
|
+
attr_accessor name
|
79
105
|
end
|
80
106
|
|
107
|
+
|
108
|
+
# def persisted?
|
109
|
+
# false
|
110
|
+
# end
|
111
|
+
|
81
112
|
# Get the list of attributes (the columns to import)
|
82
|
-
def
|
113
|
+
def Row.attributes
|
83
114
|
@keys.values
|
84
115
|
end
|
116
|
+
|
117
|
+
def attributes
|
118
|
+
self.class.attributes
|
119
|
+
end
|
120
|
+
|
121
|
+
def Row.extend_with(block)
|
122
|
+
class_name = "CustRow_#{(Time.now.to_f*(10**10)).to_i}"
|
123
|
+
Object.const_set class_name, Row.inherit(block)
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_hash
|
127
|
+
Hash[self.class.attributes.map{|a| [a, self.send(a)]}]
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_a
|
131
|
+
self.class.attributes.map{|a| self.send(a)}
|
132
|
+
end
|
85
133
|
end
|
86
134
|
end
|
135
|
+
|
136
|
+
# class Row01 < Goodsheet::Row
|
137
|
+
# column_names :filename => 0, :size => 1
|
138
|
+
# validates :size, :numericality => true
|
139
|
+
# end
|
140
|
+
|
141
|
+
# r = Row01.new(["pippo", "e"])
|
142
|
+
# p r.valid?
|
143
|
+
# puts r.class.attributes.inspect
|
144
|
+
# puts r.to_hash.inspect
|
145
|
+
# puts r.to_a.inspect
|
@@ -110,23 +110,15 @@ module Goodsheet
|
|
110
110
|
# @yield Column settings and validation rules
|
111
111
|
# @return [ValidationErrors] Validation errors
|
112
112
|
def validate(options={}, &block)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
my_class = options[:my_custom_row_class] || build_my_class(block)
|
121
|
-
|
122
|
-
line = @s_opts[index][:skip] # 0-based, from the top
|
123
|
-
@ss.parse[@s_opts[index][:skip]..-1].each do |row| # row is an array of elements
|
124
|
-
validation_errors.add(line, my_class.new(row, force_nil))
|
125
|
-
break if max_errors>0 && validation_errors.size >= max_errors
|
126
|
-
break if row_limit && row_limit>0 && line>=(row_limit+@s_opts[index][:skip]-1)
|
127
|
-
line +=1
|
113
|
+
set_variables(options)
|
114
|
+
errors = ValidationErrors.new(@max_errors)
|
115
|
+
row_class = Row.extend_with(block)
|
116
|
+
|
117
|
+
last_row = @row_limit.zero? ? @ss.last_row : min(@ss.last_row, @row_limit+@skip)
|
118
|
+
(@skip+1).upto(last_row) do |r|
|
119
|
+
break unless errors.add(r, row_class.new(@ss.row(r), @force_nil))
|
128
120
|
end
|
129
|
-
|
121
|
+
errors
|
130
122
|
end
|
131
123
|
|
132
124
|
|
@@ -141,24 +133,13 @@ module Goodsheet
|
|
141
133
|
# @yield Column settings and validation rules
|
142
134
|
# @return [ReadResult] The result
|
143
135
|
def read(options={}, &block)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
options[:my_custom_row_class] = my_class
|
152
|
-
read_result = ReadResult.new(validate(options){ block })
|
153
|
-
return read_result if read_result.invalid?
|
154
|
-
|
155
|
-
line = skip # 0-based, from the top
|
156
|
-
@ss.parse[skip..-1].each do |row| # row is an array of elements
|
157
|
-
my_class.row_attributes.each do |attribute|
|
158
|
-
read_result.add(attribute, my_class.new(row, force_nil))
|
159
|
-
end
|
160
|
-
break if row_limit && row_limit>0 && line>=(row_limit + skip - 1)
|
161
|
-
line +=1
|
136
|
+
set_variables(options)
|
137
|
+
row_class = Row.extend_with(block)
|
138
|
+
read_result = ReadResult.new(row_class.attributes, @max_errors, options[:collector]||:a_arr)
|
139
|
+
|
140
|
+
last_row = @row_limit.zero? ? @ss.last_row : min(@ss.last_row, @row_limit+@skip)
|
141
|
+
(@skip+1).upto(last_row) do |r|
|
142
|
+
break unless read_result.add(r, row_class.new(@ss.row(r), @force_nil))
|
162
143
|
end
|
163
144
|
read_result
|
164
145
|
end
|
@@ -166,9 +147,14 @@ module Goodsheet
|
|
166
147
|
|
167
148
|
private
|
168
149
|
|
169
|
-
|
170
|
-
|
171
|
-
|
150
|
+
|
151
|
+
|
152
|
+
def set_variables(options)
|
153
|
+
@skip = options[:skip] || @s_opts[index][:skip]
|
154
|
+
@header_row = options[:header_row] || @s_opts[index][:header_row]
|
155
|
+
@max_errors = options[:max_errors] || @s_opts[index][:max_errors]
|
156
|
+
@row_limit = options[:row_limit] || @s_opts[index][:row_limit] || 0
|
157
|
+
@force_nil = options[:force_nil] || @s_opts[index][:force_nil]
|
172
158
|
end
|
173
159
|
|
174
160
|
def select_sheet_options(idx)
|
@@ -189,9 +175,6 @@ module Goodsheet
|
|
189
175
|
end
|
190
176
|
end
|
191
177
|
|
192
|
-
def get_custom_row_class_name
|
193
|
-
"CustRow_#{(Time.now.to_f*(10**10)).to_i}"
|
194
|
-
end
|
195
178
|
|
196
179
|
def set_sheet_options(idx, options)
|
197
180
|
i = idx.is_a?(Integer) ? idx : @ss.sheets.index(idx)
|
@@ -203,6 +186,10 @@ module Goodsheet
|
|
203
186
|
:force_nil => options[:force_nil] || @s_opts[i][:force_nil] || nil
|
204
187
|
}
|
205
188
|
end
|
189
|
+
|
190
|
+
def min(a,b)
|
191
|
+
a<b ? a : b
|
192
|
+
end
|
206
193
|
end
|
207
194
|
end
|
208
195
|
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Goodsheet
|
2
2
|
|
3
3
|
class ValidationError
|
4
|
+
|
4
5
|
def initialize(line, val_err)
|
5
6
|
@line = line
|
6
|
-
@val_err = val_err
|
7
|
+
@val_err = val_err.full_messages.join(', ')
|
7
8
|
end
|
8
9
|
|
9
10
|
def to_s
|
10
|
-
|
11
|
-
"Row #{@line} is invalid: #{@val_err.full_messages.join(', ')}"
|
11
|
+
"Row #{@line} is not valid: #{@val_err}"
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -1,40 +1,18 @@
|
|
1
1
|
module Goodsheet
|
2
2
|
|
3
|
-
class ValidationErrors
|
4
|
-
attr_reader :array
|
3
|
+
class ValidationErrors < Array
|
5
4
|
|
6
|
-
def initialize
|
7
|
-
@
|
5
|
+
def initialize(limit=0)
|
6
|
+
@max_size = (limit==0 || limit.nil?) ? Float::INFINITY : limit
|
8
7
|
end
|
9
8
|
|
9
|
+
# Add a potential error (will be added only if the row is not valid)
|
10
|
+
#
|
11
|
+
# @param line_number [Fixnum] Line number (0-based).
|
12
|
+
# @return [boolean] Return false if the limit has been reached, true otherwise.
|
10
13
|
def add(line_number, row)
|
11
|
-
|
12
|
-
|
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
|
-
|
26
|
-
def [](i)
|
27
|
-
@array[i]
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_a
|
31
|
-
@array
|
32
|
-
end
|
33
|
-
|
34
|
-
def each(&block)
|
35
|
-
@array.each do |i|
|
36
|
-
yield(i)
|
37
|
-
end
|
14
|
+
self << ValidationError.new(line_number+1, row.errors) if row.invalid?
|
15
|
+
self.size < @max_size
|
38
16
|
end
|
39
17
|
|
40
18
|
def valid?
|
data/lib/goodsheet/version.rb
CHANGED
data/notes.txt
CHANGED
@@ -66,3 +66,16 @@ values = @ss.read(:force_nil => 0.0, :skip => 6) do |row|
|
|
66
66
|
MyRow.new(:year => row[0])
|
67
67
|
end
|
68
68
|
|
69
|
+
|
70
|
+
Test a single test file:
|
71
|
+
rake test TEST=test/test_defaults.rb
|
72
|
+
L3lIrj
|
73
|
+
|
74
|
+
|
75
|
+
Tests:
|
76
|
+
rake test TEST=test/test_defaults.rb # OOOKK! committa
|
77
|
+
rake test TEST=test/test_enel.rb # OOOKK!
|
78
|
+
rake test TEST=test/test_example_2.rb # OOOKK!
|
79
|
+
rake test TEST=test/test_roo.rb # OOOKK!
|
80
|
+
|
81
|
+
|
data/test/fixtures/example.xls
CHANGED
Binary file
|
data/test/test_row.rb
CHANGED
@@ -3,6 +3,7 @@ require 'goodsheet'
|
|
3
3
|
|
4
4
|
class TestRow < Test::Unit::TestCase
|
5
5
|
|
6
|
+
# Test various style of column_names option
|
6
7
|
def test_column_names
|
7
8
|
assert_raise ArgumentError do
|
8
9
|
Goodsheet::Row.column_names
|
@@ -11,17 +12,18 @@ class TestRow < Test::Unit::TestCase
|
|
11
12
|
Goodsheet::Row.column_names(6)
|
12
13
|
end
|
13
14
|
|
15
|
+
result = {0 => :a, 2 => :b, 3 => :c}
|
14
16
|
Goodsheet::Row.column_names(:a, nil, :b, :c)
|
15
|
-
assert_equal(Goodsheet::Row.keys,
|
17
|
+
assert_equal(Goodsheet::Row.keys, result)
|
16
18
|
|
17
19
|
Goodsheet::Row.column_names([:a, nil, :b, :c])
|
18
|
-
assert_equal(Goodsheet::Row.keys,
|
20
|
+
assert_equal(Goodsheet::Row.keys, result)
|
19
21
|
|
20
22
|
Goodsheet::Row.column_names(:a => 0, :b => 2, :c => 3)
|
21
|
-
assert_equal(Goodsheet::Row.keys,
|
23
|
+
assert_equal(Goodsheet::Row.keys, result)
|
22
24
|
|
23
25
|
Goodsheet::Row.column_names(0 => :a, 2 => :b, 3 => :c)
|
24
|
-
assert_equal(Goodsheet::Row.keys,
|
26
|
+
assert_equal(Goodsheet::Row.keys, result)
|
25
27
|
end
|
26
28
|
|
27
29
|
|