rimportor 0.3

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: 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,6 @@
1
+ module Rimportor
2
+ module Error
3
+ class BulkValidation < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Rimportor
2
+ module Error
3
+ class InvalidAdapter < StandardError
4
+ end
5
+ end
6
+ 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
@@ -0,0 +1,13 @@
1
+ module Rimportor
2
+ module Util
3
+ class Connection
4
+
5
+ def self.in_pool
6
+ ::ActiveRecord::Base.connection_pool.with_connection do |connection|
7
+ yield(connection)
8
+ end
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Rimportor
2
+ VERSION = "0.3"
3
+ 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: []