activerecord-import-in4systems 0.2.10

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 (29) hide show
  1. data/README.markdown +24 -0
  2. data/Rakefile +74 -0
  3. data/VERSION +1 -0
  4. data/lib/activerecord-import.rb +16 -0
  5. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +10 -0
  6. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -0
  7. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +6 -0
  8. data/lib/activerecord-import/active_record/adapters/mysql_adapter.rb +6 -0
  9. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +7 -0
  10. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +7 -0
  11. data/lib/activerecord-import/active_record/adapters/sqlanywhere_adapter.rb +7 -0
  12. data/lib/activerecord-import/active_record/adapters/sqlanywhere_jdbc_in4systems_adapter.rb +7 -0
  13. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +7 -0
  14. data/lib/activerecord-import/adapters/abstract_adapter.rb +119 -0
  15. data/lib/activerecord-import/adapters/mysql_adapter.rb +55 -0
  16. data/lib/activerecord-import/adapters/postgresql_adapter.rb +7 -0
  17. data/lib/activerecord-import/adapters/sqlanywhere_adapter.rb +7 -0
  18. data/lib/activerecord-import/adapters/sqlanywhere_jdbc_in4systems_adapter.rb +7 -0
  19. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +5 -0
  20. data/lib/activerecord-import/base.rb +24 -0
  21. data/lib/activerecord-import/import.rb +372 -0
  22. data/lib/activerecord-import/mysql.rb +8 -0
  23. data/lib/activerecord-import/mysql2.rb +8 -0
  24. data/lib/activerecord-import/postgresql.rb +8 -0
  25. data/lib/activerecord-import/sqlanywhere.rb +8 -0
  26. data/lib/activerecord-import/sqlanywhere_jdbc_in4systems.rb +8 -0
  27. data/lib/activerecord-import/sqlite3.rb +8 -0
  28. data/lib/activerecord-import/synchronize.rb +55 -0
  29. metadata +170 -0
data/README.markdown ADDED
@@ -0,0 +1,24 @@
1
+ # activerecord-import
2
+
3
+ activerecord-import is a library for bulk inserting data using ActiveRecord.
4
+
5
+ For more information on activerecord-import please see its wiki: https://github.com/zdennis/activerecord-import/wiki
6
+
7
+ # License
8
+
9
+ This is licensed under the ruby license.
10
+
11
+ # Author
12
+
13
+ Zach Dennis (zach.dennis@gmail.com)
14
+
15
+ # Contributors
16
+
17
+ * Blythe Dunham
18
+ * Gabe da Silveira
19
+ * Henry Work
20
+ * James Herdman
21
+ * Marcus Crafter
22
+ * Thibaud Guillaume-Gentil
23
+ * Mark Van Holstyn
24
+ * Victor Costan
data/Rakefile ADDED
@@ -0,0 +1,74 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "activerecord-import"
11
+ gem.summary = %Q{Bulk-loading extension for ActiveRecord}
12
+ gem.description = %Q{Extraction of the ActiveRecord::Base#import functionality from ar-extensions for Rails 3 and beyond}
13
+ gem.email = "zach.dennis@gmail.com"
14
+ gem.homepage = "http://github.com/zdennis/activerecord-import"
15
+ gem.authors = ["Zach Dennis"]
16
+ gem.files = FileList["VERSION", "Rakefile", "README*", "lib/**/*"]
17
+
18
+ bundler = Bundler.load
19
+ bundler.dependencies_for(:default).each do |dependency|
20
+ gem.add_dependency dependency.name, *dependency.requirements_list
21
+ end
22
+
23
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
+ end
25
+ Jeweler::GemcutterTasks.new
26
+ rescue LoadError
27
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
28
+ end
29
+
30
+ namespace :display do
31
+ task :notice do
32
+ puts
33
+ puts "To run tests you must supply the adapter, see rake -T for more information."
34
+ puts
35
+ end
36
+ end
37
+ task :default => ["display:notice"]
38
+
39
+ ADAPTERS = %w(mysql mysql2 jdbcmysql postgresql sqlite3 seamless_database_pool)
40
+ ADAPTERS.each do |adapter|
41
+ namespace :test do
42
+ desc "Runs #{adapter} database tests."
43
+ Rake::TestTask.new(adapter) do |t|
44
+ # FactoryGirl has an issue with warnings, so turn off, so noisy
45
+ # t.warning = true
46
+ t.test_files = FileList["test/adapters/#{adapter}.rb", "test/*_test.rb", "test/active_record/*_test.rb", "test/#{adapter}/**/*_test.rb"]
47
+ end
48
+ task adapter
49
+ end
50
+ end
51
+
52
+ begin
53
+ require 'rcov/rcovtask'
54
+ adapter = ENV['ARE_DB']
55
+ Rcov::RcovTask.new do |test|
56
+ test.libs << 'test'
57
+ test.pattern = ["test/adapters/#{adapter}.rb", "test/*_test.rb", "test/#{adapter}/**/*_test.rb"]
58
+ test.verbose = true
59
+ end
60
+ rescue LoadError
61
+ task :rcov do
62
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install rcov"
63
+ end
64
+ end
65
+
66
+ require 'rdoc/task'
67
+ Rake::RDocTask.new do |rdoc|
68
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
69
+
70
+ rdoc.rdoc_dir = 'rdoc'
71
+ rdoc.title = "activerecord-import #{version}"
72
+ rdoc.rdoc_files.include('README*')
73
+ rdoc.rdoc_files.include('lib/**/*.rb')
74
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.10
@@ -0,0 +1,16 @@
1
+ class ActiveRecord::Base
2
+ class << self
3
+ def establish_connection_with_activerecord_import(*args)
4
+ establish_connection_without_activerecord_import(*args)
5
+ ActiveSupport.run_load_hooks(:active_record_connection_established, connection_pool)
6
+ end
7
+ alias_method_chain :establish_connection, :activerecord_import
8
+ end
9
+ end
10
+
11
+ ActiveSupport.on_load(:active_record_connection_established) do |connection_pool|
12
+ if !ActiveRecord.const_defined?(:Import) || !ActiveRecord::Import.respond_to?(:load_from_connection_pool)
13
+ require File.join File.dirname(__FILE__), "activerecord-import/base"
14
+ end
15
+ ActiveRecord::Import.load_from_connection_pool connection_pool
16
+ end
@@ -0,0 +1,10 @@
1
+ require "activerecord-import/adapters/abstract_adapter"
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+ class AbstractAdapter # :nodoc:
6
+ extend ActiveRecord::Import::AbstractAdapter::ClassMethods
7
+ include ActiveRecord::Import::AbstractAdapter::InstanceMethods
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ require "active_record/connection_adapters/mysql_adapter"
2
+ require "activerecord-import/adapters/mysql_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::MysqlAdapter
5
+ include ActiveRecord::Import::MysqlAdapter
6
+ end
@@ -0,0 +1,6 @@
1
+ require "active_record/connection_adapters/mysql2_adapter"
2
+ require "activerecord-import/adapters/mysql_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::Mysql2Adapter
5
+ include ActiveRecord::Import::MysqlAdapter
6
+ end
@@ -0,0 +1,6 @@
1
+ require "active_record/connection_adapters/mysql_adapter"
2
+ require "activerecord-import/adapters/mysql_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::MysqlAdapter
5
+ include ActiveRecord::Import::MysqlAdapter
6
+ end
@@ -0,0 +1,7 @@
1
+ require "active_record/connection_adapters/postgresql_adapter"
2
+ require "activerecord-import/adapters/postgresql_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
5
+ include ActiveRecord::Import::PostgreSQLAdapter
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ require "seamless_database_pool"
2
+ require "active_record/connection_adapters/seamless_database_pool_adapter"
3
+ require "activerecord-import/adapters/mysql_adapter"
4
+
5
+ class ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter
6
+ include ActiveRecord::Import::MysqlAdapter
7
+ end
@@ -0,0 +1,7 @@
1
+ require "active_record/connection_adapters/sqlanywhere_adapter"
2
+ require "activerecord-import/adapters/sqlanywhere_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::SqlanywhereAdapter
5
+ include ActiveRecord::Import::SqlanywhereAdapter
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ require "active_record/connection_adapters/sqlanywhere_jdbc_in4systems_adapter"
2
+ require "activerecord-import/adapters/sqlanywhere_jdbc_in4systems_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::SQLAnywhereJdbcIn4systemsAdapter
5
+ include ActiveRecord::Import::SQLAnywhereJdbcIn4systemsAdapter
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ require "active_record/connection_adapters/sqlite3_adapter"
2
+ require "activerecord-import/adapters/sqlite3_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::Sqlite3Adapter
5
+ include ActiveRecord::Import::Sqlite3Adapter
6
+ end
7
+
@@ -0,0 +1,119 @@
1
+ module ActiveRecord::Import::AbstractAdapter
2
+ NO_MAX_PACKET = 0
3
+ QUERY_OVERHEAD = 8 #This was shown to be true for MySQL, but it's not clear where the overhead is from.
4
+
5
+ module ClassMethods
6
+ def get_insert_value_sets( values, sql_size, max_bytes ) # :nodoc:
7
+ value_sets = []
8
+ arr, current_arr_values_size, current_size = [], 0, 0
9
+ values.each_with_index do |val,i|
10
+ comma_bytes = arr.size
11
+ sql_size_thus_far = sql_size + current_size + val.size + comma_bytes
12
+ if NO_MAX_PACKET == max_bytes or sql_size_thus_far <= max_bytes
13
+ current_size += val.bytesize
14
+ arr << val
15
+ else
16
+ value_sets << arr
17
+ arr = [ val ]
18
+ current_size = val.bytesize
19
+ end
20
+
21
+ # if we're on the last iteration push whatever we have in arr to value_sets
22
+ value_sets << arr if i == (values.size-1)
23
+ end
24
+ [ *value_sets ]
25
+ end
26
+ end
27
+
28
+ module InstanceMethods
29
+ def next_value_for_sequence(sequence_name)
30
+ %{#{sequence_name}.nextval}
31
+ end
32
+
33
+ # +sql+ can be a single string or an array. If it is an array all
34
+ # elements that are in position >= 1 will be appended to the final SQL.
35
+ def insert_many( sql, values, *args ) # :nodoc:
36
+ # the number of inserts default
37
+ number_of_inserts = 0
38
+
39
+ base_sql,post_sql = if sql.is_a?( String )
40
+ [ sql, '' ]
41
+ elsif sql.is_a?( Array )
42
+ [ sql.shift, sql.join( ' ' ) ]
43
+ end
44
+
45
+ sql_size = QUERY_OVERHEAD + base_sql.size + post_sql.size
46
+
47
+ # the number of bytes the requested insert statement values will take up
48
+ values_in_bytes = values.sum {|value| value.bytesize }
49
+
50
+ # the number of bytes (commas) it will take to comma separate our values
51
+ comma_separated_bytes = values.size-1
52
+
53
+ # the total number of bytes required if this statement is one statement
54
+ total_bytes = sql_size + values_in_bytes + comma_separated_bytes
55
+
56
+ max = max_allowed_packet
57
+
58
+ # if we can insert it all as one statement
59
+ if NO_MAX_PACKET == max or total_bytes < max
60
+ number_of_inserts += 1
61
+ sql2insert = base_sql + values.join( ',' ) + post_sql
62
+ insert( sql2insert, *args )
63
+ else
64
+ value_sets = self.class.get_insert_value_sets( values, sql_size, max )
65
+ value_sets.each do |values|
66
+ number_of_inserts += 1
67
+ sql2insert = base_sql + values.join( ',' ) + post_sql
68
+ insert( sql2insert, *args )
69
+ end
70
+ end
71
+
72
+ number_of_inserts
73
+ end
74
+
75
+ def pre_sql_statements(options)
76
+ sql = []
77
+ sql << options[:pre_sql] if options[:pre_sql]
78
+ sql << options[:command] if options[:command]
79
+ sql << "IGNORE" if options[:ignore]
80
+
81
+ #add keywords like IGNORE or DELAYED
82
+ if options[:keywords].is_a?(Array)
83
+ sql.concat(options[:keywords])
84
+ elsif options[:keywords]
85
+ sql << options[:keywords].to_s
86
+ end
87
+
88
+ sql
89
+ end
90
+
91
+ # Synchronizes the passed in ActiveRecord instances with the records in
92
+ # the database by calling +reload+ on each instance.
93
+ def after_import_synchronize( instances )
94
+ instances.each { |e| e.reload }
95
+ end
96
+
97
+ # Returns an array of post SQL statements given the passed in options.
98
+ def post_sql_statements( table_name, options ) # :nodoc:
99
+ post_sql_statements = []
100
+ if options[:on_duplicate_key_update]
101
+ post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update] )
102
+ end
103
+
104
+ #custom user post_sql
105
+ post_sql_statements << options[:post_sql] if options[:post_sql]
106
+
107
+ #with rollup
108
+ post_sql_statements << rollup_sql if options[:rollup]
109
+
110
+ post_sql_statements
111
+ end
112
+
113
+ # Returns the maximum number of bytes that the server will allow
114
+ # in a single packet
115
+ def max_allowed_packet
116
+ NO_MAX_PACKET
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,55 @@
1
+ module ActiveRecord::Import::MysqlAdapter
2
+ include ActiveRecord::Import::ImportSupport
3
+ include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
4
+
5
+ # Returns the maximum number of bytes that the server will allow
6
+ # in a single packet
7
+ def max_allowed_packet # :nodoc:
8
+ @max_allowed_packet ||= begin
9
+ result = execute( "SHOW VARIABLES like 'max_allowed_packet';" )
10
+ # original Mysql gem responds to #fetch_row while Mysql2 responds to #first
11
+ val = result.respond_to?(:fetch_row) ? result.fetch_row[1] : result.first[1]
12
+ val.to_i
13
+ end
14
+ end
15
+
16
+ # Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
17
+ # in +args+.
18
+ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
19
+ sql = ' ON DUPLICATE KEY UPDATE '
20
+ arg = args.first
21
+ if arg.is_a?( Array )
22
+ sql << sql_for_on_duplicate_key_update_as_array( table_name, arg )
23
+ elsif arg.is_a?( Hash )
24
+ sql << sql_for_on_duplicate_key_update_as_hash( table_name, arg )
25
+ elsif arg.is_a?( String )
26
+ sql << arg
27
+ else
28
+ raise ArgumentError.new( "Expected Array or Hash" )
29
+ end
30
+ sql
31
+ end
32
+
33
+ def sql_for_on_duplicate_key_update_as_array( table_name, arr ) # :nodoc:
34
+ results = arr.map do |column|
35
+ qc = quote_column_name( column )
36
+ "#{table_name}.#{qc}=VALUES(#{qc})"
37
+ end
38
+ results.join( ',' )
39
+ end
40
+
41
+ def sql_for_on_duplicate_key_update_as_hash( table_name, hsh ) # :nodoc:
42
+ sql = ' ON DUPLICATE KEY UPDATE '
43
+ results = hsh.map do |column1, column2|
44
+ qc1 = quote_column_name( column1 )
45
+ qc2 = quote_column_name( column2 )
46
+ "#{table_name}.#{qc1}=VALUES( #{qc2} )"
47
+ end
48
+ results.join( ',')
49
+ end
50
+
51
+ #return true if the statement is a duplicate key record error
52
+ def duplicate_key_update_error?(exception)# :nodoc:
53
+ exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
54
+ end
55
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveRecord::Import::PostgreSQLAdapter
2
+ include ActiveRecord::Import::ImportSupport
3
+
4
+ def next_value_for_sequence(sequence_name)
5
+ %{nextval('#{sequence_name}')}
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveRecord::Import::SqlanywhereAdapter
2
+ include ActiveRecord::Import::ImportSupport
3
+
4
+ def next_value_for_sequence(sequence_name)
5
+ %{nextval('#{sequence_name}')}
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveRecord::Import::SqlanywhereJdbcIn4systemsAdapter
2
+ include ActiveRecord::Import::ImportSupport
3
+
4
+ def next_value_for_sequence(sequence_name)
5
+ %{nextval('#{sequence_name}')}
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord::Import::Sqlite3Adapter
2
+ def next_value_for_sequence(sequence_name)
3
+ %{nextval('#{sequence_name}')}
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+ require "pathname"
2
+ require "active_record"
3
+ require "active_record/version"
4
+
5
+ module ActiveRecord::Import
6
+ AdapterPath = File.join File.expand_path(File.dirname(__FILE__)), "/active_record/adapters"
7
+
8
+ # Loads the import functionality for a specific database adapter
9
+ def self.require_adapter(adapter)
10
+ require File.join(AdapterPath,"/abstract_adapter")
11
+ require File.join(AdapterPath,"/#{adapter}_adapter")
12
+ end
13
+
14
+ # Loads the import functionality for the passed in ActiveRecord connection
15
+ def self.load_from_connection_pool(connection_pool)
16
+ require_adapter connection_pool.spec.config[:adapter]
17
+ end
18
+ end
19
+
20
+
21
+ this_dir = Pathname.new File.dirname(__FILE__)
22
+ require this_dir.join("import").to_s
23
+ require this_dir.join("active_record/adapters/abstract_adapter").to_s
24
+ require this_dir.join("synchronize").to_s
@@ -0,0 +1,372 @@
1
+ require "ostruct"
2
+
3
+ module ActiveRecord::Import::ConnectionAdapters ; end
4
+
5
+ module ActiveRecord::Import #:nodoc:
6
+ class Result < Struct.new(:failed_instances, :num_inserts)
7
+ end
8
+
9
+ module ImportSupport #:nodoc:
10
+ def supports_import? #:nodoc:
11
+ true
12
+ end
13
+ end
14
+
15
+ module OnDuplicateKeyUpdateSupport #:nodoc:
16
+ def supports_on_duplicate_key_update? #:nodoc:
17
+ true
18
+ end
19
+ end
20
+
21
+ class MissingColumnError < StandardError
22
+ def initialize(name, index)
23
+ super "Missing column for value <#{name}> at index #{index}"
24
+ end
25
+ end
26
+ end
27
+
28
+ class ActiveRecord::Base
29
+ class << self
30
+
31
+ # use tz as set in ActiveRecord::Base
32
+ tproc = lambda do
33
+ ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
34
+ end
35
+
36
+ AREXT_RAILS_COLUMNS = {
37
+ :create => { "created_on" => tproc ,
38
+ "created_at" => tproc },
39
+ :update => { "updated_on" => tproc ,
40
+ "updated_at" => tproc }
41
+ }
42
+ AREXT_RAILS_COLUMN_NAMES = AREXT_RAILS_COLUMNS[:create].keys + AREXT_RAILS_COLUMNS[:update].keys
43
+
44
+ # Returns true if the current database connection adapter
45
+ # supports import functionality, otherwise returns false.
46
+ def supports_import?
47
+ connection.supports_import?
48
+ rescue NoMethodError
49
+ false
50
+ end
51
+
52
+ # Returns true if the current database connection adapter
53
+ # supports on duplicate key update functionality, otherwise
54
+ # returns false.
55
+ def supports_on_duplicate_key_update?
56
+ connection.supports_on_duplicate_key_update?
57
+ rescue NoMethodError
58
+ false
59
+ end
60
+
61
+ # Imports a collection of values to the database.
62
+ #
63
+ # This is more efficient than using ActiveRecord::Base#create or
64
+ # ActiveRecord::Base#save multiple times. This method works well if
65
+ # you want to create more than one record at a time and do not care
66
+ # about having ActiveRecord objects returned for each record
67
+ # inserted.
68
+ #
69
+ # This can be used with or without validations. It does not utilize
70
+ # the ActiveRecord::Callbacks during creation/modification while
71
+ # performing the import.
72
+ #
73
+ # == Usage
74
+ # Model.import array_of_models
75
+ # Model.import column_names, array_of_values
76
+ # Model.import column_names, array_of_values, options
77
+ #
78
+ # ==== Model.import array_of_models
79
+ #
80
+ # With this form you can call _import_ passing in an array of model
81
+ # objects that you want updated.
82
+ #
83
+ # ==== Model.import column_names, array_of_values
84
+ #
85
+ # The first parameter +column_names+ is an array of symbols or
86
+ # strings which specify the columns that you want to update.
87
+ #
88
+ # The second parameter, +array_of_values+, is an array of
89
+ # arrays. Each subarray is a single set of values for a new
90
+ # record. The order of values in each subarray should match up to
91
+ # the order of the +column_names+.
92
+ #
93
+ # ==== Model.import column_names, array_of_values, options
94
+ #
95
+ # The first two parameters are the same as the above form. The third
96
+ # parameter, +options+, is a hash. This is optional. Please see
97
+ # below for what +options+ are available.
98
+ #
99
+ # == Options
100
+ # * +validate+ - true|false, tells import whether or not to use \
101
+ # ActiveRecord validations. Validations are enforced by default.
102
+ # * +on_duplicate_key_update+ - an Array or Hash, tells import to \
103
+ # use MySQL's ON DUPLICATE KEY UPDATE ability. See On Duplicate\
104
+ # Key Update below.
105
+ # * +synchronize+ - an array of ActiveRecord instances for the model
106
+ # that you are currently importing data into. This synchronizes
107
+ # existing model instances in memory with updates from the import.
108
+ # * +timestamps+ - true|false, tells import to not add timestamps \
109
+ # (if false) even if record timestamps is disabled in ActiveRecord::Base
110
+ #
111
+ # == Examples
112
+ # class BlogPost < ActiveRecord::Base ; end
113
+ #
114
+ # # Example using array of model objects
115
+ # posts = [ BlogPost.new :author_name=>'Zach Dennis', :title=>'AREXT',
116
+ # BlogPost.new :author_name=>'Zach Dennis', :title=>'AREXT2',
117
+ # BlogPost.new :author_name=>'Zach Dennis', :title=>'AREXT3' ]
118
+ # BlogPost.import posts
119
+ #
120
+ # # Example using column_names and array_of_values
121
+ # columns = [ :author_name, :title ]
122
+ # values = [ [ 'zdennis', 'test post' ], [ 'jdoe', 'another test post' ] ]
123
+ # BlogPost.import columns, values
124
+ #
125
+ # # Example using column_names, array_of_value and options
126
+ # columns = [ :author_name, :title ]
127
+ # values = [ [ 'zdennis', 'test post' ], [ 'jdoe', 'another test post' ] ]
128
+ # BlogPost.import( columns, values, :validate => false )
129
+ #
130
+ # # Example synchronizing existing instances in memory
131
+ # post = BlogPost.find_by_author_name( 'zdennis' )
132
+ # puts post.author_name # => 'zdennis'
133
+ # columns = [ :author_name, :title ]
134
+ # values = [ [ 'yoda', 'test post' ] ]
135
+ # BlogPost.import posts, :synchronize=>[ post ]
136
+ # puts post.author_name # => 'yoda'
137
+ #
138
+ # # Example synchronizing unsaved/new instances in memory by using a uniqued imported field
139
+ # posts = [BlogPost.new(:title => "Foo"), BlogPost.new(:title => "Bar")]
140
+ # BlogPost.import posts, :synchronize => posts
141
+ # puts posts.first.new_record? # => false
142
+ #
143
+ # == On Duplicate Key Update (MySQL only)
144
+ #
145
+ # The :on_duplicate_key_update option can be either an Array or a Hash.
146
+ #
147
+ # ==== Using an Array
148
+ #
149
+ # The :on_duplicate_key_update option can be an array of column
150
+ # names. The column names are the only fields that are updated if
151
+ # a duplicate record is found. Below is an example:
152
+ #
153
+ # BlogPost.import columns, values, :on_duplicate_key_update=>[ :date_modified, :content, :author ]
154
+ #
155
+ # ==== Using A Hash
156
+ #
157
+ # The :on_duplicate_key_update option can be a hash of column name
158
+ # to model attribute name mappings. This gives you finer grained
159
+ # control over what fields are updated with what attributes on your
160
+ # model. Below is an example:
161
+ #
162
+ # BlogPost.import columns, attributes, :on_duplicate_key_update=>{ :title => :title }
163
+ #
164
+ # = Returns
165
+ # This returns an object which responds to +failed_instances+ and +num_inserts+.
166
+ # * failed_instances - an array of objects that fails validation and were not committed to the database. An empty array if no validation is performed.
167
+ # * num_inserts - the number of insert statements it took to import the data
168
+ def import( *args )
169
+ options = { :validate=>true, :timestamps=>true }
170
+ options.merge!( args.pop ) if args.last.is_a? Hash
171
+
172
+ is_validating = options.delete( :validate )
173
+
174
+ # assume array of model objects
175
+ if args.last.is_a?( Array ) and args.last.first.is_a? ActiveRecord::Base
176
+ if args.length == 2
177
+ models = args.last
178
+ column_names = args.first
179
+ else
180
+ models = args.first
181
+ column_names = self.column_names.dup
182
+ end
183
+
184
+ array_of_attributes = models.map do |model|
185
+ # this next line breaks sqlite.so with a segmentation fault
186
+ # if model.new_record? || options[:on_duplicate_key_update]
187
+ column_names.map do |name|
188
+ model.send( "#{name}_before_type_cast" )
189
+ end
190
+ # end
191
+ end
192
+ # supports empty array
193
+ elsif args.last.is_a?( Array ) and args.last.empty?
194
+ return ActiveRecord::Import::Result.new([], 0) if args.last.empty?
195
+ # supports 2-element array and array
196
+ elsif args.size == 2 and args.first.is_a?( Array ) and args.last.is_a?( Array )
197
+ column_names, array_of_attributes = args
198
+ else
199
+ raise ArgumentError.new( "Invalid arguments!" )
200
+ end
201
+
202
+ # dup the passed in array so we don't modify it unintentionally
203
+ array_of_attributes = array_of_attributes.dup
204
+
205
+ # Force the primary key col into the insert if it's not
206
+ # on the list and we are using a sequence and stuff a nil
207
+ # value for it into each row so the sequencer will fire later
208
+ if !column_names.include?(primary_key) && sequence_name && connection.prefetch_primary_key?
209
+ column_names << primary_key
210
+ array_of_attributes.each { |a| a << nil }
211
+ end
212
+
213
+ # record timestamps unless disabled in ActiveRecord::Base
214
+ if record_timestamps && options.delete( :timestamps )
215
+ add_special_rails_stamps column_names, array_of_attributes, options
216
+ end
217
+
218
+ return_obj = if is_validating
219
+ import_with_validations( column_names, array_of_attributes, options )
220
+ else
221
+ num_inserts = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
222
+ ActiveRecord::Import::Result.new([], num_inserts)
223
+ end
224
+
225
+ if options[:synchronize]
226
+ sync_keys = options[:synchronize_keys] || [self.primary_key]
227
+ synchronize( options[:synchronize], sync_keys)
228
+ end
229
+
230
+ return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
231
+ return_obj
232
+ end
233
+
234
+ # TODO import_from_table needs to be implemented.
235
+ def import_from_table( options ) # :nodoc:
236
+ end
237
+
238
+ # Imports the passed in +column_names+ and +array_of_attributes+
239
+ # given the passed in +options+ Hash with validations. Returns an
240
+ # object with the methods +failed_instances+ and +num_inserts+.
241
+ # +failed_instances+ is an array of instances that failed validations.
242
+ # +num_inserts+ is the number of inserts it took to import the data. See
243
+ # ActiveRecord::Base.import for more information on
244
+ # +column_names+, +array_of_attributes+ and +options+.
245
+ def import_with_validations( column_names, array_of_attributes, options={} )
246
+ failed_instances = []
247
+
248
+ # create instances for each of our column/value sets
249
+ arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
250
+
251
+ # keep track of the instance and the position it is currently at. if this fails
252
+ # validation we'll use the index to remove it from the array_of_attributes
253
+ arr.each_with_index do |hsh,i|
254
+ instance = new do |model|
255
+ hsh.each_pair{ |k,v| model.send("#{k}=", v) }
256
+ end
257
+ if not instance.valid?
258
+ array_of_attributes[ i ] = nil
259
+ failed_instances << instance
260
+ end
261
+ end
262
+ array_of_attributes.compact!
263
+
264
+ num_inserts = if array_of_attributes.empty? || options[:all_or_none] && failed_instances.any?
265
+ 0
266
+ else
267
+ import_without_validations_or_callbacks( column_names, array_of_attributes, options )
268
+ end
269
+ ActiveRecord::Import::Result.new(failed_instances, num_inserts)
270
+ end
271
+
272
+ # Imports the passed in +column_names+ and +array_of_attributes+
273
+ # given the passed in +options+ Hash. This will return the number
274
+ # of insert operations it took to create these records without
275
+ # validations or callbacks. See ActiveRecord::Base.import for more
276
+ # information on +column_names+, +array_of_attributes_ and
277
+ # +options+.
278
+ def import_without_validations_or_callbacks( column_names, array_of_attributes, options={} )
279
+ columns = column_names.each_with_index.map do |name, i|
280
+ column = columns_hash[name.to_s]
281
+
282
+ raise ActiveRecord::Import::MissingColumnError.new(name.to_s, i) if column.nil?
283
+
284
+ column
285
+ end
286
+
287
+ columns_sql = "(#{column_names.map{|name| connection.quote_column_name(name) }.join(',')})"
288
+ insert_sql = "INSERT #{options[:ignore] ? 'IGNORE ':''}INTO #{quoted_table_name} #{columns_sql} VALUES "
289
+ values_sql = values_sql_for_columns_and_attributes(columns, array_of_attributes)
290
+ if not supports_import?
291
+ number_inserted = 0
292
+ values_sql.each do |values|
293
+ connection.execute(insert_sql + values)
294
+ number_inserted += 1
295
+ end
296
+ else
297
+ # generate the sql
298
+ post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
299
+
300
+ # perform the inserts
301
+ number_inserted = connection.insert_many( [ insert_sql, post_sql_statements ].flatten,
302
+ values_sql,
303
+ "#{self.class.name} Create Many Without Validations Or Callbacks" )
304
+ end
305
+ number_inserted
306
+ end
307
+
308
+ private
309
+
310
+ # Returns SQL the VALUES for an INSERT statement given the passed in +columns+
311
+ # and +array_of_attributes+.
312
+ def values_sql_for_columns_and_attributes(columns, array_of_attributes) # :nodoc:
313
+ array_of_attributes.map do |arr|
314
+ my_values = arr.each_with_index.map do |val,j|
315
+ column = columns[j]
316
+
317
+ if val.nil? && !sequence_name.blank? && column.name == primary_key
318
+ connection.next_value_for_sequence(sequence_name)
319
+ else
320
+ connection.quote(column.type_cast(val), column)
321
+ end
322
+ end
323
+ "(#{my_values.join(',')})"
324
+ end
325
+ end
326
+
327
+ def add_special_rails_stamps( column_names, array_of_attributes, options )
328
+ AREXT_RAILS_COLUMNS[:create].each_pair do |key, blk|
329
+ if self.column_names.include?(key)
330
+ value = blk.call
331
+ if index=column_names.index(key)
332
+ # replace every instance of the array of attributes with our value
333
+ array_of_attributes.each{ |arr| arr[index] = value }
334
+ else
335
+ column_names << key
336
+ array_of_attributes.each { |arr| arr << value }
337
+ end
338
+ end
339
+ end
340
+
341
+ AREXT_RAILS_COLUMNS[:update].each_pair do |key, blk|
342
+ if self.column_names.include?(key)
343
+ value = blk.call
344
+ if index=column_names.index(key)
345
+ # replace every instance of the array of attributes with our value
346
+ array_of_attributes.each{ |arr| arr[index] = value }
347
+ else
348
+ column_names << key
349
+ array_of_attributes.each { |arr| arr << value }
350
+ end
351
+
352
+ if supports_on_duplicate_key_update?
353
+ if options[:on_duplicate_key_update]
354
+ options[:on_duplicate_key_update] << key.to_sym if options[:on_duplicate_key_update].is_a?(Array)
355
+ options[:on_duplicate_key_update][key.to_sym] = key.to_sym if options[:on_duplicate_key_update].is_a?(Hash)
356
+ else
357
+ options[:on_duplicate_key_update] = [ key.to_sym ]
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end
363
+
364
+ # Returns an Array of Hashes for the passed in +column_names+ and +array_of_attributes+.
365
+ def validations_array_for_column_names_and_attributes( column_names, array_of_attributes ) # :nodoc:
366
+ array_of_attributes.map do |attributes|
367
+ Hash[attributes.each_with_index.map {|attr, c| [column_names[c], attr] }]
368
+ end
369
+ end
370
+
371
+ end
372
+ end
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,8 @@
1
+ warn <<-MSG
2
+ [DEPRECATION] loading activerecord-import via 'require "activerecord-import/<adapter-name>"'
3
+ is deprecated. Update to autorequire using 'require "activerecord-import"'. See
4
+ http://github.com/zdennis/activerecord-import/wiki/Requiring for more information
5
+ MSG
6
+
7
+ require File.expand_path(File.join(File.dirname(__FILE__), "/../activerecord-import"))
8
+
@@ -0,0 +1,55 @@
1
+ module ActiveRecord # :nodoc:
2
+ class Base # :nodoc:
3
+
4
+ # Synchronizes the passed in ActiveRecord instances with data
5
+ # from the database. This is like calling reload on an individual
6
+ # ActiveRecord instance but it is intended for use on multiple instances.
7
+ #
8
+ # This uses one query for all instance updates and then updates existing
9
+ # instances rather sending one query for each instance
10
+ #
11
+ # == Examples
12
+ # # Synchronizing existing models by matching on the primary key field
13
+ # posts = Post.find_by_author("Zach")
14
+ # <.. out of system changes occur to change author name from Zach to Zachary..>
15
+ # Post.synchronize posts
16
+ # posts.first.author # => "Zachary" instead of Zach
17
+ #
18
+ # # Synchronizing using custom key fields
19
+ # posts = Post.find_by_author("Zach")
20
+ # <.. out of system changes occur to change the address of author 'Zach' to 1245 Foo Ln ..>
21
+ # Post.synchronize posts, [:name] # queries on the :name column and not the :id column
22
+ # posts.first.address # => "1245 Foo Ln" instead of whatever it was
23
+ #
24
+ def self.synchronize(instances, keys=[self.primary_key])
25
+ return if instances.empty?
26
+
27
+ conditions = {}
28
+ order = ""
29
+
30
+ key_values = keys.map { |key| instances.map(&"#{key}".to_sym) }
31
+ keys.zip(key_values).each { |key, values| conditions[key] = values }
32
+ order = keys.map{ |key| "#{key} ASC" }.join(",")
33
+
34
+ klass = instances.first.class
35
+
36
+ fresh_instances = klass.find( :all, :conditions=>conditions, :order=>order )
37
+ instances.each do |instance|
38
+ matched_instance = fresh_instances.detect do |fresh_instance|
39
+ keys.all?{ |key| fresh_instance.send(key) == instance.send(key) }
40
+ end
41
+
42
+ if matched_instance
43
+ instance.clear_aggregation_cache
44
+ instance.clear_association_cache
45
+ instance.instance_variable_set '@attributes', matched_instance.attributes
46
+ end
47
+ end
48
+ end
49
+
50
+ # See ActiveRecord::ConnectionAdapters::AbstractAdapter.synchronize
51
+ def synchronize(instances, key=[ActiveRecord::Base.primary_key])
52
+ self.class.synchronize(instances, key)
53
+ end
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-import-in4systems
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.10
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Zach Dennis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activerecord-sqlanywhere-adapter-in4systems
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activerecord-sqlanywhere-jdbc-in4systems-adapter
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: jeweler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: 1.4.0
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 1.4.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: activerecord
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '3.0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '3.0'
110
+ description: Extraction of the ActiveRecord::Base#import functionality from ar-extensions
111
+ for Rails 3 and beyond
112
+ email: zach.dennis@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files:
116
+ - README.markdown
117
+ files:
118
+ - README.markdown
119
+ - Rakefile
120
+ - VERSION
121
+ - lib/activerecord-import.rb
122
+ - lib/activerecord-import/active_record/adapters/abstract_adapter.rb
123
+ - lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb
124
+ - lib/activerecord-import/active_record/adapters/mysql2_adapter.rb
125
+ - lib/activerecord-import/active_record/adapters/mysql_adapter.rb
126
+ - lib/activerecord-import/active_record/adapters/postgresql_adapter.rb
127
+ - lib/activerecord-import/active_record/adapters/sqlanywhere_adapter.rb
128
+ - lib/activerecord-import/active_record/adapters/sqlanywhere_jdbc_in4systems_adapter.rb
129
+ - lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb
130
+ - lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb
131
+ - lib/activerecord-import/adapters/abstract_adapter.rb
132
+ - lib/activerecord-import/adapters/mysql_adapter.rb
133
+ - lib/activerecord-import/adapters/postgresql_adapter.rb
134
+ - lib/activerecord-import/adapters/sqlanywhere_adapter.rb
135
+ - lib/activerecord-import/adapters/sqlanywhere_jdbc_in4systems_adapter.rb
136
+ - lib/activerecord-import/adapters/sqlite3_adapter.rb
137
+ - lib/activerecord-import/base.rb
138
+ - lib/activerecord-import/import.rb
139
+ - lib/activerecord-import/mysql.rb
140
+ - lib/activerecord-import/mysql2.rb
141
+ - lib/activerecord-import/postgresql.rb
142
+ - lib/activerecord-import/sqlanywhere.rb
143
+ - lib/activerecord-import/sqlanywhere_jdbc_in4systems.rb
144
+ - lib/activerecord-import/sqlite3.rb
145
+ - lib/activerecord-import/synchronize.rb
146
+ homepage: http://github.com/zdennis/activerecord-import
147
+ licenses: []
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ! '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 1.8.24
167
+ signing_key:
168
+ specification_version: 3
169
+ summary: Bulk-loading extension for ActiveRecord
170
+ test_files: []