rails_spreadsheet_reader 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MTllZmRmOTIyYzY2OTRhMjRlMTM2NGNhODYyOWNlMDk3NmRiNWY4Yg==
4
+ NGQ3ZmFlMDQxYjBjNDExOGEwZGZiMmQ2YjkxNmQ4ZGM5ZmQ3ZWUwZg==
5
5
  data.tar.gz: !binary |-
6
- NjQwOGJlNmE2ZGZhMWE1NTc2NTEzNDIyNTI2YzUwNGQ5Mjg1ZDEzYg==
6
+ MDlhNjBkZDZkYThkYjg0ZmE2ZmY4ZDQ5MjZjYjc0ZjJhMGE2ZGY4OQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NTdjNWQ1MjU1MTQzZTczNjVhNDdlZjdlZmU5NzA2ODc4ODYyZjgzZTFjMDU2
10
- ZjRjN2Q5NTFkNWU4MjJjMDQ5NWM1MjBmMTNkNzRiY2IzZjliOGI5NjBjNmNm
11
- NWIxZDgwM2ZmNjE0NDA3NGQ0NWZjYzkwNGJkNzAwM2Q2MDJlMjI=
9
+ YmQ1YjRmNWM2NTdmMWQxZGI0NmRmYmRhMWFmMThkNzdmOGM0M2MwMDkyZDY4
10
+ ZjVkMzMxZTAyMWNkZjM0NjY5NzY3ZDQwYzJjZjdmYmU2OTIzMzMyOTcyMTc1
11
+ NmVjZWU1YzVlOTM1NzdhODk3NTFiNGVhZThhOTUzY2RjNjk5Nzc=
12
12
  data.tar.gz: !binary |-
13
- NDYwMTdmZDc1MmY2YWE4ZDczZGQ3ZDgxZjBhMzg2YzI4MzIwZTVhNDU3NGU3
14
- M2U0ZGE1MDFmNmMzY2ZkOWRmYzdlYTgxODVlYzUzNjQzN2VmMzk5M2JhY2Vh
15
- Yzk4OTQwMmM1Zjk1NTY3ZGY2ODYyM2ZiMTdkNjVhZTY0MDMzMTM=
13
+ ZjEwNzYxZDE4MzQ5OWZmYTM3MzNkMjEyYmVlZGEzNzRlOTU2MjMyZjBjYzlh
14
+ MzkwODMxMGY4OWVmM2ZkZjI0ZWVkZTAyZDUxN2Y1YzVmZGJiZWI3ODNiNmE5
15
+ NTEyYjRjMTE4YWM0NzMzYTM0OWNmMWEzZGM1ZmQ2NzI5ZDRhYzI=
@@ -7,13 +7,18 @@ module Rails
7
7
 
8
8
  source_root File.expand_path('../templates', __FILE__)
9
9
  argument :name, :type => :string
10
+ class_option :models, type: :array, aliases: '-m', default: ['Model1', 'Model2']
11
+ class_option :simple, default: false
12
+ class_option :attributes, aliases: '-a', default: ['attr1', 'attr2']
10
13
 
11
14
  def generate_spreadsheet
15
+ puts 'hola'
12
16
  file_prefix = set_filename(name)
13
17
  @spreadsheet_name = set_spreadsheet_name(name)
14
- template 'spreadsheet.rb', File.join(
15
- 'app/spreadsheet_reader', "#{file_prefix}_spreadsheet.rb"
16
- )
18
+ @models = @options[:models]
19
+ @simple = @options[:simple]
20
+ @attributes = @options[:attributes]
21
+ template 'spreadsheet.rb', "app/spreadsheet_reader/#{file_prefix}_spreadsheet.rb"
17
22
  end
18
23
 
19
24
  private
@@ -1,68 +1,39 @@
1
1
  class <%= @spreadsheet_name %>Spreadsheet < RailsSpreadsheetReader::Base
2
2
 
3
- # add the columns you want to read:
4
- # attr_accessor :attr1, :attr2, :attr3
3
+ # Add alias names for excel columns
4
+ # attr_accessor <%= @attributes.map{|a| ":#{a}"}.join(', ') %>
5
5
 
6
- # add validations to your fields
6
+ # Add validation for your fields
7
7
  # validates_presence_of :attr1
8
8
 
9
- # map this model attributes to your spreadsheet attributes
10
- # you can use a hash { attr1: 0, attr2: 1, attr3: 2, ... }
11
- # or a array %w(attr1 attr2 attr3 ...)
12
-
13
- def self.columns
14
- # return { attr1: 0, attr2: 1, ... }
9
+ # Map attributes to your spreadsheet columns (0-based).
10
+ # You can use a hash { attr1: 0, attr2: 1, attr3: 2, ... }
11
+ # or an array %w(attr1 attr2 attr3 ...) (which maps each key to their index).
12
+ def self.headers
13
+ {<%= @attributes.map.with_index{|a,i| "#{a}: #{i}"}.join(', ') %>}
15
14
  end
16
15
 
17
- # set the row number where the data start, default is 2 (1-based)
16
+ # Set the row number where the data start, default is 2 (1-based)
18
17
  def self.starting_row
19
18
  2
20
19
  end
21
20
 
22
- def self.validate_multiple_rows(row_collection)
23
- # This method is called when all rows of row_collection are valid. The main
24
- # idea of this method is to run validations that have to do with
25
- # the set of rows of the excel. For example, you can check here if a
26
- # excel column is unique. It have to raise an exception if there was
27
- # an error with a certain row.
28
- #
29
- # Example:
30
- #
31
- # def self.validate_multiple_rows(row_collection)
32
- # username_list = []
33
- # row_collection.rows.each do |row|
34
- # if username_list.include?(row.username)
35
- # row_collection.invalid_row = row
36
- # row.errors[:username] = 'is unique'
37
- # raise 'Validation Error'
38
- # else
39
- # username_list << row.username
40
- # end
41
- # end
42
- # end
43
- #
21
+ # Returns 1 or more ActiveRecord classes where data will be saved
22
+ def self.models
23
+ <%= @models.count == 1 ? @models.first : @models.join(', ') %>
44
24
  end
45
25
 
46
- def self.persist(row_collection)
47
- # This method is called after the self.validate_multiple_rows method
48
- # only if it have not raised an exception. The idea of this method
49
- # is to persist the row's data. If there was an error with a certain
50
- # row, you have to Rollback the valid transactions, set the
51
- # invalid error to the row_collection and set the row errors to the
52
- # row object.
53
- #
54
- # Example:
55
- # def self.persist(row_collection)
56
- # User.transaction do
57
- # row_collection.rows.each do |row|
58
- # user = User.new(attr1: row.attr1, attr2: row.attr2, ...)
59
- # unless user.save
60
- # row_collection.set_invalid_row(row, user) # pass the model with errors as second parameter
61
- # rollback # use the rollback helper to rollback the transaction.
62
- # end
63
- # end
64
- # end
65
- # end
26
+ # Map a spreadsheet instance to model parameters
27
+ <% @models.each do |model| %>
28
+ def <%= model.downcase %>
29
+ # Returns { attr1: self.attr1, attr2: self.attr2 }
66
30
  end
31
+ <% end %>
32
+
33
+ # Persist
34
+ #def persist
35
+ # Create or save your record. By default, it will use your methods
36
+ # defined above (<%= @models.map{|m| m.downcase}.join(', ') %>)
37
+ #end
67
38
 
68
39
  end
@@ -7,32 +7,54 @@ module RailsSpreadsheetReader
7
7
  class Base
8
8
 
9
9
  include ActiveModel::Model
10
+ include ActiveModel::Validations::Callbacks
10
11
 
11
12
  class MethodNotImplementedError < StandardError; end
12
13
  class InvalidTypeError < StandardError; end
13
14
 
14
15
  attr_accessor :row_number
16
+ attr_accessor :record_with_error
17
+ attr_accessor :copied_errors
18
+ attr_accessor :collection
15
19
 
16
- attr_accessor :models_with_errors
20
+ BASE_ATTRIBUTES = %w(row_number record_with_error copied_errors collection)
17
21
 
18
- # Errors should be copied in a validation callback because valid?
19
- # method flushes errors.
20
- validate :copy_errors_on_validation
22
+ validate :check_record_with_error
21
23
 
22
- def copy_errors_on_validation
23
- self.models_with_errors ||= []
24
- self.models_with_errors.each do |model|
25
- model.errors.full_messages.each do |msg|
26
- errors.add(:base, msg)
24
+ def check_record_with_error
25
+ if record_with_error.present? and record_with_error.errors.any?
26
+ record_with_error.errors.full_messages.each do |msg|
27
+ @errors.add(:base, msg)
27
28
  end
28
29
  end
29
30
  end
30
31
 
31
- # Models are added models_with_errors. They will be copied in to self.errors on
32
- # copy_errors_on_validation callback
33
- def copy_errors(model)
34
- self.models_with_errors ||= []
35
- self.models_with_errors << model
32
+ def self.last_record
33
+ @last_record ||= nil
34
+ end
35
+
36
+ def self.last_record=(record)
37
+ @last_record = record
38
+ end
39
+
40
+ def models
41
+ fail(
42
+ MethodNotImplementedError,
43
+ 'Please implement this method in your class.'
44
+ )
45
+ end
46
+
47
+ def persist
48
+ models = self.class.models.is_a?(Array) ? self.class.models : [self.class.models]
49
+ models.each do |model|
50
+ method_name = model.model_name.human.downcase
51
+ if respond_to?(method_name)
52
+ instance = model.new(send(model.model_name.human.downcase))
53
+ else
54
+ instance = model.new(as_json(except: BASE_ATTRIBUTES))
55
+ end
56
+ instance.save!
57
+ end
36
58
  end
37
59
 
38
60
  # Defines the starting row of the excel where the class should start reading the data.
@@ -48,19 +70,19 @@ module RailsSpreadsheetReader
48
70
  # a Array of strings/symbols (representing columns names) or a Hash (which map column names to columns indexes).
49
71
  #
50
72
  # Array Example
51
- # def self.columns
73
+ # def self.headers
52
74
  # [:username, :email, :gender]
53
75
  # end
54
76
  #
55
77
  # Hash Example
56
- # def self.columns
78
+ # def self.headers
57
79
  # { :username => 0, :email => 1, :gender => 2 }
58
80
  # end
59
81
  #
60
82
  # == Returns:
61
83
  # An Array or a Hash defining the columns of the excel.
62
84
  #
63
- def self.columns
85
+ def self.headers
64
86
  fail(
65
87
  MethodNotImplementedError,
66
88
  'Please implement this method in your class.'
@@ -73,11 +95,11 @@ module RailsSpreadsheetReader
73
95
  # order of the columns.
74
96
  #
75
97
  # For example, given the following self.columns definition
76
- # def self.columns
98
+ # def self.headers
77
99
  # [:username, :email, :gender]
78
100
  # end
79
101
  # Or
80
- # def self.columns
102
+ # def self.headers
81
103
  # { :username => 0, :email => 1, :gender => 2 }
82
104
  # end
83
105
  # Row.formatted([username email@test.cl male]) will return
@@ -111,6 +133,7 @@ module RailsSpreadsheetReader
111
133
  # Array or Hash of values which represents an excel column.
112
134
 
113
135
  def initialize(arr_or_hash = {})
136
+ self.copied_errors = ActiveModel::Errors.new(self)
114
137
  if arr_or_hash.is_a?(Array)
115
138
  super(self.class.formatted_hash(arr_or_hash))
116
139
  else
@@ -118,66 +141,39 @@ module RailsSpreadsheetReader
118
141
  end
119
142
  end
120
143
 
121
- # This method is called when all rows of row_collection are valid. The main
122
- # idea of this method is to run validations that have to do with
123
- # the set of rows of the excel. For example, you can check here if a
124
- # excel column is unique:
125
- #
126
- # Example:
127
- #
128
- # def self.validate_multiple_rows(row_collection)
129
- # username_list = []
130
- # row_collection.each do |row|
131
- # if username_list.include?(row.username)
132
- # row_collection.invalid_row = row
133
- # row.errors[:username] = 'is unique'
134
- # else
135
- # username_list << row.username
136
- # end
137
- # end
138
- # end
139
- #
140
- # == Parameters:
141
- # row_collection::
142
- # SpreadsheetReader::RowCollection instance
143
- def self.validate_multiple_rows(row_collection)
144
-
145
- end
146
-
147
- # This method is called after the self.validate_multiple_rows method
148
- # only if it have not raised an exception. The idea of this method
149
- # is to persist the row's data. If there was an error with a certain
150
- # row, you have to Rollback the valid transactions, set the
151
- # invalid error to the row_collection and set the row errors to the
152
- # row object.
153
- #
154
- # Example:
155
- # def self.persist(row_collection)
156
- # User.transaction do
157
- # row_collection.each do |row|
158
- # user = User.new(attr1: row.attr1, attr2: row.attr2, ...)
159
- # unless user.save
160
- # row_collection.set_invalid_row(row, user) # pass the model with errors as second parameter
161
- # rollback # use the rollback helper to rollback the transaction.
162
- # end
163
- # end
164
- # end
165
- # end
144
+ # Persist all rows of collection if they all are valid
166
145
  #
167
146
  # == Parameters:
168
147
  # row_collection::
169
148
  # SpreadsheetReader::RowCollection instance
170
- def self.persist(row_collection)
171
-
149
+ def self.persist(collection)
150
+ if collection.valid?
151
+ ActiveRecord::Base.transaction do
152
+ collection.each do |row|
153
+ # If any of validations fail ActiveRecord::RecordInvalid gets raised.
154
+ # If any of the before_* callbacks return false the action is cancelled and save! raises ActiveRecord::RecordNotSaved.
155
+ begin
156
+ row.persist
157
+ rescue ActiveRecord::RecordInvalid => e
158
+ row.record_with_error = e.record
159
+ collection.invalid_row = row
160
+ rollback
161
+ rescue ActiveRecord::RecordNotSaved => e
162
+ row.model_with_error = e.record
163
+ collection.invalid_row = row
164
+ rollback
165
+ end
166
+ end
167
+ end
168
+ end
172
169
  end
173
170
 
174
- def self.open_spreadsheet(spreadsheet_file)
171
+ def self.open(spreadsheet_file)
175
172
  Roo::Spreadsheet.open(spreadsheet_file)
176
173
  end
177
174
 
178
- # Read and validates the given #spreadsheet_file. First calls
179
- # #validate_rows method which run #ActiveModel::Model.valid? on each row.
180
- # Then calls #validate_multiple_rows and finally ends with #persist
175
+ # Read and validates the given #spreadsheet_file. Persistence is triggered
176
+ # after all validation pass
181
177
  #
182
178
  # == Parameters:
183
179
  # spreadsheet_file::
@@ -185,20 +181,27 @@ module RailsSpreadsheetReader
185
181
  # == Returns:
186
182
  # row_collection::
187
183
  # SpreadsheetReader::RowCollection instance
188
- def self.read_spreadsheet(spreadsheet_file)
184
+ def self.read(spreadsheet_file)
189
185
 
190
- if columns.empty?
186
+ if headers.empty?
191
187
  raise MethodNotImplementedError
192
188
  end
193
189
 
194
- spreadsheet = open_spreadsheet(spreadsheet_file)
195
- row_collection = RailsSpreadsheetReader::RowCollection.new
190
+ spreadsheet = open(spreadsheet_file)
191
+ collection = RailsSpreadsheetReader::RowCollection.new
192
+
193
+ # Populate collection
194
+ (starting_row..spreadsheet.last_row).each do |row_number|
195
+ parameters = formatted_hash(spreadsheet.row(row_number))
196
+ parameters[:row_number] = row_number
197
+ parameters[:collection] = collection
198
+ collection << self.new(parameters)
199
+ end
196
200
 
197
- validate_rows(spreadsheet, row_collection)
198
- validate_multiple_rows(row_collection) if row_collection.valid?
199
- persist(row_collection) if row_collection.valid?
200
- row_collection
201
+ # Validation and persist
202
+ persist(collection)
201
203
 
204
+ collection
202
205
  end
203
206
 
204
207
  # Transform an array [a0, a1, a2, ...] to a Hash { a0 => 0, a1 => 1, etc }
@@ -209,22 +212,11 @@ module RailsSpreadsheetReader
209
212
 
210
213
  private
211
214
 
212
- # Calls row.valid? on every row of the spreadsheet.
213
- # Stops when a row is an invalid ActiveModel::Model.
214
- def self.validate_rows(spreadsheet, row_collection)
215
- (starting_row..spreadsheet.last_row).each do |number|
216
- hash = formatted_hash(spreadsheet.row(number))
217
- hash[:row_number] = number
218
- row_collection.push self.new(hash)
219
- break if row_collection.invalid?
220
- end
221
- end
222
-
223
215
  # Returns the Hash representation of the defined columns
224
216
  def self.format
225
217
 
226
- if columns.is_a?(Array) or columns.is_a?(Hash)
227
- return columns.is_a?(Array) ? array_to_hash(columns) : columns
218
+ if headers.is_a?(Array) or headers.is_a?(Hash)
219
+ return headers.is_a?(Array) ? array_to_hash(headers) : headers
228
220
  end
229
221
 
230
222
  fail(
@@ -8,20 +8,20 @@ module RailsSpreadsheetReader
8
8
  self.rows = []
9
9
  end
10
10
 
11
- def push(row)
12
- self.invalid_row = row if row.invalid?
11
+ def <<(row)
13
12
  self.rows << row
14
13
  end
15
14
 
16
- def invalid_row=(row)
17
- if row.invalid?
18
- @invalid_row = row
19
- end
20
- # TODO: Throw exception maybe?
21
- end
22
-
23
15
  def valid?
24
- self.invalid_row.nil?
16
+ valid = true
17
+ rows.each do |row|
18
+ if row.invalid?
19
+ self.invalid_row = row
20
+ valid = false
21
+ break
22
+ end
23
+ end
24
+ valid
25
25
  end
26
26
 
27
27
  def invalid?
@@ -29,18 +29,17 @@ module RailsSpreadsheetReader
29
29
  end
30
30
 
31
31
  def errors
32
- self.invalid_row.errors if invalid?
33
- end
34
-
35
- def set_invalid_row(row, model_with_errors)
36
- row.copy_errors(model_with_errors)
37
- self.invalid_row = row
32
+ invalid? ? self.invalid_row.errors : []
38
33
  end
39
34
 
40
35
  def each(&block)
41
36
  self.rows.each(&block)
42
37
  end
43
38
 
39
+ def count
40
+ self.rows.count
41
+ end
42
+
44
43
  end
45
44
 
46
45
  end
@@ -1,3 +1,3 @@
1
1
  module RailsSpreadsheetReader
2
- VERSION = '0.1.4'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -22,6 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency 'rake'
23
23
  spec.add_development_dependency 'rspec', '~> 3.0'
24
24
  spec.add_development_dependency 'factory_girl', '~> 4.4'
25
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
26
+ spec.add_development_dependency 'generator_spec', '~> 0.9'
25
27
 
26
28
  spec.add_dependency 'roo', '~> 1.13'
27
29
  spec.add_dependency 'rails', '~> 4.2.0'
data/spec/base_spec.rb CHANGED
@@ -1,8 +1,4 @@
1
- require 'spec_helper'
2
- require_relative 'models/invalid_column_spreadsheet'
3
- require_relative 'models/user_spreadsheet'
4
- require_relative 'models/user_invalid_spreadsheet'
5
- require_relative 'models/empty_column_spreadsheet.rb'
1
+ require_relative 'spec_helper'
6
2
 
7
3
  def test_file(filename, ext)
8
4
  File.open(File.join TEST_DIR, "#{filename}.#{ext}")
@@ -11,7 +7,7 @@ end
11
7
  describe RailsSpreadsheetReader::Base do
12
8
 
13
9
  it 'not implementing columns method should raise an error' do
14
- expect { RailsSpreadsheetReader::Base.columns }.to raise_error(RailsSpreadsheetReader::Base::MethodNotImplementedError)
10
+ expect { RailsSpreadsheetReader::Base.headers }.to raise_error(RailsSpreadsheetReader::Base::MethodNotImplementedError)
15
11
  end
16
12
 
17
13
  it 'format method should raise an error if columns method is not valid' do
@@ -28,16 +24,16 @@ describe RailsSpreadsheetReader::Base do
28
24
 
29
25
  it 'Base.open_spreadsheet should return a Roo::Base instance' do
30
26
  file = test_file 'users', :csv
31
- roo_row = RailsSpreadsheetReader::Base.open_spreadsheet file
27
+ roo_row = RailsSpreadsheetReader::Base.open file
32
28
  expect(roo_row.is_a?(Roo::Base)).not_to eq(false)
33
29
  end
34
30
 
35
- it 'empty columns should raise an error' do
31
+ it 'empty headers should raise an error' do
36
32
  file = test_file 'users', :csv
37
- expect { EmptyColumnSpreadsheet.read_spreadsheet(file) }.to raise_error(RailsSpreadsheetReader::Base::MethodNotImplementedError)
33
+ expect { EmptyColumnSpreadsheet.read(file) }.to raise_error(RailsSpreadsheetReader::Base::MethodNotImplementedError)
38
34
  end
39
35
 
40
- it '#formatted_hash should work in a derived class which overrides "columns" method' do
36
+ it '#formatted_hash should work in a derived class which overrides "headers" method' do
41
37
  formatted_hash = UserSpreadsheet.formatted_hash %w(username email@test.com male)
42
38
  expect(formatted_hash).to eq({ username: 'username', email: 'email@test.com', gender: 'male' })
43
39
 
@@ -46,41 +42,96 @@ describe RailsSpreadsheetReader::Base do
46
42
  end
47
43
 
48
44
  it '#new from derived class' do
49
- user_spreadsheet = UserSpreadsheet.new(%w(username email@test.com male))
50
- expect(user_spreadsheet.username).to eq('username')
51
- expect(user_spreadsheet.email).to eq('email@test.com')
52
- expect(user_spreadsheet.gender).to eq('male')
53
-
54
- user_spreadsheet = UserSpreadsheet.new({ email: 'email@test.com', gender: 'male', username: 'username' })
55
- expect(user_spreadsheet.username).to eq('username')
56
- expect(user_spreadsheet.email).to eq('email@test.com')
57
- expect(user_spreadsheet.gender).to eq('male')
45
+ row = UserSpreadsheet.new(%w(username email@test.com male))
46
+ expect(row.username).to eq('username')
47
+ expect(row.email).to eq('email@test.com')
48
+ expect(row.gender).to eq('male')
49
+
50
+ row = UserSpreadsheet.new({ email: 'email@test.com', gender: 'male', username: 'username' })
51
+ expect(row.username).to eq('username')
52
+ expect(row.email).to eq('email@test.com')
53
+ expect(row.gender).to eq('male')
58
54
  end
59
55
 
60
56
  it '#valid? should work as desired (derived class)' do
61
- valid_user_spreadsheet = UserSpreadsheet.new({ email: 'email@test.com', gender: 'male', username: 'username' })
62
- expect(valid_user_spreadsheet.valid?).to eq(true)
57
+ valid_row = UserSpreadsheet.new({ email: 'email@test.com', gender: 'male', username: 'username' })
58
+ expect(valid_row.valid?).to eq(true)
63
59
 
64
- invalid_user_spreadsheet = UserSpreadsheet.new({ gender: 'male', username: 'username' })
65
- expect(invalid_user_spreadsheet.valid?).to eq(false)
60
+ invalid_row = UserSpreadsheet.new({ gender: 'male', username: 'username' })
61
+ expect(invalid_row.valid?).to eq(false)
66
62
  end
67
63
 
68
64
  it '#read_spreadsheet should be invalid because a row is not valid' do
69
65
  file = test_file 'users_invalid', :csv
70
- row_collection = UserSpreadsheet.read_spreadsheet(file)
66
+ row_collection = UserSpreadsheet.read(file)
71
67
  expect(row_collection.valid?).to eq(false)
72
68
  expect(row_collection.invalid_row.row_number).to eq(2)
73
69
  expect(row_collection.errors.full_messages).to eq(["Email can't be blank"])
74
70
  end
75
71
 
76
- it 'copy_errors' do
77
- user_spreadsheet = UserSpreadsheet.new
78
- user_spreadsheet.errors.add(:username, 'is unique')
79
- expect(user_spreadsheet.valid?).to eq(false)
80
- expect(user_spreadsheet.invalid?).to eq(true)
81
- valid = EmptyColumnSpreadsheet.new
82
- valid.copy_errors(user_spreadsheet)
83
- expect(valid.valid?).to eq(false)
72
+ it 'A row with an invalid record should be invalid' do
73
+ row = UserSpreadsheet.new
74
+ invalid_record = User.make_invalid
75
+ row.record_with_error = invalid_record
76
+ expect(row.valid?).to eq(false)
77
+ expect(row.invalid?).to eq(true)
78
+ end
79
+
80
+ it 'A row should persist a valid record' do
81
+ params = User.make_valid.as_json
82
+ params.delete('id')
83
+ expect(User.exists?(params)).to eq(false)
84
+ row = UserSpreadsheet.new(params)
85
+ row.persist
86
+ expect(User.exists?(params)).to eq(true)
87
+ end
88
+
89
+ it 'A row should fail when persisting an invalid record' do
90
+ params = User.make_invalid.as_json
91
+ params.delete('id')
92
+ UserSpreadsheet.headers
93
+ UserSpreadsheet.models
94
+ row = UserSpreadsheet.new(params)
95
+ expect { row.persist }.to raise_error(ActiveRecord::RecordInvalid)
96
+ end
97
+
98
+ it 'Should not save records to database when failing' do
99
+ file = test_file 'student_benefit_invalid', :csv
100
+ student_count = Student.count
101
+ benefit_count = Benefit.count
102
+ result = StudentBenefitSpreadsheet.read(file)
103
+ expect(result.valid?).to eq(false)
104
+ expect(student_count).to eq(Student.count)
105
+ expect(benefit_count).to eq(Benefit.count)
106
+ end
107
+
108
+ it 'Should save multiple records to database' do
109
+ file = test_file 'student_benefit', :csv
110
+ student_count = Student.count
111
+ benefit_count = Benefit.count
112
+ result = StudentBenefitSpreadsheet.read(file)
113
+ expect(result.valid?).to eq(true)
114
+ expect(student_count).to eq(Student.count - result.count)
115
+ expect(benefit_count).to eq(Benefit.count - result.count)
116
+ end
117
+
118
+ it 'Spreadsheet.models order does matter when persisting' do
119
+ file = test_file 'employee_enterprise', :csv
120
+ result = EmployeeEnterpriseSpreadsheet.read(file)
121
+ expect(result.valid?).to eq(true)
122
+ end
123
+
124
+ it 'Spreadsheet with custom persist works as expected' do
125
+ file = test_file 'employee_enterprise', :csv
126
+ result = CustomPersistSpreadsheet.read(file)
127
+ expect(result.valid?).to eq(true)
128
+ end
129
+
130
+ it 'Custom validator using uniqueness inside the same excel' do
131
+ file = test_file 'users_invalid', :csv
132
+ result = UserInvalidSpreadsheet.read(file)
133
+ expect(result.valid?).to eq(false)
134
+ expect(result.invalid_row.errors[:username]).to eq(["Username already defined in excel at row 4"])
84
135
  end
85
136
 
86
137
  end
@@ -0,0 +1,74 @@
1
+ require 'sqlite3'
2
+ require 'active_record'
3
+
4
+ # Connect to an in-memory sqlite3 database
5
+ ActiveRecord::Base.establish_connection(
6
+ adapter: 'sqlite3',
7
+ database: ':memory:'
8
+ )
9
+
10
+ # Drop tables
11
+ connection = ActiveRecord::Base.connection
12
+ connection.tables.each do |table|
13
+ connection.drop_table(table)
14
+ end
15
+
16
+ # Define a minimal database schema
17
+ ActiveRecord::Schema.define do
18
+ create_table :users, force: true do |t|
19
+ t.string :email
20
+ t.string :username
21
+ t.string :name
22
+ t.string :gender
23
+ end
24
+ create_table :students, force: true do |t|
25
+ t.string :code
26
+ t.string :name
27
+ end
28
+ create_table :benefits, force: true do |t|
29
+ t.string :code
30
+ t.string :name
31
+ end
32
+ create_table :employees, force: true do |t|
33
+ t.string :name
34
+ t.references :enterprise
35
+ end
36
+ create_table :enterprises, force: true do |t|
37
+ t.string :name
38
+ end
39
+ end
40
+
41
+ # Define the models
42
+ class User < ActiveRecord::Base
43
+ validates_presence_of :email, :username
44
+ validates_uniqueness_of :username, :email
45
+
46
+ def self.make_valid
47
+ User.new(username: 'username', email: 'username@example.com')
48
+ end
49
+
50
+ def self.make_invalid
51
+ User.new(username: 'username')
52
+ end
53
+
54
+ end
55
+
56
+ class Student < ActiveRecord::Base
57
+ validates_uniqueness_of :code
58
+ validates_presence_of :code
59
+ end
60
+
61
+ class Benefit < ActiveRecord::Base
62
+ validates_uniqueness_of :code
63
+ validates_presence_of :code
64
+ end
65
+
66
+ class Employee < ActiveRecord::Base
67
+ belongs_to :enterprise
68
+ end
69
+
70
+ class Enterprise < ActiveRecord::Base
71
+ has_many :employees
72
+ validates_presence_of :name
73
+ validates_uniqueness_of :name
74
+ end
@@ -0,0 +1,8 @@
1
+ EmployeeName,EnterpriseName
2
+ p1,e1
3
+ p2,e2
4
+ p3,e3
5
+ p4,e4
6
+ p5,e5
7
+ p6,e6
8
+ p7,e7
@@ -0,0 +1,10 @@
1
+ Name,Benefit,Code
2
+ Student1,Benefit1,Code1
3
+ Student2,Benefit2,Code2
4
+ Student3,Benefit3,Code3
5
+ Student4,Benefit4,Code4
6
+ Student5,Benefit5,Code5
7
+ Student6,Benefit6,Code6
8
+ Student7,Benefit7,Code7
9
+ Student8,Benefit8,Code8
10
+ Student9,Benefit9,Code9
@@ -0,0 +1,3 @@
1
+ Name,Benefit,Code
2
+ Student1,Benefit1,Code1
3
+ Student2,Benefit2,Code1
@@ -0,0 +1,28 @@
1
+ # spec/lib/generators/test/test_generator_spec.rb
2
+
3
+ require "generator_spec"
4
+ require_relative '../../../lib/generators/rails/spreadsheet_generator'
5
+
6
+ describe Rails::Generators::SpreadsheetGenerator, type: :generator do
7
+
8
+ destination File.expand_path("../../tmp", __FILE__)
9
+ arguments %w(name -m User)
10
+
11
+ before(:all) do
12
+ prepare_destination
13
+ run_generator
14
+ end
15
+
16
+ it "creates a test initializer" do
17
+ expect(destination_root).to have_structure do
18
+ directory 'app' do
19
+ directory 'spreadsheet_reader' do
20
+ file 'name_spreadsheet.rb' do
21
+ contains 'class NameSpreadsheet'
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ end
@@ -1,46 +1,35 @@
1
1
  require 'spec_helper'
2
- require_relative 'models/invalid_column_spreadsheet'
3
- require_relative 'models/user_spreadsheet'
4
- require_relative 'models/user_invalid_spreadsheet'
5
- require_relative 'models/empty_column_spreadsheet.rb'
2
+ require_relative 'spreadsheets/invalid_column_spreadsheet'
3
+ require_relative 'spreadsheets/user_spreadsheet'
4
+ require_relative 'spreadsheets/user_invalid_spreadsheet'
5
+ require_relative 'spreadsheets/empty_column_spreadsheet.rb'
6
6
 
7
7
  describe RailsSpreadsheetReader::RowCollection do
8
8
 
9
- it 'empty properties' do
9
+ it 'New collection should be empty' do
10
10
  collection = RailsSpreadsheetReader::RowCollection.new
11
11
  expect(collection.rows).to eq([])
12
12
  expect(collection.invalid?).to eq(false)
13
13
  expect(collection.valid?).to eq(true)
14
- expect(collection.errors).to eq(nil)
14
+ expect(collection.errors).to eq([])
15
15
  end
16
16
 
17
- it 'push rows' do
17
+ it 'Pushed rows should be saved into the collection' do
18
18
  collection = RailsSpreadsheetReader::RowCollection.new
19
- user_spreadsheet = UserSpreadsheet.new
20
- collection.push(user_spreadsheet)
19
+ row = UserSpreadsheet.new
20
+ collection << row
21
21
  expect(collection.rows.count).to eq(1)
22
+ expect(collection.rows.first).to eq(row)
22
23
  end
23
24
 
24
- it 'push invalid rows' do
25
- collection = RailsSpreadsheetReader::RowCollection.new
26
- user_spreadsheet = UserSpreadsheet.new
27
- user_spreadsheet.make_invalid
28
- expect(user_spreadsheet.invalid?).to eq(true)
29
- collection.push(user_spreadsheet)
30
- expect(collection.valid?).to eq(false)
31
- expect(collection.invalid?).to eq(true)
32
- end
33
-
34
- it 'set_invalid_row' do
25
+ it 'A collection with an invalid row should be invalid' do
35
26
  collection = RailsSpreadsheetReader::RowCollection.new
36
27
  row = UserSpreadsheet.new
37
- model = UserSpreadsheet.new
38
- model.make_invalid
39
- collection.push(row)
40
- collection.set_invalid_row(row, model)
28
+ row.make_invalid
29
+ expect(row.invalid?).to eq(true)
30
+ collection << row
41
31
  expect(collection.valid?).to eq(false)
42
32
  expect(collection.invalid?).to eq(true)
43
- expect(collection.invalid_row).to_not eq(nil)
44
33
  end
45
34
 
46
35
  end
data/spec/spec_helper.rb CHANGED
@@ -18,6 +18,10 @@
18
18
  require File.expand_path('../../lib/rails_spreadsheet_reader', __FILE__)
19
19
  TEST_DIR = File.join(File.dirname(__FILE__), 'files')
20
20
 
21
+ require_relative 'db/database'
22
+
23
+ Dir["#{File.dirname(__FILE__)}/spreadsheets/*.rb"].each {|file| require file }
24
+
21
25
  RSpec.configure do |config|
22
26
  # The settings below are suggested to provide a good initial experience
23
27
  # with RSpec, but feel free to customize to your heart's content.
@@ -0,0 +1,27 @@
1
+ class CustomPersistSpreadsheet < RailsSpreadsheetReader::Base
2
+
3
+ attr_accessor :employee_name, :enterprise_name
4
+ validates_presence_of :employee_name, :enterprise_name
5
+
6
+ def self.headers
7
+ %w(employee_name enterprise_name)
8
+ end
9
+
10
+ def self.models
11
+ [Enterprise, Employee]
12
+ end
13
+
14
+ def employee
15
+ { name: employee_name }
16
+ end
17
+
18
+ def enterprise
19
+ { name: enterprise_name }
20
+ end
21
+
22
+ def persist
23
+ enterprise = Enterprise.find_or_create_by(name: enterprise_name)
24
+ Employee.create!(name: enterprise_name, enterprise: enterprise)
25
+ end
26
+
27
+ end
@@ -0,0 +1,22 @@
1
+ class EmployeeEnterpriseSpreadsheet < RailsSpreadsheetReader::Base
2
+
3
+ attr_accessor :employee_name, :enterprise_name
4
+ validates_presence_of :employee_name, :enterprise_name
5
+
6
+ def self.headers
7
+ %w(employee_name enterprise_name)
8
+ end
9
+
10
+ def self.models
11
+ [Enterprise, Employee]
12
+ end
13
+
14
+ def employee
15
+ { name: employee_name, enterprise: Enterprise.find_by(name: enterprise_name) }
16
+ end
17
+
18
+ def enterprise
19
+ { name: enterprise_name }
20
+ end
21
+
22
+ end
@@ -1,6 +1,6 @@
1
1
  class InvalidColumnSpreadsheet < RailsSpreadsheetReader::Base
2
2
 
3
- def self.columns
3
+ def self.headers
4
4
  10
5
5
  end
6
6
 
@@ -0,0 +1,23 @@
1
+ class StudentBenefitSpreadsheet < RailsSpreadsheetReader::Base
2
+
3
+ attr_accessor :student_name, :benefit_name, :code
4
+
5
+ validates_presence_of :student_name, :benefit_name, :code
6
+
7
+ def self.headers
8
+ %w(student_name benefit_name code)
9
+ end
10
+
11
+ def self.models
12
+ [Student, Benefit]
13
+ end
14
+
15
+ def student
16
+ { name: student_name, code: code }
17
+ end
18
+
19
+ def benefit
20
+ { name: benefit_name, code: code }
21
+ end
22
+
23
+ end
@@ -0,0 +1,23 @@
1
+ class UserInvalidSpreadsheet < RailsSpreadsheetReader::Base
2
+
3
+ attr_accessor :username, :email, :gender, :birthday
4
+
5
+ validates_presence_of :username
6
+ validate :uniqueness_of_username
7
+
8
+ def self.headers
9
+ { :username => 0, :email => 1, :gender => 2, :birthday => 3 }
10
+ end
11
+
12
+ def uniqueness_of_username
13
+ if collection.present?
14
+ detected = collection.rows.find{|r| r.username == self.username and r.row_number < row_number }
15
+ errors.add(:username, "Username already defined in excel at row #{detected.row_number}") if detected.present?
16
+ end
17
+ end
18
+
19
+ def to_s
20
+ {username: username, email: email, gender: gender, birthday: birthday}
21
+ end
22
+
23
+ end
@@ -1,15 +1,19 @@
1
1
  class UserSpreadsheet < RailsSpreadsheetReader::Base
2
2
 
3
- attr_accessor :username, :email, :gender
3
+ attr_accessor :username, :email, :gender, :name
4
4
 
5
5
  validates_presence_of :username, :email
6
6
 
7
- def self.columns
7
+ def self.headers
8
8
  { :username => 0, :email => 1, :gender => 2 }
9
9
  end
10
10
 
11
+ def self.models
12
+ User
13
+ end
14
+
11
15
  def make_invalid
12
- errors[:username] = 'is invalid'
16
+ self.record_with_error = User.make_invalid
13
17
  end
14
18
 
15
19
  end
data/spec/user_spec.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe User do
4
+
5
+ it 'Making an invalid user' do
6
+ user = User.make_invalid
7
+ expect(user.valid?).to eq(false)
8
+ end
9
+
10
+ it 'Making a valid user' do
11
+ user = User.make_valid
12
+ expect(user.valid?).to eq(true)
13
+ end
14
+
15
+ it 'Creating a user twice should fail' do
16
+ user1 = User.make_valid
17
+ user2 = User.make_valid
18
+ expect(user1.save).to eq(true)
19
+ expect(user2.save).to eq(false)
20
+ end
21
+
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_spreadsheet_reader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - muzk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-30 00:00:00.000000000 Z
11
+ date: 2016-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,34 @@ dependencies:
66
66
  - - ~>
67
67
  - !ruby/object:Gem::Version
68
68
  version: '4.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: generator_spec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '0.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '0.9'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: roo
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +142,23 @@ files:
114
142
  - lib/rails_spreadsheet_reader/version.rb
115
143
  - rails_spreadsheet_reader.gemspec
116
144
  - spec/base_spec.rb
145
+ - spec/db/database.rb
146
+ - spec/files/employee_enterprise.csv
147
+ - spec/files/student_benefit.csv
148
+ - spec/files/student_benefit_invalid.csv
117
149
  - spec/files/users.csv
118
150
  - spec/files/users_invalid.csv
119
- - spec/models/empty_column_spreadsheet.rb
120
- - spec/models/invalid_column_spreadsheet.rb
121
- - spec/models/user_invalid_spreadsheet.rb
122
- - spec/models/user_spreadsheet.rb
151
+ - spec/lib/generators/generator_spec.rb
123
152
  - spec/row_collection_spec.rb
124
153
  - spec/spec_helper.rb
154
+ - spec/spreadsheets/custom_persist_spreadsheet.rb
155
+ - spec/spreadsheets/employee_enterprise_spreadsheet.rb
156
+ - spec/spreadsheets/empty_column_spreadsheet.rb
157
+ - spec/spreadsheets/invalid_column_spreadsheet.rb
158
+ - spec/spreadsheets/student_benefit_spreadsheet.rb
159
+ - spec/spreadsheets/user_invalid_spreadsheet.rb
160
+ - spec/spreadsheets/user_spreadsheet.rb
161
+ - spec/user_spec.rb
125
162
  homepage: https://github.com/HasuSoftware/rails_spreadsheet_reader
126
163
  licenses:
127
164
  - MIT
@@ -148,11 +185,20 @@ specification_version: 4
148
185
  summary: Provides an easy way to add model-based validations to excel files.
149
186
  test_files:
150
187
  - spec/base_spec.rb
188
+ - spec/db/database.rb
189
+ - spec/files/employee_enterprise.csv
190
+ - spec/files/student_benefit.csv
191
+ - spec/files/student_benefit_invalid.csv
151
192
  - spec/files/users.csv
152
193
  - spec/files/users_invalid.csv
153
- - spec/models/empty_column_spreadsheet.rb
154
- - spec/models/invalid_column_spreadsheet.rb
155
- - spec/models/user_invalid_spreadsheet.rb
156
- - spec/models/user_spreadsheet.rb
194
+ - spec/lib/generators/generator_spec.rb
157
195
  - spec/row_collection_spec.rb
158
196
  - spec/spec_helper.rb
197
+ - spec/spreadsheets/custom_persist_spreadsheet.rb
198
+ - spec/spreadsheets/employee_enterprise_spreadsheet.rb
199
+ - spec/spreadsheets/empty_column_spreadsheet.rb
200
+ - spec/spreadsheets/invalid_column_spreadsheet.rb
201
+ - spec/spreadsheets/student_benefit_spreadsheet.rb
202
+ - spec/spreadsheets/user_invalid_spreadsheet.rb
203
+ - spec/spreadsheets/user_spreadsheet.rb
204
+ - spec/user_spec.rb
@@ -1,24 +0,0 @@
1
- class UserInvalidSpreadsheet < RailsSpreadsheetReader::Base
2
-
3
- attr_accessor :username, :email, :gender
4
-
5
- validates_presence_of :username
6
-
7
- def self.columns
8
- { :username => 0, :email => 1, :gender => 2 }
9
- end
10
-
11
- def self.validate_multiple_rows(row_collection)
12
- usernames = {}
13
- row_collection.rows.each do |row|
14
- if usernames.has_key?(row.username)
15
- row_collection.invalid_row = row
16
- row.errors[:username] = 'is unique'
17
- break
18
- else
19
- usernames[row.username] = true
20
- end
21
- end
22
- end
23
-
24
- end