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.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +40 -0
  3. data/Gemfile +7 -0
  4. data/README.md +69 -3
  5. data/active_record_importer.gemspec +7 -3
  6. data/lib/active_record_importer/attribute/attributes_builder.rb +1 -0
  7. data/lib/active_record_importer/batch_importer.rb +58 -24
  8. data/lib/active_record_importer/batch_persister.rb +103 -0
  9. data/lib/active_record_importer/data_processor.rb +71 -25
  10. data/lib/active_record_importer/dispatcher.rb +3 -3
  11. data/lib/active_record_importer/failed_file_builder.rb +2 -2
  12. data/lib/active_record_importer/import_callbacker.rb +2 -2
  13. data/lib/active_record_importer/importable.rb +3 -2
  14. data/lib/active_record_importer/instance_builder.rb +17 -9
  15. data/lib/active_record_importer/options_builder.rb +5 -1
  16. data/lib/active_record_importer/record_preloader.rb +72 -0
  17. data/lib/active_record_importer/version.rb +1 -1
  18. data/lib/active_record_importer.rb +6 -1
  19. data/spec/active_record_importer/attribute/builders_spec.rb +51 -0
  20. data/spec/active_record_importer/batch_persister_spec.rb +87 -0
  21. data/spec/active_record_importer/data_processor_spec.rb +89 -0
  22. data/spec/active_record_importer/failed_file_builder_spec.rb +26 -0
  23. data/spec/active_record_importer/import_callbacker_spec.rb +25 -0
  24. data/spec/active_record_importer/instance_builder_spec.rb +77 -0
  25. data/spec/active_record_importer/options_builder_spec.rb +19 -0
  26. data/spec/active_record_importer/record_preloader_spec.rb +59 -0
  27. data/spec/active_record_importer_spec.rb +1 -5
  28. data/spec/integration/import_spec.rb +457 -0
  29. data/spec/spec_helper.rb +41 -0
  30. data/spec/support/csv_helper.rb +42 -0
  31. data/spec/support/database.rb +100 -0
  32. data/spec/support/models.rb +112 -0
  33. data/spec/support/query_counter.rb +22 -0
  34. 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.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: 2017-08-18 00:00:00.000000000 Z
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: '2.99'
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: '2.99'
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: '4.0'
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: '4.0'
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: '4.0'
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: '4.0'
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: '0'
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
- rubyforge_project:
194
- rubygems_version: 2.2.2
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: []