activerecord-import 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +31 -0
- data/Brewfile +3 -0
- data/Gemfile +39 -0
- data/Rakefile +1 -24
- data/activerecord-import.gemspec +22 -0
- data/benchmarks/README +32 -0
- data/benchmarks/benchmark.rb +64 -0
- data/benchmarks/boot.rb +18 -0
- data/benchmarks/lib/base.rb +137 -0
- data/benchmarks/lib/cli_parser.rb +103 -0
- data/benchmarks/lib/float.rb +15 -0
- data/benchmarks/lib/mysql_benchmark.rb +22 -0
- data/benchmarks/lib/output_to_csv.rb +18 -0
- data/benchmarks/lib/output_to_html.rb +69 -0
- data/benchmarks/models/test_innodb.rb +3 -0
- data/benchmarks/models/test_memory.rb +3 -0
- data/benchmarks/models/test_myisam.rb +3 -0
- data/benchmarks/schema/mysql_schema.rb +16 -0
- data/gemfiles/3.1.gemfile +4 -0
- data/gemfiles/3.2.gemfile +4 -0
- data/gemfiles/4.0.gemfile +4 -0
- data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +0 -1
- data/lib/activerecord-import/active_record/adapters/em_mysql2_adapter.rb +8 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +5 -58
- data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +5 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +50 -3
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +28 -3
- data/lib/activerecord-import/base.rb +3 -2
- data/lib/activerecord-import/em_mysql2.rb +7 -0
- data/lib/activerecord-import/import.rb +29 -29
- data/lib/activerecord-import/synchronize.rb +11 -10
- data/lib/activerecord-import/value_sets_parser.rb +54 -0
- data/lib/activerecord-import/version.rb +5 -0
- data/test/adapters/em_mysql2.rb +1 -0
- data/test/adapters/jdbcmysql.rb +1 -0
- data/test/adapters/mysql.rb +1 -0
- data/test/adapters/mysql2.rb +1 -0
- data/test/adapters/mysql2spatial.rb +1 -0
- data/test/adapters/mysqlspatial.rb +1 -0
- data/test/adapters/postgis.rb +1 -0
- data/test/adapters/postgresql.rb +1 -0
- data/test/adapters/seamless_database_pool.rb +1 -0
- data/test/adapters/spatialite.rb +1 -0
- data/test/adapters/sqlite3.rb +1 -0
- data/test/database.yml.sample +57 -0
- data/test/em_mysql2/import_test.rb +6 -0
- data/test/import_test.rb +350 -0
- data/test/jdbcmysql/import_test.rb +6 -0
- data/test/models/book.rb +3 -0
- data/test/models/group.rb +3 -0
- data/test/models/topic.rb +7 -0
- data/test/models/widget.rb +7 -0
- data/test/mysql/import_test.rb +6 -0
- data/test/mysql2/import_test.rb +6 -0
- data/test/mysqlspatial/import_test.rb +6 -0
- data/test/mysqlspatial2/import_test.rb +6 -0
- data/test/postgis/import_test.rb +4 -0
- data/test/postgresql/import_test.rb +4 -0
- data/test/schema/generic_schema.rb +104 -0
- data/test/schema/mysql_schema.rb +17 -0
- data/test/schema/version.rb +10 -0
- data/test/sqlite3/import_test.rb +52 -0
- data/test/support/active_support/test_case_extensions.rb +67 -0
- data/test/support/factories.rb +19 -0
- data/test/support/generate.rb +29 -0
- data/test/support/mysql/assertions.rb +55 -0
- data/test/support/mysql/import_examples.rb +147 -0
- data/test/support/postgresql/import_examples.rb +21 -0
- data/test/synchronize_test.rb +22 -0
- data/test/test_helper.rb +48 -0
- data/test/value_sets_bytes_parser_test.rb +96 -0
- data/test/value_sets_records_parser_test.rb +32 -0
- metadata +120 -58
- data/VERSION +0 -1
@@ -0,0 +1,15 @@
|
|
1
|
+
# Taken from http://www.programmingishard.com/posts/show/128
|
2
|
+
# Posted by rbates
|
3
|
+
class Float
|
4
|
+
def round_to(x)
|
5
|
+
(self * 10**x).round.to_f / 10**x
|
6
|
+
end
|
7
|
+
|
8
|
+
def ceil_to(x)
|
9
|
+
(self * 10**x).ceil.to_f / 10**x
|
10
|
+
end
|
11
|
+
|
12
|
+
def floor_to(x)
|
13
|
+
(self * 10**x).floor.to_f / 10**x
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class MysqlBenchmark < BenchmarkBase
|
2
|
+
|
3
|
+
def benchmark_all( array_of_cols_and_vals )
|
4
|
+
methods = self.methods.find_all { |m| m =~ /benchmark_/ }
|
5
|
+
methods.delete_if{ |m| m =~ /benchmark_(all|model)/ }
|
6
|
+
methods.each { |method| self.send( method, array_of_cols_and_vals ) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def benchmark_myisam( array_of_cols_and_vals )
|
10
|
+
bm_model( TestMyISAM, array_of_cols_and_vals )
|
11
|
+
end
|
12
|
+
|
13
|
+
def benchmark_innodb( array_of_cols_and_vals )
|
14
|
+
bm_model( TestInnoDb, array_of_cols_and_vals )
|
15
|
+
end
|
16
|
+
|
17
|
+
def benchmark_memory( array_of_cols_and_vals )
|
18
|
+
bm_model( TestMemory, array_of_cols_and_vals )
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'fastercsv'
|
2
|
+
|
3
|
+
module OutputToCSV
|
4
|
+
def self.output_results( filename, results )
|
5
|
+
FasterCSV.open( filename, 'w' ) do |csv|
|
6
|
+
# Iterate over each result set, which contains many results
|
7
|
+
results.each do |result_set|
|
8
|
+
columns, times = [], []
|
9
|
+
result_set.each do |result|
|
10
|
+
columns << result.description
|
11
|
+
times << result.tms.real
|
12
|
+
end
|
13
|
+
csv << columns
|
14
|
+
csv << times
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module OutputToHTML
|
4
|
+
|
5
|
+
TEMPLATE_HEADER =<<"EOT"
|
6
|
+
<div>
|
7
|
+
All times are rounded to the nearest thousandth for display purposes. Speedups next to each time are computed
|
8
|
+
before any rounding occurs. Also, all speedup calculations are computed by comparing a given time against
|
9
|
+
the very first column (which is always the default ActiveRecord::Base.create method.
|
10
|
+
</div>
|
11
|
+
EOT
|
12
|
+
|
13
|
+
TEMPLATE =<<"EOT"
|
14
|
+
<style>
|
15
|
+
td#benchmarkTitle {
|
16
|
+
border: 1px solid black;
|
17
|
+
padding: 2px;
|
18
|
+
font-size: 0.8em;
|
19
|
+
background-color: black;
|
20
|
+
color: white;
|
21
|
+
}
|
22
|
+
td#benchmarkCell {
|
23
|
+
border: 1px solid black;
|
24
|
+
padding: 2px;
|
25
|
+
font-size: 0.8em;
|
26
|
+
}
|
27
|
+
</style>
|
28
|
+
<table>
|
29
|
+
<tr>
|
30
|
+
<% columns.each do |col| %>
|
31
|
+
<td id="benchmarkTitle"><%= col %></td>
|
32
|
+
<% end %>
|
33
|
+
</tr>
|
34
|
+
<tr>
|
35
|
+
<% times.each do |time| %>
|
36
|
+
<td id="benchmarkCell"><%= time %></td>
|
37
|
+
<% end %>
|
38
|
+
</tr>
|
39
|
+
<tr><td> </td></tr>
|
40
|
+
</table>
|
41
|
+
EOT
|
42
|
+
|
43
|
+
def self.output_results( filename, results )
|
44
|
+
html = ''
|
45
|
+
results.each do |result_set|
|
46
|
+
columns, times = [], []
|
47
|
+
result_set.each do |result|
|
48
|
+
columns << result.description
|
49
|
+
if result.failed
|
50
|
+
times << "failed"
|
51
|
+
else
|
52
|
+
time = result.tms.real.round_to( 3 )
|
53
|
+
speedup = ( result_set.first.tms.real / result.tms.real ).round
|
54
|
+
|
55
|
+
if result == result_set.first
|
56
|
+
times << "#{time}"
|
57
|
+
else
|
58
|
+
times << "#{time} (#{speedup}x speedup)"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
template = ERB.new( TEMPLATE, 0, "%<>")
|
64
|
+
html << template.result( binding )
|
65
|
+
end
|
66
|
+
|
67
|
+
File.open( filename, 'w' ){ |file| file.write( TEMPLATE_HEADER + html ) }
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table :test_myisam, :options=>'ENGINE=MyISAM', :force=>true do |t|
|
3
|
+
t.column :my_name, :string, :null=>false
|
4
|
+
t.column :description, :string
|
5
|
+
end
|
6
|
+
|
7
|
+
create_table :test_innodb, :options=>'ENGINE=InnoDb', :force=>true do |t|
|
8
|
+
t.column :my_name, :string, :null=>false
|
9
|
+
t.column :description, :string
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table :test_memory, :options=>'ENGINE=Memory', :force=>true do |t|
|
13
|
+
t.column :my_name, :string, :null=>false
|
14
|
+
t.column :description, :string
|
15
|
+
end
|
16
|
+
end
|
@@ -3,7 +3,6 @@ require "activerecord-import/adapters/abstract_adapter"
|
|
3
3
|
module ActiveRecord # :nodoc:
|
4
4
|
module ConnectionAdapters # :nodoc:
|
5
5
|
class AbstractAdapter # :nodoc:
|
6
|
-
extend ActiveRecord::Import::AbstractAdapter::ClassMethods
|
7
6
|
include ActiveRecord::Import::AbstractAdapter::InstanceMethods
|
8
7
|
end
|
9
8
|
end
|
@@ -1,73 +1,20 @@
|
|
1
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.bytesize + 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
2
|
module InstanceMethods
|
29
3
|
def next_value_for_sequence(sequence_name)
|
30
4
|
%{#{sequence_name}.nextval}
|
31
5
|
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.
|
6
|
+
|
35
7
|
def insert_many( sql, values, *args ) # :nodoc:
|
36
|
-
|
37
|
-
|
38
|
-
|
8
|
+
number_of_inserts = 1
|
9
|
+
|
39
10
|
base_sql,post_sql = if sql.is_a?( String )
|
40
11
|
[ sql, '' ]
|
41
12
|
elsif sql.is_a?( Array )
|
42
13
|
[ sql.shift, sql.join( ' ' ) ]
|
43
14
|
end
|
44
|
-
|
45
|
-
sql_size = QUERY_OVERHEAD + base_sql.size + post_sql.size
|
46
15
|
|
47
|
-
|
48
|
-
|
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
|
16
|
+
sql2insert = base_sql + values.join( ',' ) + post_sql
|
17
|
+
insert( sql2insert, *args )
|
71
18
|
|
72
19
|
number_of_inserts
|
73
20
|
end
|
@@ -1,7 +1,54 @@
|
|
1
1
|
module ActiveRecord::Import::MysqlAdapter
|
2
|
-
include ActiveRecord::Import::ImportSupport
|
2
|
+
include ActiveRecord::Import::ImportSupport
|
3
3
|
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
4
4
|
|
5
|
+
NO_MAX_PACKET = 0
|
6
|
+
QUERY_OVERHEAD = 8 #This was shown to be true for MySQL, but it's not clear where the overhead is from.
|
7
|
+
|
8
|
+
# +sql+ can be a single string or an array. If it is an array all
|
9
|
+
# elements that are in position >= 1 will be appended to the final SQL.
|
10
|
+
def insert_many( sql, values, *args ) # :nodoc:
|
11
|
+
# the number of inserts default
|
12
|
+
number_of_inserts = 0
|
13
|
+
|
14
|
+
base_sql,post_sql = if sql.is_a?( String )
|
15
|
+
[ sql, '' ]
|
16
|
+
elsif sql.is_a?( Array )
|
17
|
+
[ sql.shift, sql.join( ' ' ) ]
|
18
|
+
end
|
19
|
+
|
20
|
+
sql_size = QUERY_OVERHEAD + base_sql.size + post_sql.size
|
21
|
+
|
22
|
+
# the number of bytes the requested insert statement values will take up
|
23
|
+
values_in_bytes = values.sum {|value| value.bytesize }
|
24
|
+
|
25
|
+
# the number of bytes (commas) it will take to comma separate our values
|
26
|
+
comma_separated_bytes = values.size-1
|
27
|
+
|
28
|
+
# the total number of bytes required if this statement is one statement
|
29
|
+
total_bytes = sql_size + values_in_bytes + comma_separated_bytes
|
30
|
+
|
31
|
+
max = max_allowed_packet
|
32
|
+
|
33
|
+
# if we can insert it all as one statement
|
34
|
+
if NO_MAX_PACKET == max or total_bytes < max
|
35
|
+
number_of_inserts += 1
|
36
|
+
sql2insert = base_sql + values.join( ',' ) + post_sql
|
37
|
+
insert( sql2insert, *args )
|
38
|
+
else
|
39
|
+
value_sets = ::ActiveRecord::Import::ValueSetsBytesParser.parse(values,
|
40
|
+
:reserved_bytes => sql_size,
|
41
|
+
:max_bytes => max)
|
42
|
+
value_sets.each do |values|
|
43
|
+
number_of_inserts += 1
|
44
|
+
sql2insert = base_sql + values.join( ',' ) + post_sql
|
45
|
+
insert( sql2insert, *args )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
number_of_inserts
|
50
|
+
end
|
51
|
+
|
5
52
|
# Returns the maximum number of bytes that the server will allow
|
6
53
|
# in a single packet
|
7
54
|
def max_allowed_packet # :nodoc:
|
@@ -12,9 +59,9 @@ module ActiveRecord::Import::MysqlAdapter
|
|
12
59
|
val.to_i
|
13
60
|
end
|
14
61
|
end
|
15
|
-
|
62
|
+
|
16
63
|
# Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
|
17
|
-
# in +args+.
|
64
|
+
# in +args+.
|
18
65
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
19
66
|
sql = ' ON DUPLICATE KEY UPDATE '
|
20
67
|
arg = args.first
|
@@ -1,18 +1,43 @@
|
|
1
|
+
require 'debugger'
|
1
2
|
module ActiveRecord::Import::SQLite3Adapter
|
2
3
|
include ActiveRecord::Import::ImportSupport
|
3
4
|
|
5
|
+
MIN_VERSION_FOR_IMPORT = "3.7.11"
|
6
|
+
SQLITE_LIMIT_COMPOUND_SELECT = 500
|
7
|
+
|
4
8
|
# Override our conformance to ActiveRecord::Import::ImportSupport interface
|
5
9
|
# to ensure that we only support import in supported version of SQLite.
|
6
|
-
# Which INSERT statements with multiple value sets was introduced in 3.
|
10
|
+
# Which INSERT statements with multiple value sets was introduced in 3.7.11.
|
7
11
|
def supports_import?(current_version=self.sqlite_version)
|
8
|
-
|
9
|
-
if current_version >= minimum_supported_version
|
12
|
+
if current_version >= MIN_VERSION_FOR_IMPORT
|
10
13
|
true
|
11
14
|
else
|
12
15
|
false
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
19
|
+
# +sql+ can be a single string or an array. If it is an array all
|
20
|
+
# elements that are in position >= 1 will be appended to the final SQL.
|
21
|
+
def insert_many(sql, values, *args) # :nodoc:
|
22
|
+
number_of_inserts = 0
|
23
|
+
base_sql,post_sql = if sql.is_a?( String )
|
24
|
+
[ sql, '' ]
|
25
|
+
elsif sql.is_a?( Array )
|
26
|
+
[ sql.shift, sql.join( ' ' ) ]
|
27
|
+
end
|
28
|
+
|
29
|
+
value_sets = ::ActiveRecord::Import::ValueSetsRecordsParser.parse(values,
|
30
|
+
:max_records => SQLITE_LIMIT_COMPOUND_SELECT)
|
31
|
+
|
32
|
+
value_sets.each do |values|
|
33
|
+
number_of_inserts += 1
|
34
|
+
sql2insert = base_sql + values.join( ',' ) + post_sql
|
35
|
+
insert( sql2insert, *args )
|
36
|
+
end
|
37
|
+
|
38
|
+
number_of_inserts
|
39
|
+
end
|
40
|
+
|
16
41
|
def next_value_for_sequence(sequence_name)
|
17
42
|
%{nextval('#{sequence_name}')}
|
18
43
|
end
|
@@ -14,7 +14,7 @@ module ActiveRecord::Import
|
|
14
14
|
else adapter
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
# Loads the import functionality for a specific database adapter
|
19
19
|
def self.require_adapter(adapter)
|
20
20
|
require File.join(AdapterPath,"/abstract_adapter")
|
@@ -31,4 +31,5 @@ end
|
|
31
31
|
this_dir = Pathname.new File.dirname(__FILE__)
|
32
32
|
require this_dir.join("import").to_s
|
33
33
|
require this_dir.join("active_record/adapters/abstract_adapter").to_s
|
34
|
-
require this_dir.join("synchronize").to_s
|
34
|
+
require this_dir.join("synchronize").to_s
|
35
|
+
require this_dir.join("value_sets_parser").to_s
|
@@ -0,0 +1,7 @@
|
|
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"))
|
@@ -11,7 +11,7 @@ module ActiveRecord::Import #:nodoc:
|
|
11
11
|
true
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
module OnDuplicateKeyUpdateSupport #:nodoc:
|
16
16
|
def supports_on_duplicate_key_update? #:nodoc:
|
17
17
|
true
|
@@ -32,7 +32,7 @@ class ActiveRecord::Base
|
|
32
32
|
tproc = lambda do
|
33
33
|
ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
AREXT_RAILS_COLUMNS = {
|
37
37
|
:create => { "created_on" => tproc ,
|
38
38
|
"created_at" => tproc },
|
@@ -40,7 +40,7 @@ class ActiveRecord::Base
|
|
40
40
|
"updated_at" => tproc }
|
41
41
|
}
|
42
42
|
AREXT_RAILS_COLUMN_NAMES = AREXT_RAILS_COLUMNS[:create].keys + AREXT_RAILS_COLUMNS[:update].keys
|
43
|
-
|
43
|
+
|
44
44
|
# Returns true if the current database connection adapter
|
45
45
|
# supports import functionality, otherwise returns false.
|
46
46
|
def supports_import?(*args)
|
@@ -57,14 +57,14 @@ class ActiveRecord::Base
|
|
57
57
|
rescue NoMethodError
|
58
58
|
false
|
59
59
|
end
|
60
|
-
|
61
|
-
# Imports a collection of values to the database.
|
60
|
+
|
61
|
+
# Imports a collection of values to the database.
|
62
62
|
#
|
63
63
|
# This is more efficient than using ActiveRecord::Base#create or
|
64
64
|
# ActiveRecord::Base#save multiple times. This method works well if
|
65
65
|
# you want to create more than one record at a time and do not care
|
66
66
|
# about having ActiveRecord objects returned for each record
|
67
|
-
# inserted.
|
67
|
+
# inserted.
|
68
68
|
#
|
69
69
|
# This can be used with or without validations. It does not utilize
|
70
70
|
# the ActiveRecord::Callbacks during creation/modification while
|
@@ -74,9 +74,9 @@ class ActiveRecord::Base
|
|
74
74
|
# Model.import array_of_models
|
75
75
|
# Model.import column_names, array_of_values
|
76
76
|
# Model.import column_names, array_of_values, options
|
77
|
-
#
|
77
|
+
#
|
78
78
|
# ==== Model.import array_of_models
|
79
|
-
#
|
79
|
+
#
|
80
80
|
# With this form you can call _import_ passing in an array of model
|
81
81
|
# objects that you want updated.
|
82
82
|
#
|
@@ -108,9 +108,9 @@ class ActiveRecord::Base
|
|
108
108
|
# * +timestamps+ - true|false, tells import to not add timestamps \
|
109
109
|
# (if false) even if record timestamps is disabled in ActiveRecord::Base
|
110
110
|
#
|
111
|
-
# == Examples
|
111
|
+
# == Examples
|
112
112
|
# class BlogPost < ActiveRecord::Base ; end
|
113
|
-
#
|
113
|
+
#
|
114
114
|
# # Example using array of model objects
|
115
115
|
# posts = [ BlogPost.new :author_name=>'Zach Dennis', :title=>'AREXT',
|
116
116
|
# BlogPost.new :author_name=>'Zach Dennis', :title=>'AREXT2',
|
@@ -128,7 +128,7 @@ class ActiveRecord::Base
|
|
128
128
|
# BlogPost.import( columns, values, :validate => false )
|
129
129
|
#
|
130
130
|
# # Example synchronizing existing instances in memory
|
131
|
-
# post = BlogPost.
|
131
|
+
# post = BlogPost.where(author_name: 'zdennis').first
|
132
132
|
# puts post.author_name # => 'zdennis'
|
133
133
|
# columns = [ :author_name, :title ]
|
134
134
|
# values = [ [ 'yoda', 'test post' ] ]
|
@@ -143,7 +143,7 @@ class ActiveRecord::Base
|
|
143
143
|
# == On Duplicate Key Update (MySQL only)
|
144
144
|
#
|
145
145
|
# The :on_duplicate_key_update option can be either an Array or a Hash.
|
146
|
-
#
|
146
|
+
#
|
147
147
|
# ==== Using an Array
|
148
148
|
#
|
149
149
|
# The :on_duplicate_key_update option can be an array of column
|
@@ -158,9 +158,9 @@ class ActiveRecord::Base
|
|
158
158
|
# to model attribute name mappings. This gives you finer grained
|
159
159
|
# control over what fields are updated with what attributes on your
|
160
160
|
# model. Below is an example:
|
161
|
-
#
|
162
|
-
# BlogPost.import columns, attributes, :on_duplicate_key_update=>{ :title => :title }
|
163
|
-
#
|
161
|
+
#
|
162
|
+
# BlogPost.import columns, attributes, :on_duplicate_key_update=>{ :title => :title }
|
163
|
+
#
|
164
164
|
# = Returns
|
165
165
|
# This returns an object which responds to +failed_instances+ and +num_inserts+.
|
166
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.
|
@@ -180,7 +180,7 @@ class ActiveRecord::Base
|
|
180
180
|
models = args.first
|
181
181
|
column_names = self.column_names.dup
|
182
182
|
end
|
183
|
-
|
183
|
+
|
184
184
|
array_of_attributes = models.map do |model|
|
185
185
|
# this next line breaks sqlite.so with a segmentation fault
|
186
186
|
# if model.new_record? || options[:on_duplicate_key_update]
|
@@ -230,23 +230,23 @@ class ActiveRecord::Base
|
|
230
230
|
return_obj.num_inserts = 0 if return_obj.num_inserts.nil?
|
231
231
|
return_obj
|
232
232
|
end
|
233
|
-
|
234
|
-
# TODO import_from_table needs to be implemented.
|
233
|
+
|
234
|
+
# TODO import_from_table needs to be implemented.
|
235
235
|
def import_from_table( options ) # :nodoc:
|
236
236
|
end
|
237
|
-
|
237
|
+
|
238
238
|
# Imports the passed in +column_names+ and +array_of_attributes+
|
239
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.
|
240
|
+
# object with the methods +failed_instances+ and +num_inserts+.
|
241
|
+
# +failed_instances+ is an array of instances that failed validations.
|
242
242
|
# +num_inserts+ is the number of inserts it took to import the data. See
|
243
243
|
# ActiveRecord::Base.import for more information on
|
244
244
|
# +column_names+, +array_of_attributes+ and +options+.
|
245
245
|
def import_with_validations( column_names, array_of_attributes, options={} )
|
246
246
|
failed_instances = []
|
247
|
-
|
247
|
+
|
248
248
|
# create instances for each of our column/value sets
|
249
|
-
arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
|
249
|
+
arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
|
250
250
|
|
251
251
|
# keep track of the instance and the position it is currently at. if this fails
|
252
252
|
# validation we'll use the index to remove it from the array_of_attributes
|
@@ -257,7 +257,7 @@ class ActiveRecord::Base
|
|
257
257
|
if not instance.valid?
|
258
258
|
array_of_attributes[ i ] = nil
|
259
259
|
failed_instances << instance
|
260
|
-
end
|
260
|
+
end
|
261
261
|
end
|
262
262
|
array_of_attributes.compact!
|
263
263
|
|
@@ -268,7 +268,7 @@ class ActiveRecord::Base
|
|
268
268
|
end
|
269
269
|
ActiveRecord::Import::Result.new(failed_instances, num_inserts)
|
270
270
|
end
|
271
|
-
|
271
|
+
|
272
272
|
# Imports the passed in +column_names+ and +array_of_attributes+
|
273
273
|
# given the passed in +options+ Hash. This will return the number
|
274
274
|
# of insert operations it took to create these records without
|
@@ -309,7 +309,7 @@ class ActiveRecord::Base
|
|
309
309
|
post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
|
310
310
|
|
311
311
|
# perform the inserts
|
312
|
-
number_inserted = connection.insert_many( [ insert_sql, post_sql_statements ].flatten,
|
312
|
+
number_inserted = connection.insert_many( [ insert_sql, post_sql_statements ].flatten,
|
313
313
|
values_sql,
|
314
314
|
"#{self.class.name} Create Many Without Validations Or Callbacks" )
|
315
315
|
end
|
@@ -367,7 +367,7 @@ class ActiveRecord::Base
|
|
367
367
|
column_names << key
|
368
368
|
array_of_attributes.each { |arr| arr << value }
|
369
369
|
end
|
370
|
-
|
370
|
+
|
371
371
|
if supports_on_duplicate_key_update?
|
372
372
|
if options[:on_duplicate_key_update]
|
373
373
|
options[:on_duplicate_key_update] << key.to_sym if options[:on_duplicate_key_update].is_a?(Array)
|
@@ -379,13 +379,13 @@ class ActiveRecord::Base
|
|
379
379
|
end
|
380
380
|
end
|
381
381
|
end
|
382
|
-
|
382
|
+
|
383
383
|
# Returns an Array of Hashes for the passed in +column_names+ and +array_of_attributes+.
|
384
384
|
def validations_array_for_column_names_and_attributes( column_names, array_of_attributes ) # :nodoc:
|
385
385
|
array_of_attributes.map do |attributes|
|
386
386
|
Hash[attributes.each_with_index.map {|attr, c| [column_names[c], attr] }]
|
387
387
|
end
|
388
388
|
end
|
389
|
-
|
389
|
+
|
390
390
|
end
|
391
391
|
end
|