rails_spreadsheet_reader 0.1.4 → 0.2.0

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