activerecord-import_with_callbacks 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7bb8752cb77453895d342343f18ab91bc5059053
4
+ data.tar.gz: e1c5199c1c0bc578c6b0b619c365c528aeab7e8f
5
+ SHA512:
6
+ metadata.gz: ee4770efa79ac7c4b46e294c173c3790c884b5532f8759a9761895353473d77f2bcb09bbbc1a569593fa44bd1ed74cb718e80197b865798cb8386e8233e359ac
7
+ data.tar.gz: 7bcb41a8e7cea51d70b2c65620b8608c50d91640884efe85536bcd44db00b04fb5b27bc16d2177375d4a9649b0df3504c0ebc252e1d2fa21423d1b496b783f70
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,11 @@
1
+ ---
2
+ AllCops:
3
+ Exclude:
4
+ - lib/active_record/import.rb
5
+ Rails:
6
+ Enabled: true
7
+ Style/Documentation:
8
+ Enabled: false
9
+ Style/FileName:
10
+ Exclude:
11
+ - lib/activerecord-import_with_callbacks.rb
@@ -0,0 +1,7 @@
1
+ ---
2
+ before_script: createdb activerecord-import_with_callbacks
3
+ cache: bundler
4
+ language: ruby
5
+ rvm:
6
+ - 2.2
7
+ sudo: false
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at erik@instacart.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'pg'
7
+ gem 'pry'
8
+ gem 'pry-byebug'
9
+ gem 'rake', '~> 11.2'
10
+ gem 'rubocop', '~> 0.41.1'
11
+ end
12
+
13
+ group :test do
14
+ gem 'database_cleaner'
15
+ gem 'rspec', '~> 3.0'
16
+ gem 'simplecov'
17
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Instacart
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,55 @@
1
+ # ActiveRecord::ImportWithCallbacks
2
+
3
+ A library for bulk inserting data using ActiveRecord with callbacks.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'activerecord-import_with_callbacks'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install activerecord-import_with_callbacks
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ users = Array.new(200) { |i| User.new(name: "User #{i}") }
25
+ # Default batch size is 100 but can be overridden
26
+ result = User.import_with_callbacks(users, batch_size: 50)
27
+ result.num_inserts #=> 4
28
+ result.ids.size #=> 200
29
+ result.failed_instances #=> []
30
+ ```
31
+
32
+ ## Development
33
+
34
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
35
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
36
+ prompt that will allow you to experiment.
37
+
38
+ To install this gem onto your local machine, run `bundle exec rake install`. To
39
+ release a new version, update the version number in `version.rb`, and then run
40
+ `bundle exec rake release`, which will create a git tag for the version, push
41
+ git commits and tags, and push the `.gem` file to
42
+ [rubygems.org](https://rubygems.org).
43
+
44
+ ## Contributing
45
+
46
+ Bug reports and pull requests are welcome on GitHub at
47
+ https://github.com/instacart/activerecord-import_with_callbacks. This project
48
+ is intended to be a safe, welcoming space for collaboration, and contributors
49
+ are expected to adhere to the [Contributor
50
+ Covenant](http://contributor-covenant.org) code of conduct.
51
+
52
+ ## License
53
+
54
+ The gem is available as open source under the terms of the [MIT
55
+ License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ RuboCop::RakeTask.new
8
+
9
+ task default: [:spec, :rubocop]
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'active_record/import_with_callbacks/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'activerecord-import_with_callbacks'
7
+ spec.version = ActiveRecord::ImportWithCallbacks::VERSION
8
+ spec.authors = ['Emmanuel Turlay', 'Erik Michaels-Ober']
9
+ spec.email = ['emmanuel@instacart.com', 'erik@instacart.com']
10
+
11
+ spec.summary = 'A library for bulk importing data using ActiveRecord'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/instacart/activerecord-import_with_callbacks'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split("\n").reject { |f| f.start_with?('spec/') }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.required_ruby_version = '~> 2.2'
20
+
21
+ spec.add_dependency 'activerecord', '>= 4.1', '< 5.1'
22
+ spec.add_dependency 'activerecord-import', '~> 0.19.1'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.11'
25
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'active_record'
5
+
6
+ spec_dir = Pathname.new(File.dirname(__FILE__)).join('../spec')
7
+ database = spec_dir.join('database.yml')
8
+
9
+ ActiveRecord::Base.configurations['test'] = YAML.load_file(database)['test']
10
+ ActiveRecord::Base.default_timezone = :utc
11
+ ActiveRecord::Base.establish_connection(:test)
12
+ ActiveRecord::Base.raise_in_transactional_callbacks = true
13
+
14
+ require_relative '../spec/schema'
15
+
16
+ Dir[spec_dir.join('models/*.rb')].each { |file| require_relative "../#{file}" }
17
+
18
+ require 'active_record/import_with_callbacks'
19
+
20
+ ActiveRecord::Import.require_adapter('postgresql')
21
+
22
+ require 'pry'
23
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ createdb activerecord-import_with_callbacks
@@ -0,0 +1,119 @@
1
+ require 'active_record'
2
+ require 'activerecord-import/base'
3
+ require 'fiber'
4
+
5
+ module ActiveRecord
6
+ module ImportWithCallbacks
7
+ Result = Class.new(ActiveRecord::Import::Result)
8
+ DEFAULT_BATCH_SIZE = 100
9
+ DEFAULT_RECURSIVE = true
10
+ extend ActiveSupport::Concern
11
+
12
+ # Insert multiple records in batches with callbacks
13
+ #
14
+ # @param [Array<ActiveRecord::Base>] records
15
+ # @param [Hash] options
16
+ # @option options [Boolean] :validate Default: true
17
+ # @option options [Array, Hash] :on_duplicate_key_update
18
+ # @option options [Array<ActiveRecord::Base>] :synchronize Synchronize
19
+ # existing records in memory with updates from the import
20
+ # @option options [Boolan] :timestamps
21
+ # @option options [Boolan] :recursive Import all autosave associations
22
+ # @option options [Integer] :batch_size Default: 100
23
+ # @return [ActiveRecord::Import::Result]
24
+ def import_with_callbacks(*args)
25
+ options = args.extract_options!
26
+ records = args.pop
27
+ options[:batch_size] = DEFAULT_BATCH_SIZE unless options.key?(:batch_size)
28
+ options[:recursive] = DEFAULT_RECURSIVE unless options.key?(:recursive)
29
+ import_in_transaction(records, options)
30
+ end
31
+
32
+ private
33
+
34
+ def import_in_transaction(records, options)
35
+ self.transaction do
36
+ import_in_batches(records, options)
37
+ end
38
+ end
39
+
40
+ def import_in_batches(records, options)
41
+ results = records.each_slice(options.fetch(:batch_size)).map do |slice|
42
+ slice.each { |record| record.send(:remember_transaction_record_state) }
43
+ with_callbacks(slice) do
44
+ import(slice, options)
45
+ end
46
+ end
47
+ merge_results(results)
48
+ end
49
+
50
+ def with_callbacks(records)
51
+ run_callbacks(records, :before, :save)
52
+ around_save_fibers = split_callbacks(records, :save)
53
+ run_callbacks(records, :before, :create)
54
+ around_create_fibers = split_callbacks(records, :create)
55
+ result = yield
56
+ # Execute the "after" part of around_create callbacks
57
+ around_create_fibers.select(&:alive?).each(&:resume)
58
+ run_callbacks(records, :after, :create)
59
+ # Execute the "after" part of around_save callbacks
60
+ around_save_fibers.select(&:alive?).each(&:resume)
61
+ run_callbacks(records, :after, :save, :commit)
62
+ result
63
+ end
64
+
65
+ def run_callbacks(records, before_or_after, *callbacks)
66
+ callbacks.each do |callback|
67
+ records.each do |record|
68
+ chain = build_callback_chain(record, before_or_after, callback)
69
+ run_chain(record, chain)
70
+ end
71
+ end
72
+ end
73
+
74
+ def split_callbacks(records, callback)
75
+ fibers = records.map do |record|
76
+ chain = build_callback_chain(record, :around, callback)
77
+ split_chain(record, chain)
78
+ end
79
+ # Execute the before part of around_* callbacks and pause at the yield
80
+ fibers.each(&:resume)
81
+ end
82
+
83
+ def build_callback_chain(record, before_after_or_around, callback)
84
+ chain = ActiveSupport::Callbacks::CallbackChain.new(callback, {})
85
+ callbacks = record.public_send(:"_#{callback}_callbacks")
86
+ append_callbacks_to_chain(callbacks, chain, before_after_or_around)
87
+ chain
88
+ end
89
+
90
+ def append_callbacks_to_chain(callbacks, chain, kind)
91
+ callbacks.select { |cb| cb.kind.equal?(kind) }.each do |callback|
92
+ next if callback.filter.to_s.start_with?('autosave_')
93
+ chain.append(callback)
94
+ end
95
+ end
96
+
97
+ def run_chain(m, chain)
98
+ runner = chain.compile
99
+ e = ActiveSupport::Callbacks::Filters::Environment.new(m, false)
100
+ runner.call(e)
101
+ end
102
+
103
+ def split_chain(m, chain)
104
+ runner = chain.compile
105
+ p = proc { Fiber.yield }
106
+ e = ActiveSupport::Callbacks::Filters::Environment.new(m, false, nil, p)
107
+ Fiber.new { runner.call(e) }
108
+ end
109
+
110
+ def merge_results(results)
111
+ failed_instances = results.inject([]) { |a, e| a + e.failed_instances }
112
+ num_inserts = results.inject(0) { |a, e| a + e.num_inserts }
113
+ ids = results.inject([]) { |a, e| a + e.ids }
114
+ Result.new(failed_instances, num_inserts, ids)
115
+ end
116
+ end
117
+
118
+ Base.extend(ActiveRecord::ImportWithCallbacks)
119
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module ImportWithCallbacks
3
+ VERSION = '0.3.7'.freeze
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_record/import_with_callbacks'
2
+ require 'active_record/import_with_callbacks/version'
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-import_with_callbacks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.7
5
+ platform: ruby
6
+ authors:
7
+ - Emmanuel Turlay
8
+ - Erik Michaels-Ober
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-10-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '4.1'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '5.1'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '4.1'
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.1'
34
+ - !ruby/object:Gem::Dependency
35
+ name: activerecord-import
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.19.1
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.19.1
48
+ - !ruby/object:Gem::Dependency
49
+ name: bundler
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.11'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.11'
62
+ description: A library for bulk importing data using ActiveRecord
63
+ email:
64
+ - emmanuel@instacart.com
65
+ - erik@instacart.com
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - ".gitignore"
71
+ - ".rspec"
72
+ - ".rubocop.yml"
73
+ - ".travis.yml"
74
+ - CODE_OF_CONDUCT.md
75
+ - Gemfile
76
+ - LICENSE.txt
77
+ - README.md
78
+ - Rakefile
79
+ - activerecord-import_with_callbacks.gemspec
80
+ - bin/console
81
+ - bin/setup
82
+ - lib/active_record/import_with_callbacks.rb
83
+ - lib/active_record/import_with_callbacks/version.rb
84
+ - lib/activerecord-import_with_callbacks.rb
85
+ homepage: https://github.com/instacart/activerecord-import_with_callbacks
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '2.2'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.6.13
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: A library for bulk importing data using ActiveRecord
109
+ test_files: []