active_record_importer 0.4.0 → 1.0.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 +5 -5
- data/CHANGELOG.md +40 -0
- data/Gemfile +7 -0
- data/README.md +69 -3
- data/active_record_importer.gemspec +7 -3
- data/lib/active_record_importer/attribute/attributes_builder.rb +1 -0
- data/lib/active_record_importer/batch_importer.rb +58 -24
- data/lib/active_record_importer/batch_persister.rb +103 -0
- data/lib/active_record_importer/data_processor.rb +71 -25
- data/lib/active_record_importer/dispatcher.rb +3 -3
- data/lib/active_record_importer/failed_file_builder.rb +2 -2
- data/lib/active_record_importer/import_callbacker.rb +2 -2
- data/lib/active_record_importer/importable.rb +3 -2
- data/lib/active_record_importer/instance_builder.rb +17 -9
- data/lib/active_record_importer/options_builder.rb +5 -1
- data/lib/active_record_importer/record_preloader.rb +72 -0
- data/lib/active_record_importer/version.rb +1 -1
- data/lib/active_record_importer.rb +6 -1
- data/spec/active_record_importer/attribute/builders_spec.rb +51 -0
- data/spec/active_record_importer/batch_persister_spec.rb +87 -0
- data/spec/active_record_importer/data_processor_spec.rb +89 -0
- data/spec/active_record_importer/failed_file_builder_spec.rb +26 -0
- data/spec/active_record_importer/import_callbacker_spec.rb +25 -0
- data/spec/active_record_importer/instance_builder_spec.rb +77 -0
- data/spec/active_record_importer/options_builder_spec.rb +19 -0
- data/spec/active_record_importer/record_preloader_spec.rb +59 -0
- data/spec/active_record_importer_spec.rb +1 -5
- data/spec/integration/import_spec.rb +457 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/csv_helper.rb +42 -0
- data/spec/support/database.rb +100 -0
- data/spec/support/models.rb +112 -0
- data/spec/support/query_counter.rb +22 -0
- metadata +57 -14
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Minimal stand-in for the host application's Import model
|
|
3
|
+
# (the one described in the README), without Paperclip.
|
|
4
|
+
##
|
|
5
|
+
class Import < ActiveRecord::Base
|
|
6
|
+
store :properties, accessors: %i(insert_method find_options batch_size)
|
|
7
|
+
|
|
8
|
+
attr_accessor :execute_on_create
|
|
9
|
+
|
|
10
|
+
validates :resource, presence: true
|
|
11
|
+
|
|
12
|
+
def execute!
|
|
13
|
+
resource_class.import!(object: self, execute: true)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def resource_class
|
|
17
|
+
resource.safe_constantize
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def import_file
|
|
21
|
+
file.path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_accessor :file
|
|
25
|
+
|
|
26
|
+
# Mimics Paperclip: receives the generated failed csv and keeps its content
|
|
27
|
+
def failed_file=(io)
|
|
28
|
+
self.failed_file_content = io.read
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def failed_csv
|
|
32
|
+
CSV.parse(failed_file_content.to_s, headers: true)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Importable model with validations, a unique DB index that is NOT
|
|
38
|
+
# backed by a model validation (employee_no), and ActiveRecord callbacks.
|
|
39
|
+
##
|
|
40
|
+
class User < ActiveRecord::Base
|
|
41
|
+
include ActiveRecordImporter::Importable
|
|
42
|
+
|
|
43
|
+
store :properties, accessors: %i(nickname department)
|
|
44
|
+
|
|
45
|
+
validates :first_name, presence: true
|
|
46
|
+
validates :email, presence: true, format: { with: /\A[^@\s]+@[^@\s]+\z/ }
|
|
47
|
+
validates :age, numericality: { greater_than: 0 }, allow_nil: true
|
|
48
|
+
|
|
49
|
+
after_save :bump_imports_count
|
|
50
|
+
|
|
51
|
+
class << self
|
|
52
|
+
attr_accessor :after_save_log, :state_log
|
|
53
|
+
|
|
54
|
+
def reset_logs!
|
|
55
|
+
self.after_save_log = []
|
|
56
|
+
self.state_log = []
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
reset_logs!
|
|
60
|
+
|
|
61
|
+
# Fake state machine API, enough for TransitionProcessor
|
|
62
|
+
Transition = Struct.new(:from, :to, :event)
|
|
63
|
+
|
|
64
|
+
def state_transitions
|
|
65
|
+
[
|
|
66
|
+
Transition.new('pending', 'active', :activate),
|
|
67
|
+
Transition.new('active', 'archived', :archive)
|
|
68
|
+
]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def state_was
|
|
72
|
+
attribute_was('state')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def activate
|
|
76
|
+
update_column(:state, 'active')
|
|
77
|
+
self.class.state_log << [id, 'active']
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def archive
|
|
81
|
+
update_column(:state, 'archived')
|
|
82
|
+
self.class.state_log << [id, 'archived']
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.member_role(_row)
|
|
86
|
+
'member'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def bump_imports_count
|
|
92
|
+
update_column(:imports_count, imports_count.to_i + 1)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# Second importable model: makes sure options are not shared between models
|
|
98
|
+
##
|
|
99
|
+
class Account < ActiveRecord::Base
|
|
100
|
+
include ActiveRecordImporter::Importable
|
|
101
|
+
self.table_name = 'users'
|
|
102
|
+
|
|
103
|
+
acts_as_importable insert_method: 'upsert', find_options: %i(email)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Model that never called acts_as_importable
|
|
108
|
+
##
|
|
109
|
+
class Plain < ActiveRecord::Base
|
|
110
|
+
include ActiveRecordImporter::Importable
|
|
111
|
+
self.table_name = 'users'
|
|
112
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Counts the SQL statements issued inside a block, grouped by verb,
|
|
3
|
+
# so specs can assert that a chunk is persisted in bulk.
|
|
4
|
+
##
|
|
5
|
+
module QueryCounter
|
|
6
|
+
IGNORED = /\A\s*(BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE|ROLLBACK TO)/i
|
|
7
|
+
# Statements on the Import record itself are bookkeeping, not row persistence
|
|
8
|
+
IMPORTS_TABLE = /["`]imports["`]/
|
|
9
|
+
|
|
10
|
+
def count_queries
|
|
11
|
+
counts = Hash.new(0)
|
|
12
|
+
subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*, payload|
|
|
13
|
+
sql = payload[:sql].to_s
|
|
14
|
+
next if payload[:name] == 'SCHEMA' || sql =~ IGNORED || sql =~ IMPORTS_TABLE
|
|
15
|
+
counts[sql[/\A\s*(\w+)/, 1].to_s.upcase] += 1
|
|
16
|
+
end
|
|
17
|
+
yield
|
|
18
|
+
counts
|
|
19
|
+
ensure
|
|
20
|
+
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
|
|
21
|
+
end
|
|
22
|
+
end
|
metadata
CHANGED
|
@@ -1,57 +1,85 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_record_importer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Nera
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-10-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rake
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '12.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '12.0'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: rspec
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
16
30
|
requirements:
|
|
17
31
|
- - "~>"
|
|
18
32
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
33
|
+
version: '3.0'
|
|
20
34
|
type: :development
|
|
21
35
|
prerelease: false
|
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
37
|
requirements:
|
|
24
38
|
- - "~>"
|
|
25
39
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
40
|
+
version: '3.0'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: activerecord
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
30
44
|
requirements:
|
|
31
45
|
- - ">="
|
|
32
46
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
47
|
+
version: '6.0'
|
|
34
48
|
type: :runtime
|
|
35
49
|
prerelease: false
|
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
51
|
requirements:
|
|
38
52
|
- - ">="
|
|
39
53
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
54
|
+
version: '6.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: activerecord-import
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.0'
|
|
41
69
|
- !ruby/object:Gem::Dependency
|
|
42
70
|
name: activesupport
|
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
|
44
72
|
requirements:
|
|
45
73
|
- - ">="
|
|
46
74
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
75
|
+
version: '6.0'
|
|
48
76
|
type: :runtime
|
|
49
77
|
prerelease: false
|
|
50
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
79
|
requirements:
|
|
52
80
|
- - ">="
|
|
53
81
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
82
|
+
version: '6.0'
|
|
55
83
|
- !ruby/object:Gem::Dependency
|
|
56
84
|
name: enumerize
|
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -142,6 +170,7 @@ files:
|
|
|
142
170
|
- ".gitignore"
|
|
143
171
|
- ".rspec"
|
|
144
172
|
- ".travis.yml"
|
|
173
|
+
- CHANGELOG.md
|
|
145
174
|
- CODE_OF_CONDUCT.md
|
|
146
175
|
- Gemfile
|
|
147
176
|
- LICENSE.txt
|
|
@@ -156,6 +185,7 @@ files:
|
|
|
156
185
|
- lib/active_record_importer/attribute/find_options_builder.rb
|
|
157
186
|
- lib/active_record_importer/attribute/helpers.rb
|
|
158
187
|
- lib/active_record_importer/batch_importer.rb
|
|
188
|
+
- lib/active_record_importer/batch_persister.rb
|
|
159
189
|
- lib/active_record_importer/data_processor.rb
|
|
160
190
|
- lib/active_record_importer/dispatcher.rb
|
|
161
191
|
- lib/active_record_importer/engine.rb
|
|
@@ -166,16 +196,30 @@ files:
|
|
|
166
196
|
- lib/active_record_importer/instance_builder.rb
|
|
167
197
|
- lib/active_record_importer/options_builder.rb
|
|
168
198
|
- lib/active_record_importer/railtie.rb
|
|
199
|
+
- lib/active_record_importer/record_preloader.rb
|
|
169
200
|
- lib/active_record_importer/transition_processor.rb
|
|
170
201
|
- lib/active_record_importer/version.rb
|
|
171
202
|
- lib/app/models/import.rb
|
|
203
|
+
- spec/active_record_importer/attribute/builders_spec.rb
|
|
204
|
+
- spec/active_record_importer/batch_persister_spec.rb
|
|
205
|
+
- spec/active_record_importer/data_processor_spec.rb
|
|
206
|
+
- spec/active_record_importer/failed_file_builder_spec.rb
|
|
207
|
+
- spec/active_record_importer/import_callbacker_spec.rb
|
|
208
|
+
- spec/active_record_importer/instance_builder_spec.rb
|
|
209
|
+
- spec/active_record_importer/options_builder_spec.rb
|
|
210
|
+
- spec/active_record_importer/record_preloader_spec.rb
|
|
172
211
|
- spec/active_record_importer_spec.rb
|
|
212
|
+
- spec/integration/import_spec.rb
|
|
173
213
|
- spec/spec_helper.rb
|
|
214
|
+
- spec/support/csv_helper.rb
|
|
215
|
+
- spec/support/database.rb
|
|
216
|
+
- spec/support/models.rb
|
|
217
|
+
- spec/support/query_counter.rb
|
|
174
218
|
homepage: https://github.com/michaelnera/active_record_importer
|
|
175
219
|
licenses:
|
|
176
220
|
- MIT
|
|
177
221
|
metadata: {}
|
|
178
|
-
post_install_message:
|
|
222
|
+
post_install_message:
|
|
179
223
|
rdoc_options: []
|
|
180
224
|
require_paths:
|
|
181
225
|
- lib
|
|
@@ -183,16 +227,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
183
227
|
requirements:
|
|
184
228
|
- - ">="
|
|
185
229
|
- !ruby/object:Gem::Version
|
|
186
|
-
version: '
|
|
230
|
+
version: '2.7'
|
|
187
231
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
232
|
requirements:
|
|
189
233
|
- - ">="
|
|
190
234
|
- !ruby/object:Gem::Version
|
|
191
235
|
version: '0'
|
|
192
236
|
requirements: []
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
signing_key:
|
|
237
|
+
rubygems_version: 3.5.23
|
|
238
|
+
signing_key:
|
|
196
239
|
specification_version: 4
|
|
197
240
|
summary: Active Record Importer
|
|
198
241
|
test_files: []
|