activerecord-import-in4systems 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
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: []