active_importer 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a6daa340c010143c7efa4f1f94cb5cb1778c13bf
4
+ data.tar.gz: a45ede6787d0fee8563973292bd2456133b61ae8
5
+ SHA512:
6
+ metadata.gz: 974291103c95864dc5409fa19544fd135ff1485b559193f779a7243e6abd42185ff80d602a6d868b9cc625b7d265a6e06e2e2cf5984e395645e7e4b845631931
7
+ data.tar.gz: 608cf19443e5e470d600cb41f0e966cacc53c563a5b68ebaa3e6d93ad4116982e9fb5273fba199ae414fbf43211c7b25894459ce83b55b040da0b689e7d0b0a5
data/README.md CHANGED
@@ -48,168 +48,38 @@ columns declared. Any extra columns are ignored. Any errors while processing
48
48
  the data file does not interrupt the whole process. Instead, errors are
49
49
  notified via some callbacks defined in the importer (see below).
50
50
 
51
- ### Supported formats
51
+ ## Documentation
52
52
 
53
- This library currently supports reading from most spreadsheet formats, thanks
54
- to the wonderfull [roo](https://github.com/Empact/roo) gem. Specifically, the
55
- following formats are supported:
53
+ For mote detailed information about the different aspects of importing data
54
+ with `active_importer`, refer to the following sections in the [wiki]().
56
55
 
57
- * OpenOffice
58
- * Excel
59
- * Google spreadsheets
60
- * Excelx
61
- * LibreOffice
62
- * CSV
56
+ [wiki]: https://github.com/continuum/active_importer/wiki
63
57
 
64
- The spreadsheet contents are scanned, row by row, until a row is found that
65
- matches the expect header column, which should contain header cells for all the
66
- columns declared in the importer. If no such row is found, the spreadsheet
67
- processing fails without importing any data.
58
+ ### Getting started
68
59
 
69
- If the header row is found, data is scanned from the next row on, until the end
70
- of the spreadsheet.
60
+ * [Understanding how spreadsheets are parsed](https://github.com/continuum/active_importer/wiki/Understanding-how-spreadsheets-are-parsed)
61
+ * [Mapping columns to attributes](https://github.com/continuum/active_importer/wiki/Mapping-columns-to-attributes)
71
62
 
72
- ### Callbacks
63
+ ### Diving in
73
64
 
74
- An importer class can define blocks of code acting as callbacks, to be notified
75
- of certain events that occur while importing the data.
65
+ * [Custom data processing](https://github.com/continuum/active_importer/wiki/Custom-data-processing)
66
+ * [Helper methods](https://github.com/continuum/active_importer/wiki/Helper-methods)
67
+ * [File extension and supported formats](https://github.com/continuum/active_importer/wiki/File-extension-and-supported-formats)
68
+ * [Passing custom parameters](https://github.com/continuum/active_importer/wiki/Custom-parameters)
69
+ * [Events and callbacks](https://github.com/continuum/active_importer/wiki/Callbacks)
70
+ * [Selecting the model instance to import into (Update instead of create)](https://github.com/continuum/active_importer/wiki/Update-instead-of-create)
71
+ * [Error handling](https://github.com/continuum/active_importer/wiki/Error-handling)
72
+ * [Selecting the sheet to get data from](https://github.com/continuum/active_importer/wiki/Selecting-the-sheet-to-work-with)
73
+ * [Skipping rows](https://github.com/continuum/active_importer/wiki/Skipping-rows)
76
74
 
77
- ```ruby
78
- class EmployeeImporter < ActiveImporter::Base
79
- imports Employee
80
-
81
- attr_reader :row_count
82
-
83
- column 'First name'
84
- column 'Last name'
85
- column 'Department', :department do |department_name|
86
- Department.find_by(name: department_name)
87
- end
88
-
89
- on :row_processing do
90
- model.full_name = [row['First name'], row['Last name']].join(' ')
91
- end
92
-
93
- on :import_started do
94
- @row_count = 0
95
- end
96
-
97
- on :row_processed do
98
- @row_count += 1
99
- end
100
-
101
- on :import_finished do
102
- send_notification("Data imported successfully!")
103
- end
75
+ ### Advanced features
104
76
 
105
- on :import_failed do |exception|
106
- send_notification("Fatal error while importing data: #{exception.message}")
107
- end
108
-
109
- private
110
-
111
- def send_notification(message)
112
- # ...
113
- end
114
- end
115
- ```
116
-
117
- The supported events are:
118
-
119
- - **import_failed:** Fired once **before** the beginning of the data
120
- processing, if the input data cannot be processed for some reason. If this
121
- event is fired by an importer, none of its other events are ever fired.
122
- - **import_started:** Fired once at the beginning of the data processing,
123
- before the first row is processed.
124
- - **row_processing:** Fired while the row is being processed to be imported
125
- into a model instance.
126
- - **row_skipped:** Fired once for each row that matches the `skip_rows_if`
127
- condition, if any.
128
- - **row_processed:** Fired once for each row that has been processed,
129
- regardless of whether it resulted in success or error.
130
- - **row_success:** Fired once for each row that was imported successfully into
131
- the data model.
132
- - **row_error:** Fired once for each row that was **not** imported successfully
133
- into the data model.
134
- - **import_finished:** Fired once **after** all rows have been processed.
135
- - **import_aborted:** Fired once if the import process is aborted by invoking
136
- `abort!`.
137
-
138
- More than one block of code can be provided for each of these events, and they
139
- will all be invoked in the same order in which they were declared. All blocks
140
- are executed in the context of the importer instance, so they have access to
141
- all the importer attributes and instance variables. Error-related events
142
- (`:import_failed` and `:row_error`) pass to the blocks the instance of the
143
- exception that provoked the error condition.
144
-
145
- Additionally, all the `row_*` events have access to the `row` and `model`
146
- variables, which reference the spreadsheet row being processed, and the model
147
- object where the row data is being stored, respectively. This feature is
148
- specifically useful for the `:row_processing` event handler, which is triggered
149
- while a row is being processed, and before the corresponding data model is
150
- saved. This allows to define any complex data-import logic that cannot be
151
- expressed in terms of mapping a column to a data field.
152
-
153
- ### Selecting the model instance to import into
154
-
155
- By default, the importer will attempt to generate a new model instance per row
156
- processed. The importer can be instructed to update records instead, if they
157
- already exist, instead of always attempting to generate a new one.
158
-
159
- ```ruby
160
- class EmployeeImporter
161
- imports Employee
162
-
163
- fetch_model do
164
- Employee.where(first_name: row['First name'], last_name: row['Last name']).first_or_initialize
165
- end
166
-
167
- # ...
168
- end
169
- ```
170
-
171
- The code above specifies that, for each row, the importer should attempt to
172
- find an existing model for the employee with the first and last name in the row
173
- being processed. If this record exist, the row data will be used to update the
174
- given model instance. Otherwise, a new employee record will be created.
175
-
176
- ### Selecting the sheet to get data from
177
-
178
- Spreadsheet files often have more than one sheet of data, so it is desirable to
179
- select which sheet to use when importing.
180
-
181
- ```ruby
182
- class EmployeeImporter
183
- imports Employee
184
-
185
- sheet "Employees"
186
-
187
- # ...
188
- end
189
- ```
190
-
191
- The importer defined above specifies that data should be read from a sheet
192
- named "Employees". By default an importer will read from the first sheet in
193
- the spreadsheet.
194
-
195
- Also, sheets can be specified by name or by index, starting by 1, which is the
196
- first sheet. For instance, the following importer will read data from the
197
- third sheet, no matter what's its name.
198
-
199
- ```ruby
200
- class EmployeeImporter
201
- imports Employee
202
-
203
- sheet 3
204
-
205
- # ...
206
- end
207
- ```
77
+ * [Aborting the import process](https://github.com/continuum/active_importer/wiki/Aborting-the-import-process)
78
+ * [Transactional importers](https://github.com/continuum/active_importer/wiki/Transactional-importers)
208
79
 
209
80
  ## Contributing
210
81
 
211
- 1. Fork it
212
- 2. Create your feature branch (`git checkout -b my-new-feature`)
213
- 3. Commit your changes (`git commit -am 'Add some feature'`)
214
- 4. Push to the branch (`git push origin my-new-feature`)
215
- 5. Create new Pull Request
82
+ Contributions are welcome! Take a look at our [contributions guide][] for
83
+ details.
84
+
85
+ [contributions guide]: https://github.com/continuum/active_importer/wiki/Contributing
@@ -23,4 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "bundler", "~> 1.3"
24
24
  spec.add_development_dependency "rake"
25
25
  spec.add_development_dependency "rspec"
26
+
27
+ spec.add_development_dependency "sqlite3"
28
+ spec.add_development_dependency "activerecord"
26
29
  end
@@ -53,18 +53,64 @@ module ActiveImporter
53
53
  @skip_rows_block
54
54
  end
55
55
 
56
- def self.column(title, field = nil, &block)
56
+ def self.column(title, field = nil, options = nil, &block)
57
57
  title = title.strip
58
58
  if columns[title]
59
59
  raise "Duplicate importer column '#{title}'"
60
60
  end
61
- columns[title] = { field_name: field, transform: block }
61
+
62
+ if field.is_a?(Hash)
63
+ raise "Invalid column '#{title}': expected a single set of options" unless options.nil?
64
+ options = field
65
+ field = nil
66
+ else
67
+ options ||= {}
68
+ end
69
+
70
+ if field.nil? && block_given?
71
+ raise "Invalid column '#{title}': must have a corresponding attribute, or it shouldn't have a block"
72
+ end
73
+
74
+ columns[title] = {
75
+ field_name: field,
76
+ transform: block,
77
+ optional: !!options[:optional],
78
+ }
62
79
  end
63
80
 
64
81
  def self.import(file, options = {})
65
82
  new(file, options).import
66
83
  end
67
84
 
85
+ #
86
+ # Transactions
87
+ #
88
+
89
+ def self.transactional(flag = true)
90
+ if flag
91
+ raise "Model class does not support transactions" unless @model_class.respond_to?(:transaction)
92
+ end
93
+ @transactional = !!flag
94
+ end
95
+
96
+ def self.transactional?
97
+ @transactional || false
98
+ end
99
+
100
+ def transactional?
101
+ @transactional || self.class.transactional?
102
+ end
103
+
104
+ def transaction
105
+ if transactional?
106
+ model_class.transaction { yield }
107
+ else
108
+ yield
109
+ end
110
+ end
111
+
112
+ private :transaction
113
+
68
114
  #
69
115
  # Callbacks
70
116
  #
@@ -117,11 +163,14 @@ module ActiveImporter
117
163
  attr_reader :header, :row, :model
118
164
  attr_reader :row_count, :row_index
119
165
  attr_reader :row_errors
120
- attr_reader :context
166
+ attr_reader :params
121
167
 
122
168
  def initialize(file, options = {})
123
169
  @row_errors = []
124
- @context = options.delete(:context)
170
+ @params = options.delete(:params)
171
+ @transactional = options.fetch(:transactional, self.class.transactional?)
172
+
173
+ raise "Importer is declared transactional at the class level" if !@transactional && self.class.transactional?
125
174
 
126
175
  @book = Roo::Spreadsheet.open(file, options)
127
176
  load_sheet
@@ -134,6 +183,7 @@ module ActiveImporter
134
183
  @row_count = 0
135
184
  @row_index = 1
136
185
  fire_event :import_failed, e
186
+ raise
137
187
  end
138
188
 
139
189
  def fetch_model_block
@@ -149,21 +199,27 @@ module ActiveImporter
149
199
  end
150
200
 
151
201
  def import
152
- return if @book.nil?
153
- fire_event :import_started
154
- @data_row_indices.each do |index|
155
- @row_index = index
156
- @row = row_to_hash @book.row(index)
157
- if skip_row?
158
- fire_event :row_skipped
159
- next
160
- end
161
- import_row
162
- if aborted?
163
- fire_event :import_aborted, @abort_message
164
- break
202
+ transaction do
203
+ return if @book.nil?
204
+ fire_event :import_started
205
+ @data_row_indices.each do |index|
206
+ @row_index = index
207
+ @row = row_to_hash @book.row(index)
208
+ if skip_row?
209
+ fire_event :row_skipped
210
+ next
211
+ end
212
+ import_row
213
+ if aborted?
214
+ fire_event :import_aborted, @abort_message
215
+ break
216
+ end
165
217
  end
166
218
  end
219
+ rescue => e
220
+ fire_event :import_aborted, e.message
221
+ raise
222
+ ensure
167
223
  fire_event :import_finished
168
224
  end
169
225
 
@@ -201,9 +257,10 @@ module ActiveImporter
201
257
  end
202
258
 
203
259
  def find_header_index
260
+ required_column_keys = columns.keys.reject { |title| columns[title][:optional] }
204
261
  (1..@book.last_row).each do |index|
205
262
  row = @book.row(index).map { |cell| cell.to_s.strip }
206
- return index if columns.keys.all? { |item| row.include?(item) }
263
+ return index if required_column_keys.all? { |item| row.include?(item) }
207
264
  end
208
265
  return nil
209
266
  end
@@ -225,6 +282,7 @@ module ActiveImporter
225
282
  rescue => e
226
283
  @row_errors << { row_index: row_index, error_message: e.message }
227
284
  fire_event :row_error, e
285
+ raise if transactional?
228
286
  return false
229
287
  end
230
288
  fire_event :row_success
@@ -1,3 +1,3 @@
1
1
  module ActiveImporter
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  end
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'stubs/employee'
3
2
 
4
3
  describe ActiveImporter::Base do
5
4
  let(:spreadsheet_data) do
@@ -24,9 +23,16 @@ describe ActiveImporter::Base do
24
23
  let(:importer) { EmployeeImporter.new('/dummy/file') }
25
24
 
26
25
  before do
27
- expect(Roo::Spreadsheet).to receive(:open).at_least(:once).and_return { Spreadsheet.new(spreadsheet_data) }
26
+ allow(Roo::Spreadsheet).to receive(:open).at_least(:once).and_return { Spreadsheet.new(spreadsheet_data) }
28
27
  EmployeeImporter.instance_variable_set(:@fetch_model_block, nil)
29
28
  EmployeeImporter.instance_variable_set(:@sheet_index, nil)
29
+ EmployeeImporter.transactional(false)
30
+ end
31
+
32
+ describe '.column' do
33
+ it 'does not allow a column with block and no attribute' do
34
+ expect { EmployeeImporter.column('Dummy') {} }.to raise_error
35
+ end
30
36
  end
31
37
 
32
38
  it 'imports all data from the spreadsheet into the model' do
@@ -48,6 +54,28 @@ describe ActiveImporter::Base do
48
54
  EmployeeImporter.import('/dummy/file')
49
55
  end
50
56
 
57
+ it 'can receive custom parameters via the `params` option' do
58
+ importer = EmployeeImporter.new('/dummy/file', params: 'anything')
59
+ expect(importer.params).to eql('anything')
60
+ end
61
+
62
+ context do
63
+ let(:spreadsheet_data) do
64
+ [
65
+ [' Name ', 'Birth Date', 'Department', 'Unused', 'Manager'],
66
+ ['Mary', '2013-10-25', 'IT', 'hello'],
67
+ ['John', '2013-10-26', 'Sales', 'world'],
68
+ ]
69
+ end
70
+
71
+ it 'processes optional columns when present' do
72
+ expect(EmployeeImporter).to receive(:new).once.and_return(importer)
73
+ expect {
74
+ EmployeeImporter.import('/dummy/file')
75
+ }.to change(Employee.where.not(unused_field: nil), :count).by(2)
76
+ end
77
+ end
78
+
51
79
  context do
52
80
  let(:spreadsheet_data) { spreadsheet_data_with_errors }
53
81
 
@@ -112,7 +140,9 @@ describe ActiveImporter::Base do
112
140
 
113
141
  it 'notifies the failure' do
114
142
  expect_any_instance_of(EmployeeImporter).to receive(:import_failed)
115
- EmployeeImporter.import('/dummy/file')
143
+ expect {
144
+ EmployeeImporter.import('/dummy/file')
145
+ }.to raise_error
116
146
  end
117
147
  end
118
148
 
@@ -184,7 +214,9 @@ describe ActiveImporter::Base do
184
214
  it 'fails if the specified sheet cannot be found' do
185
215
  expect_any_instance_of(EmployeeImporter).to receive(:import_failed)
186
216
  EmployeeImporter.sheet 5
187
- EmployeeImporter.import('/dummy/file')
217
+ expect {
218
+ EmployeeImporter.import('/dummy/file')
219
+ }.to raise_error
188
220
  end
189
221
  end
190
222
 
@@ -228,4 +260,84 @@ describe ActiveImporter::Base do
228
260
  EmployeeImporter.import('/dummy/file')
229
261
  end
230
262
  end
263
+
264
+ describe '#initialize' do
265
+ context "when invoked with option 'transactional: true'" do
266
+ it 'declares the instance to be transactional even when the importer class is not' do
267
+ EmployeeImporter.transactional(false)
268
+ importer = EmployeeImporter.new('/dummy/file', transactional: true)
269
+ expect(importer).to be_transactional
270
+ end
271
+ end
272
+
273
+ context "when invoked with option 'transactional: false'" do
274
+ it 'does not override the class-wide setting' do
275
+ EmployeeImporter.transactional(true)
276
+ expect_any_instance_of(EmployeeImporter).to receive(:import_failed)
277
+ expect {
278
+ EmployeeImporter.new('/dummy/file', transactional: false)
279
+ }.to raise_error
280
+ end
281
+ end
282
+ end
283
+
284
+ describe '.transactional' do
285
+ let(:spreadsheet_data) { spreadsheet_data_with_errors }
286
+
287
+ before(:each) do
288
+ allow(EmployeeImporter).to receive(:new).once.and_return(importer)
289
+ end
290
+
291
+ context 'when called with true as an argument' do
292
+ before(:each) { EmployeeImporter.transactional(true) }
293
+
294
+ it 'declares all importers of its kind to be transactional' do
295
+ expect(EmployeeImporter).to be_transactional
296
+ importer = EmployeeImporter.new('/dummy/file')
297
+ expect(importer).to be_transactional
298
+ end
299
+
300
+ it 'runs the import process within a transaction' do
301
+ expect {
302
+ EmployeeImporter.import('/dummy/file') rescue nil
303
+ }.not_to change(Employee, :count)
304
+ end
305
+
306
+ it 'exposes the exception that aborted the transaction' do
307
+ expect {
308
+ EmployeeImporter.import('/dummy/file')
309
+ }.to raise_error
310
+ end
311
+
312
+ it 'still invokes the :row_error event' do
313
+ expect(importer).to receive(:row_error)
314
+ EmployeeImporter.import('/dummy/file') rescue nil
315
+ end
316
+
317
+ it 'still invokes the :import_finished event' do
318
+ expect(importer).to receive(:import_finished)
319
+ EmployeeImporter.import('/dummy/file') rescue nil
320
+ end
321
+
322
+ it 'invokes the :import_aborted event' do
323
+ expect(importer).to receive(:import_aborted)
324
+ EmployeeImporter.import('/dummy/file') rescue nil
325
+ end
326
+ end
327
+
328
+ context 'when called with false as an argument' do
329
+ it 'does not run the import process within a transactio' do
330
+ EmployeeImporter.transactional(false)
331
+ expect {
332
+ EmployeeImporter.import('/dummy/file')
333
+ }.to change(Employee, :count).by(2)
334
+ end
335
+
336
+ it 'declares all importers of its kind not to be transactional' do
337
+ expect(EmployeeImporter).not_to be_transactional
338
+ importer = EmployeeImporter.new('/dummy/file')
339
+ expect(importer).not_to be_transactional
340
+ end
341
+ end
342
+ end
231
343
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  require 'active_importer'
2
2
 
3
+ # Require files in spec/support
4
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
5
+
3
6
  # This file was generated by the `rspec --init` command. Conventionally, all
4
7
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
8
  # Require this file using `require "spec_helper"` to ensure that it is only
@@ -21,3 +24,5 @@ RSpec.configure do |config|
21
24
  # --seed 1234
22
25
  config.order = 'random'
23
26
  end
27
+
28
+ I18n.enforce_available_locales = false
@@ -0,0 +1,5 @@
1
+ require File.expand_path('../schema', __FILE__)
2
+
3
+ class Employee < ::ActiveRecord::Base
4
+ validates_exclusion_of :name, in: ['Invalid']
5
+ end
@@ -0,0 +1,17 @@
1
+ require 'active_record'
2
+ require 'logger'
3
+
4
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
5
+ ActiveRecord::Base.logger = Logger.new('/dev/null')
6
+ ActiveRecord::Migration.verbose = false
7
+
8
+ ActiveRecord::Schema.define do
9
+ create_table :employees, :force => true do |t|
10
+ t.column :name, :string
11
+ t.column :birth_date, :string
12
+ t.column :department_id, :integer
13
+ t.column :unused_field, :string
14
+ t.column :created_at, :datetime
15
+ t.column :updated_at, :datetime
16
+ end
17
+ end
@@ -1,14 +1,3 @@
1
- require 'stubs/data_model'
2
- require 'stubs/spreadsheet'
3
-
4
- class Employee < DataModel
5
- attr_accessor :name, :birth_date, :department, :department_id
6
-
7
- def validate
8
- errors << 'Invalid name' if name == 'Invalid'
9
- end
10
- end
11
-
12
1
  class EmployeeBaseImporter < ActiveImporter::Base
13
2
  on(:import_finished) { base_import_finished }
14
3
 
@@ -24,6 +13,8 @@ class EmployeeImporter < EmployeeBaseImporter
24
13
  column 'Name', :name
25
14
  column 'Birth Date', :birth_date
26
15
  column 'Manager'
16
+ column 'Unused', :unused_field, optional: true
17
+ column 'Extra', optional: true
27
18
  column ' Department ', :department_id do |value|
28
19
  find_department(value)
29
20
  end
File without changes
metadata CHANGED
@@ -1,78 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_importer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
5
- prerelease:
4
+ version: 0.2.5
6
5
  platform: ruby
7
6
  authors:
8
7
  - Ernesto Garcia
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-11-20 00:00:00.000000000 Z
11
+ date: 2014-02-21 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: roo
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: bundler
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ~>
31
+ - - "~>"
36
32
  - !ruby/object:Gem::Version
37
33
  version: '1.3'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ~>
38
+ - - "~>"
44
39
  - !ruby/object:Gem::Version
45
40
  version: '1.3'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: rake
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - ">="
52
46
  - !ruby/object:Gem::Version
53
47
  version: '0'
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - ">="
60
53
  - !ruby/object:Gem::Version
61
54
  version: '0'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rspec
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ! '>='
59
+ - - ">="
68
60
  - !ruby/object:Gem::Version
69
61
  version: '0'
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ! '>='
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activerecord
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
76
95
  - !ruby/object:Gem::Version
77
96
  version: '0'
78
97
  description: Import tabular data from spreadsheets or similar sources into data models
@@ -82,8 +101,8 @@ executables: []
82
101
  extensions: []
83
102
  extra_rdoc_files: []
84
103
  files:
85
- - .gitignore
86
- - .rspec
104
+ - ".gitignore"
105
+ - ".rspec"
87
106
  - Gemfile
88
107
  - LICENSE.txt
89
108
  - README.md
@@ -94,43 +113,38 @@ files:
94
113
  - lib/active_importer/version.rb
95
114
  - spec/active_importer/base_spec.rb
96
115
  - spec/spec_helper.rb
97
- - spec/stubs/data_model.rb
98
- - spec/stubs/employee.rb
99
- - spec/stubs/spreadsheet.rb
116
+ - spec/support/active_record/models.rb
117
+ - spec/support/active_record/schema.rb
118
+ - spec/support/employee_importer.rb
119
+ - spec/support/spreadsheet.rb
100
120
  homepage: ''
101
121
  licenses:
102
122
  - MIT
123
+ metadata: {}
103
124
  post_install_message:
104
125
  rdoc_options: []
105
126
  require_paths:
106
127
  - lib
107
128
  required_ruby_version: !ruby/object:Gem::Requirement
108
- none: false
109
129
  requirements:
110
- - - ! '>='
130
+ - - ">="
111
131
  - !ruby/object:Gem::Version
112
132
  version: '0'
113
- segments:
114
- - 0
115
- hash: 3645783224165529099
116
133
  required_rubygems_version: !ruby/object:Gem::Requirement
117
- none: false
118
134
  requirements:
119
- - - ! '>='
135
+ - - ">="
120
136
  - !ruby/object:Gem::Version
121
137
  version: '0'
122
- segments:
123
- - 0
124
- hash: 3645783224165529099
125
138
  requirements: []
126
139
  rubyforge_project:
127
- rubygems_version: 1.8.23
140
+ rubygems_version: 2.2.0
128
141
  signing_key:
129
- specification_version: 3
142
+ specification_version: 4
130
143
  summary: Import tabular data into data models
131
144
  test_files:
132
145
  - spec/active_importer/base_spec.rb
133
146
  - spec/spec_helper.rb
134
- - spec/stubs/data_model.rb
135
- - spec/stubs/employee.rb
136
- - spec/stubs/spreadsheet.rb
147
+ - spec/support/active_record/models.rb
148
+ - spec/support/active_record/schema.rb
149
+ - spec/support/employee_importer.rb
150
+ - spec/support/spreadsheet.rb
@@ -1,62 +0,0 @@
1
- class DataModel
2
-
3
- def self.count
4
- @count ||= 0
5
- end
6
-
7
- attr_reader :errors
8
-
9
- def initialize(attributes = {})
10
- @new_record = true
11
- @errors = []
12
- attributes.each_pair do |key, value|
13
- self[key] = value
14
- end
15
- end
16
-
17
- def []=(field, value)
18
- send("#{field}=", value)
19
- end
20
-
21
- def to_s
22
- "#{self.class.name}(#{attributes})"
23
- end
24
-
25
- def save
26
- if valid?
27
- self.class.send(:increment_count) if @new_record
28
- @new_record = false
29
- true
30
- else
31
- false
32
- end
33
- end
34
-
35
- def save!
36
- raise 'Invalid model' unless save
37
- end
38
-
39
- def new_record?
40
- @new_record
41
- end
42
-
43
- def valid?
44
- validate
45
- errors.empty?
46
- end
47
-
48
- def validate
49
- # ...
50
- end
51
-
52
- private
53
-
54
- def self.increment_count
55
- count
56
- @count += 1
57
- end
58
-
59
- class << self
60
- private :increment_count
61
- end
62
- end