rimportor 0.3
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +76 -0
- data/Rakefile +23 -0
- data/lib/generators/install_generator.rb +11 -0
- data/lib/rimportor/active_record/adapter/mysql2.rb +40 -0
- data/lib/rimportor/active_record/import.rb +70 -0
- data/lib/rimportor/active_record/sql_builder.rb +43 -0
- data/lib/rimportor/error/bulk_validation.rb +6 -0
- data/lib/rimportor/error/invalid_adapter.rb +6 -0
- data/lib/rimportor/plugin.rb +28 -0
- data/lib/rimportor/util/connection.rb +13 -0
- data/lib/rimportor/version.rb +3 -0
- data/lib/rimportor.rb +29 -0
- data/lib/templates/rimportor.rb +7 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 11df56cdb93fc78219c02060fa2ad2dbbf944217
|
4
|
+
data.tar.gz: e5f6b49bc437eff5e28ad65c08da8a98d6314f1f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fa2bddde54b617c167030ba41bb7a7a79dde80eeccf3455287cf227eda27440a2b4bbe9cea4307f9b0d2ad711d4d3d608c9507faf84b36f0fa9bcd12d62e14e1
|
7
|
+
data.tar.gz: 1e849d21abee6a89233174355441bb78d22cfc2996da0a501fb0cfd893333bba1bc97bc71b9777e1c2a6d1ea6dfa6c187631c64f7077b3a9101ec7f0b33b267e
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Erwin Schens
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Rimportor
|
2
|
+
|
3
|
+
Rimportor is a new and modern bulk importing gem.
|
4
|
+
It utilizes arel under the hood to generate insert statements.
|
5
|
+
By working directly on the model Rimportor is able to execute callbacks and validate the records before inserting them into the database - **which is missing in most importing gems**.
|
6
|
+
|
7
|
+
### Features
|
8
|
+
- Import in batches
|
9
|
+
- Validation of the bulk
|
10
|
+
- Callback execution
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'rimportor', '~> 0.3'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install rimportor
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
Rimportor adds to every ActiveRecord model an additional method called rimport. This method then takes a collection of your records you want to persist.
|
30
|
+
Let me give you an example.
|
31
|
+
```ruby
|
32
|
+
users = []
|
33
|
+
1000.times.each { User.new(some_params) }
|
34
|
+
User.rimport users # Imports your collection as a bulk insert to your database
|
35
|
+
```
|
36
|
+
But wait... what about validations and callbacks of my bulk?
|
37
|
+
Rimportor got you! Just add some configuration options for your rimport.
|
38
|
+
Let me show you what i mean.
|
39
|
+
```ruby
|
40
|
+
users = []
|
41
|
+
1000.times.each { User.new(some_params) }
|
42
|
+
|
43
|
+
# true if bulk valid and imported else false
|
44
|
+
User.rimport users, before_callbacks: true,
|
45
|
+
after_callbacks: true,
|
46
|
+
validate_bulk: true
|
47
|
+
```
|
48
|
+
The rimport method returns true if your bulk is valid and all callbacks are executed.
|
49
|
+
If an error occurs Rimportor won't insert your bulk in the database.
|
50
|
+
|
51
|
+
And what if i want to insert my records in batches? Rimportor got your back on that too.
|
52
|
+
```ruby
|
53
|
+
users = []
|
54
|
+
1000.times.each { User.new(some_params) }
|
55
|
+
|
56
|
+
# Rimportor will insert the 1000 records in 100 chunks
|
57
|
+
User.rimport users, batch_size: 100
|
58
|
+
```
|
59
|
+
|
60
|
+
## Benchmarks
|
61
|
+
|
62
|
+
In progress...
|
63
|
+
|
64
|
+
## Development
|
65
|
+
|
66
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
67
|
+
|
68
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
69
|
+
|
70
|
+
## Contributing
|
71
|
+
|
72
|
+
1. Fork it
|
73
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
74
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
75
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
76
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Rimportor'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Rimportor
|
4
|
+
class InstallGenerator < ::Rails::Generators::Base
|
5
|
+
source_root(File.expand_path(File.dirname(__FILE__)))
|
6
|
+
|
7
|
+
def copy_initializer
|
8
|
+
copy_file '../templates/rimportor.rb', 'config/initializers/rimportor.rb'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Rimportor
|
2
|
+
module ActiveRecord
|
3
|
+
module Adapter
|
4
|
+
class Mysql2
|
5
|
+
|
6
|
+
def max_allowed_packet
|
7
|
+
exec_in_pool do |connection|
|
8
|
+
result = connection.execute("SHOW VARIABLES like 'max_allowed_packet';")
|
9
|
+
val = result.respond_to?(:fetch_row) ? result.fetch_row[1] : result.first[1]
|
10
|
+
val.to_i
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def exec_in_pool
|
15
|
+
::Rimportor::Util::Connection.in_pool do |connection|
|
16
|
+
yield(connection)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def statement_too_big?(statement)
|
21
|
+
statement.size > max_allowed_packet
|
22
|
+
end
|
23
|
+
|
24
|
+
def exec_insert(import_statement)
|
25
|
+
insert_statement, value_statements = import_statement
|
26
|
+
if statement_too_big? ("#{insert_statement}, #{value_statements.join(',')}")
|
27
|
+
puts 'Statement too big'
|
28
|
+
else
|
29
|
+
exec_statement "#{insert_statement},#{value_statements.join(',')}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def exec_statement(statement)
|
34
|
+
exec_in_pool { |connection| connection.execute statement }
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
|
3
|
+
module Rimportor
|
4
|
+
module ActiveRecord
|
5
|
+
class Import
|
6
|
+
|
7
|
+
def initialize(bulk, adapter, opts = {})
|
8
|
+
@bulk = bulk
|
9
|
+
@adapter = adapter
|
10
|
+
@before_callbacks = !!opts[:before_callbacks]
|
11
|
+
@after_callbacks = !!opts[:after_callbacks]
|
12
|
+
@validate_bulk = !!opts[:validate_bulk]
|
13
|
+
@batch_size = opts[:batch_size] ? opts[:batch_size] : 1000
|
14
|
+
@threads = Rimportor.configuration.threads
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_before_callbacks
|
18
|
+
::Parallel.map(@bulk, in_threads: @threads) do |element|
|
19
|
+
execute_callbacks(element, :before)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_after_callbacks
|
24
|
+
::Parallel.map(@bulk, in_threads: @threads) do |element|
|
25
|
+
execute_callbacks(element, :after)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def run_validations
|
30
|
+
validation_result = ::Parallel.map(@bulk, in_threads: @threads) do |element|
|
31
|
+
element.valid?
|
32
|
+
end.all?
|
33
|
+
raise Rimportor::Error::BulkValidation.new("Your bulk is not valid") unless validation_result
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute_callbacks(element, before_or_after)
|
37
|
+
case before_or_after
|
38
|
+
when :before
|
39
|
+
element.run_callbacks(:save) { false }
|
40
|
+
when :after
|
41
|
+
element.run_callbacks(:save) { true }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def import_statement(batch)
|
46
|
+
insert_statement = SqlBuilder.new(batch.first).full_insert_statement
|
47
|
+
result = ::Parallel.map(batch.drop(1), in_threads: @threads) do |element|
|
48
|
+
@adapter.exec_in_pool { SqlBuilder.new(element).partial_insert_statement.gsub('VALUES', '') }
|
49
|
+
end
|
50
|
+
[insert_statement, result]
|
51
|
+
end
|
52
|
+
|
53
|
+
def exec_statement
|
54
|
+
begin
|
55
|
+
run_validations if @validate_bulk
|
56
|
+
run_before_callbacks if @before_callbacks
|
57
|
+
@bulk.each_slice(@batch_size) do |batch|
|
58
|
+
@adapter.exec_insert(import_statement(batch))
|
59
|
+
end
|
60
|
+
run_after_callbacks if @after_callbacks
|
61
|
+
true
|
62
|
+
rescue => e
|
63
|
+
puts "Error importing the bulk. Reason #{e.message}"
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rimportor
|
2
|
+
module ActiveRecord
|
3
|
+
class SqlBuilder
|
4
|
+
|
5
|
+
def initialize(model)
|
6
|
+
@model = model
|
7
|
+
set_timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
def full_insert_statement
|
11
|
+
insert_manager.tap do |im|
|
12
|
+
im.insert(arel_for_create)
|
13
|
+
end.to_sql
|
14
|
+
end
|
15
|
+
|
16
|
+
def partial_insert_statement
|
17
|
+
insert_manager.insert(arel_for_create).to_sql
|
18
|
+
end
|
19
|
+
|
20
|
+
def arel_for_create
|
21
|
+
@model.send(:arel_attributes_with_values_for_create, @model.attribute_names)
|
22
|
+
end
|
23
|
+
|
24
|
+
def insert_manager
|
25
|
+
@model.class.arel_table.create_insert
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_timestamps
|
29
|
+
set_created_at
|
30
|
+
set_updated_at
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_created_at
|
34
|
+
@model.updated_at = Time.zone.now if @model.respond_to? :updated_at
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_updated_at
|
38
|
+
@model.created_at = Time.zone.now if @model.respond_to? :created_at
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Rimportor
|
2
|
+
module Plugin
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def rimport(records, options = {})
|
10
|
+
::Rimportor::ActiveRecord::Import.new(records, self.current_adapter, options).exec_statement
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_adapter
|
14
|
+
load_adapter(::ActiveRecord::Base.connection_config[:adapter])
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_adapter(adapter_name)
|
18
|
+
begin
|
19
|
+
::Rimportor::ActiveRecord::Adapter.const_get(adapter_name.to_s.camelize).new
|
20
|
+
rescue => e
|
21
|
+
raise ::Rimportor::Error::InvalidAdapter.new("Invalid adapter. Reason #{e}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/rimportor.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rimportor/active_record/sql_builder'
|
2
|
+
require 'rimportor/active_record/import'
|
3
|
+
require 'rimportor/plugin'
|
4
|
+
require 'rimportor/error/bulk_validation'
|
5
|
+
require 'rimportor/error/invalid_adapter'
|
6
|
+
require 'rimportor/active_record/adapter/mysql2'
|
7
|
+
require 'rimportor/util/connection'
|
8
|
+
require 'generators/install_generator'
|
9
|
+
|
10
|
+
module Rimportor
|
11
|
+
class << self
|
12
|
+
attr_accessor :configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configure
|
16
|
+
self.configuration ||= Configuration.new
|
17
|
+
yield(configuration) if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
class Configuration
|
21
|
+
attr_accessor :threads
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@threads = 4
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
ActiveRecord::Base.send :include, Rimportor::Plugin
|
@@ -0,0 +1,7 @@
|
|
1
|
+
Rimportor.configure do |config|
|
2
|
+
# Configure how many threads rimportor should use for importing.
|
3
|
+
# Consider that rimportor will use threads not only for building the statement
|
4
|
+
# but also for running validations for your bulk.
|
5
|
+
# The default value are 4 threads
|
6
|
+
# config.threads = 4
|
7
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rimportor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.3'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Erwin Schens
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 4.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 4.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: parallel
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.5'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.5.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.5'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.5.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: sqlite3
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rspec-rails
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 3.0.0
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 3.0.0
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 3.0.0
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 3.0.0
|
87
|
+
description: Fast, modern and concurrent bulk import for ruby on rails.
|
88
|
+
email:
|
89
|
+
- erwin.schens@qurasoft.de
|
90
|
+
executables: []
|
91
|
+
extensions: []
|
92
|
+
extra_rdoc_files: []
|
93
|
+
files:
|
94
|
+
- MIT-LICENSE
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- lib/generators/install_generator.rb
|
98
|
+
- lib/rimportor.rb
|
99
|
+
- lib/rimportor/active_record/adapter/mysql2.rb
|
100
|
+
- lib/rimportor/active_record/import.rb
|
101
|
+
- lib/rimportor/active_record/sql_builder.rb
|
102
|
+
- lib/rimportor/error/bulk_validation.rb
|
103
|
+
- lib/rimportor/error/invalid_adapter.rb
|
104
|
+
- lib/rimportor/plugin.rb
|
105
|
+
- lib/rimportor/util/connection.rb
|
106
|
+
- lib/rimportor/version.rb
|
107
|
+
- lib/templates/rimportor.rb
|
108
|
+
homepage: https://github.com/ndea/rimportor
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.4.8
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Fast, modern and concurrent bulk import for ruby on rails.
|
132
|
+
test_files: []
|