Compare commits

...

34 Commits

Author SHA1 Message Date
Denis Knauf 038a29682e import_many, import_*: returns last_inserted_id 2013-01-03 17:07:10 +01:00
Zach Dennis 1c675d7c31 Merge branch 'michael-groble_master' 2012-12-14 10:29:46 -05:00
Zach Dennis 6697e2b4db Ensuring that the geo spatial adapters are successfully wired up and working with base import functionality.
[#47]
2012-12-14 10:29:04 -05:00
michael groble eec20355e5 support rgeo spatial database adapter names 2012-12-14 10:08:46 -05:00
Zach Dennis 5f0e220038 Merge branch 'dougo_clear-new-record-on-synchronize' 2012-12-14 10:03:51 -05:00
Doug Orleans d0c1055b65 Fix example in doc comment. 2012-12-14 09:59:59 -05:00
Doug Orleans 9fd19a36bd Also clear the destroyed flag after synchronizing. 2012-12-14 09:59:58 -05:00
Doug Orleans 413e35fedd Add failing test for destroyed records.
Also, generalize the previous test to use persisted? rather than !new_record?.
2012-12-14 09:59:58 -05:00
Doug Orleans 31ff20a332 Fix failing test - clear @new_record on synchronize. 2012-12-14 09:59:58 -05:00
Doug Orleans 58cac8bfb0 Test that no records are new after synchronize, not all.
Also fix the spelling of :synchronize_keys.
2012-12-14 09:59:58 -05:00
Zach Dennis 082a95df0e Merge branch 'sishen_master' 2012-12-14 09:51:29 -05:00
Dingding Ye 1ae5122642 Performance improvements.
* Serialize the column using specified coder before inserting
* Cache the ActiveRecord connection once per call to generate values SQL
* Call sequence_name last since it's more expensive
2012-12-14 09:50:36 -05:00
Zach Dennis ec2059e802 assert_include => assert_includes 2012-12-14 09:49:54 -05:00
Zach Dennis a4f17e6d45 Merge remote-tracking branch 'chewi/scope-awareness' 2012-12-14 09:40:03 -05:00
Zach Dennis 394e0b51c3 Version bump to 0.2.11 2012-10-05 13:10:38 -04:00
Zach Dennis ee5604d4d5 Merge pull request #67 from mildmojo/fix_sqlite3_adapter_spelling
Fix case-sensitive SQLite3 adapter name typos.
2012-10-01 06:44:46 -07:00
mildmojo 43bd628e05 Fixes case-sensitive SQLite3 adapter name typos.
The SQLite3 adapter name was referenced as
`ActiveRecord::ConnectionAdapters::Sqlite3Adapter`, but the
ActiveRecord class is spelled `SQLite3Adapter` (upper-case "SQL"). As
a result, `active_record/adapters/sqlite3_adapter.rb` wasn't actually
including the methods from `adapters/sqlite3_adapter.rb` into the AR
adapter class.

This adds a test for proper inheritance and fixes the spelling of
`SQLite3Adapter`, unifying all references to the ActiveRecord version.
2012-10-01 03:08:07 -04:00
James Le Cuirot 22473145d1 Add scope-awareness feature 2012-09-20 15:15:21 +01:00
Zach Dennis c08d31b528 Merge pull request #65 from jabley/missing-column
Provide more detailed error message
2012-09-14 08:00:18 -07:00
James Abley b03af0cd3e Provide more detailed error message
If we don't have a mapping for a column name, provide the column name
as well as the index in the array.
2012-09-14 14:01:00 +01:00
Zach Dennis 6182b944ef Merge pull request #56 from grzuy/support_all_or_none_records
Adds support for :all_or_none option on #import
2012-08-29 20:48:18 -07:00
Zach Dennis 0b03543612 Merge pull request #58 from alexagranov/e6cafdb23a64e68a246a838565afaecf6faf952a
The jdbcmysql_adapter was left out of the InstanceMethods refactoring.
2012-08-29 20:47:07 -07:00
Zach Dennis b4bc1466f1 Adding gemspec. To re-generate use: rake gemspec:generate 2012-08-29 23:45:16 -04:00
Zach Dennis 75a63d77d1 Merge pull request #62 from spectator/seamless_database_pool
Seamless database pool adapter
2012-08-29 20:41:52 -07:00
Yury Velikanau 190fcd32ff Remove remain of ruby-debug-19 from Gemfile.lock 2012-08-22 19:51:47 -07:00
Yury Velikanau 1d9b30cf83 Put together tests for seamless_database_pool adapter 2012-08-22 19:50:37 -07:00
Yury Velikanau 23a07371dd Add seamless_database_pool gem 2012-08-22 19:49:48 -07:00
Yury Velikanau 151c2f271f Gemfile.lock changes for debugger gem 2012-08-22 19:49:27 -07:00
Yury Velikanau 7628e0a9d8 debugger gem supports ruby 1.9.3 just fine 2012-08-22 18:57:41 -07:00
Yury Velikanau de048bb61e Remove whitespace. 2012-08-22 18:57:15 -07:00
Evan Petrie 9ee02d87ff fix file name 2012-08-22 18:44:48 -07:00
Evan Petrie cc0185abb3 make work with seamless database pool 2012-08-22 18:44:37 -07:00
Gonzalo Rodriguez eac7d5f949 Adds support for :all_or_none option on #import 2012-05-19 17:40:04 -03:00
Alex Agranov e6cafdb23a somehow the jdbcmysql_adapter was left out in the refactoring that removed usage of the InstanceMethods module 2012-02-28 19:31:55 -05:00
30 changed files with 314 additions and 76 deletions

View File

@ -14,6 +14,7 @@ group :test do
gem "mysql2", "~> 0.3.0"
gem "pg", "~> 0.9"
gem "sqlite3-ruby", "~> 1.3.1"
gem "seamless_database_pool", "~> 1.0.11"
end
platforms :jruby do
@ -24,7 +25,7 @@ group :test do
# Support libs
gem "factory_girl", "~> 1.3.3"
gem "delorean", "~> 0.2.0"
# Debugging
platforms :mri_18 do
gem "ruby-debug", "= 0.10.4"
@ -36,7 +37,6 @@ group :test do
end
platforms :mri_19 do
# TODO: Remove the conditional when ruby-debug19 supports Ruby >= 1.9.3
gem "ruby-debug19" if RUBY_VERSION < "1.9.3"
gem "debugger"
end
end

View File

@ -16,11 +16,17 @@ GEM
activesupport (3.2.7)
i18n (~> 0.6)
multi_json (~> 1.0)
archive-tar-minitar (0.5.2)
arel (3.0.2)
builder (3.0.0)
chronic (0.7.0)
columnize (0.3.6)
debugger (1.2.0)
columnize (>= 0.3.1)
debugger-linecache (~> 1.1.1)
debugger-ruby_core_source (~> 1.1.3)
debugger-linecache (1.1.2)
debugger-ruby_core_source (>= 1.1.1)
debugger-ruby_core_source (1.1.3)
delorean (0.2.1)
chronic
factory_girl (1.3.3)
@ -36,8 +42,6 @@ GEM
json (1.7.4-java)
linecache (0.46)
rbx-require-relative (> 0.0.4)
linecache19 (0.5.12)
ruby_core_source (>= 0.1.4)
multi_json (1.3.6)
mysql (2.8.1)
mysql2 (0.3.11)
@ -52,16 +56,8 @@ GEM
ruby-debug-base (0.10.4)
linecache (>= 0.3)
ruby-debug-base (0.10.4-java)
ruby-debug-base19 (0.11.25)
columnize (>= 0.3.1)
linecache19 (>= 0.5.11)
ruby_core_source (>= 0.1.4)
ruby-debug19 (0.11.6)
columnize (>= 0.3.1)
linecache19 (>= 0.5.11)
ruby-debug-base19 (>= 0.11.19)
ruby_core_source (0.1.5)
archive-tar-minitar (>= 0.5.2)
seamless_database_pool (1.0.11)
activerecord (>= 2.2.2)
sqlite3 (1.3.6)
sqlite3-ruby (1.3.3)
sqlite3 (>= 1.3.3)
@ -74,6 +70,7 @@ PLATFORMS
DEPENDENCIES
activerecord (~> 3.0)
activerecord-jdbcmysql-adapter
debugger
delorean (~> 0.2.0)
factory_girl (~> 1.3.3)
jdbc-mysql
@ -84,5 +81,5 @@ DEPENDENCIES
rake
ruby-debug (= 0.10.4)
ruby-debug-base (= 0.10.4)
ruby-debug19
seamless_database_pool (~> 1.0.11)
sqlite3-ruby (~> 1.3.1)

View File

@ -36,7 +36,7 @@ namespace :display do
end
task :default => ["display:notice"]
ADAPTERS = %w(mysql mysql2 jdbcmysql postgresql sqlite3)
ADAPTERS = %w(mysql mysql2 jdbcmysql postgresql sqlite3 seamless_database_pool mysqlspatial mysql2spatial spatialite postgis)
ADAPTERS.each do |adapter|
namespace :test do
desc "Runs #{adapter} database tests."

View File

@ -1 +1 @@
0.2.10
0.2.11

View File

@ -0,0 +1,68 @@
# Generated by jeweler
# DO NOT EDIT THIS FILE DIRECTLY
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = "activerecord-import"
s.version = "0.2.10"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Zach Dennis"]
s.date = "2012-08-30"
s.description = "Extraction of the ActiveRecord::Base#import functionality from ar-extensions for Rails 3 and beyond"
s.email = "zach.dennis@gmail.com"
s.extra_rdoc_files = [
"README.markdown"
]
s.files = [
"README.markdown",
"Rakefile",
"VERSION",
"lib/activerecord-import.rb",
"lib/activerecord-import/active_record/adapters/abstract_adapter.rb",
"lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb",
"lib/activerecord-import/active_record/adapters/mysql2_adapter.rb",
"lib/activerecord-import/active_record/adapters/mysql_adapter.rb",
"lib/activerecord-import/active_record/adapters/postgresql_adapter.rb",
"lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb",
"lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb",
"lib/activerecord-import/adapters/abstract_adapter.rb",
"lib/activerecord-import/adapters/mysql_adapter.rb",
"lib/activerecord-import/adapters/postgresql_adapter.rb",
"lib/activerecord-import/adapters/sqlite3_adapter.rb",
"lib/activerecord-import/base.rb",
"lib/activerecord-import/import.rb",
"lib/activerecord-import/mysql.rb",
"lib/activerecord-import/mysql2.rb",
"lib/activerecord-import/postgresql.rb",
"lib/activerecord-import/sqlite3.rb",
"lib/activerecord-import/synchronize.rb"
]
s.homepage = "http://github.com/zdennis/activerecord-import"
s.require_paths = ["lib"]
s.rubygems_version = "1.8.24"
s.summary = "Bulk-loading extension for ActiveRecord"
if s.respond_to? :specification_version then
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<activerecord>, ["~> 3.0"])
s.add_development_dependency(%q<rake>, [">= 0"])
s.add_development_dependency(%q<jeweler>, [">= 1.4.0"])
s.add_runtime_dependency(%q<activerecord>, ["~> 3.0"])
else
s.add_dependency(%q<activerecord>, ["~> 3.0"])
s.add_dependency(%q<rake>, [">= 0"])
s.add_dependency(%q<jeweler>, [">= 1.4.0"])
s.add_dependency(%q<activerecord>, ["~> 3.0"])
end
else
s.add_dependency(%q<activerecord>, ["~> 3.0"])
s.add_dependency(%q<rake>, [">= 0"])
s.add_dependency(%q<jeweler>, [">= 1.4.0"])
s.add_dependency(%q<activerecord>, ["~> 3.0"])
end
end

View File

@ -2,5 +2,5 @@ require "active_record/connection_adapters/mysql_adapter"
require "activerecord-import/adapters/mysql_adapter"
class ActiveRecord::ConnectionAdapters::MysqlAdapter
include ActiveRecord::Import::MysqlAdapter::InstanceMethods
include ActiveRecord::Import::MysqlAdapter
end

View File

@ -1,6 +1,6 @@
require "active_record/connection_adapters/mysql2_adapter"
require "activerecord-import/adapters/mysql_adapter"
require "activerecord-import/adapters/mysql2_adapter"
class ActiveRecord::ConnectionAdapters::Mysql2Adapter
include ActiveRecord::Import::MysqlAdapter
include ActiveRecord::Import::Mysql2Adapter
end

View File

@ -0,0 +1,7 @@
require "seamless_database_pool"
require "active_record/connection_adapters/seamless_database_pool_adapter"
require "activerecord-import/adapters/mysql_adapter"
class ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter
include ActiveRecord::Import::MysqlAdapter
end

View File

@ -1,7 +1,7 @@
require "active_record/connection_adapters/sqlite3_adapter"
require "activerecord-import/adapters/sqlite3_adapter"
class ActiveRecord::ConnectionAdapters::Sqlite3Adapter
include ActiveRecord::Import::Sqlite3Adapter
class ActiveRecord::ConnectionAdapters::SQLite3Adapter
include ActiveRecord::Import::SQLite3Adapter
end

View File

@ -34,7 +34,7 @@ module ActiveRecord::Import::AbstractAdapter
# elements that are in position >= 1 will be appended to the final SQL.
def insert_many( sql, values, *args ) # :nodoc:
# the number of inserts default
number_of_inserts = 0
number_of_inserts, last_inserted_id = 0, nil
base_sql,post_sql = if sql.is_a?( String )
[ sql, '' ]
@ -59,17 +59,17 @@ module ActiveRecord::Import::AbstractAdapter
if NO_MAX_PACKET == max or total_bytes < max
number_of_inserts += 1
sql2insert = base_sql + values.join( ',' ) + post_sql
insert( sql2insert, *args )
last_inserted_id = insert( sql2insert, *args )
else
value_sets = self.class.get_insert_value_sets( values, sql_size, max )
value_sets.each do |values|
number_of_inserts += 1
sql2insert = base_sql + values.join( ',' ) + post_sql
insert( sql2insert, *args )
last_inserted_id = insert( sql2insert, *args )
end
end
number_of_inserts
[number_of_inserts, last_inserted_id]
end
def pre_sql_statements(options)

View File

@ -0,0 +1,5 @@
require File.dirname(__FILE__) + "/mysql_adapter"
module ActiveRecord::Import::Mysql2Adapter
include ActiveRecord::Import::MysqlAdapter
end

View File

@ -1,4 +1,4 @@
module ActiveRecord::Import::Sqlite3Adapter
module ActiveRecord::Import::SQLite3Adapter
def next_value_for_sequence(sequence_name)
%{nextval('#{sequence_name}')}
end

View File

@ -5,10 +5,20 @@ require "active_record/version"
module ActiveRecord::Import
AdapterPath = File.join File.expand_path(File.dirname(__FILE__)), "/active_record/adapters"
def self.base_adapter(adapter)
case adapter
when 'mysqlspatial' then 'mysql'
when 'mysql2spatial' then 'mysql2'
when 'spatialite' then 'sqlite3'
when 'postgis' then 'postgresql'
else adapter
end
end
# Loads the import functionality for a specific database adapter
def self.require_adapter(adapter)
require File.join(AdapterPath,"/abstract_adapter")
require File.join(AdapterPath,"/#{adapter}_adapter")
require File.join(AdapterPath,"/#{base_adapter(adapter)}_adapter")
end
# Loads the import functionality for the passed in ActiveRecord connection

View File

@ -3,7 +3,7 @@ require "ostruct"
module ActiveRecord::Import::ConnectionAdapters ; end
module ActiveRecord::Import #:nodoc:
class Result < Struct.new(:failed_instances, :num_inserts)
class Result < Struct.new(:failed_instances, :num_inserts, :last_inserted_id)
end
module ImportSupport #:nodoc:
@ -19,8 +19,8 @@ module ActiveRecord::Import #:nodoc:
end
class MissingColumnError < StandardError
def initialize(index)
super "Missing column for value at index #{index}"
def initialize(name, index)
super "Missing column for value <#{name}> at index #{index}"
end
end
end
@ -137,8 +137,8 @@ class ActiveRecord::Base
#
# # Example synchronizing unsaved/new instances in memory by using a uniqued imported field
# posts = [BlogPost.new(:title => "Foo"), BlogPost.new(:title => "Bar")]
# BlogPost.import posts, :synchronize => posts
# puts posts.first.new_record? # => false
# BlogPost.import posts, :synchronize => posts, :synchronize_keys => [:title]
# puts posts.first.persisted? # => true
#
# == On Duplicate Key Update (MySQL only)
#
@ -162,9 +162,10 @@ class ActiveRecord::Base
# BlogPost.import columns, attributes, :on_duplicate_key_update=>{ :title => :title }
#
# = Returns
# This returns an object which responds to +failed_instances+ and +num_inserts+.
# This returns an object which responds to +failed_instances+, +num_inserts+, +last_inserted_id+.
# * failed_instances - an array of objects that fails validation and were not committed to the database. An empty array if no validation is performed.
# * num_inserts - the number of insert statements it took to import the data
# * num_inserts - the number of insert statements it took to import the data.
# * last_inserted_id - the last inserted id. Should be the id of the latest inserted row.
def import( *args )
options = { :validate=>true, :timestamps=>true }
options.merge!( args.pop ) if args.last.is_a? Hash
@ -191,7 +192,7 @@ class ActiveRecord::Base
end
# supports empty array
elsif args.last.is_a?( Array ) and args.last.empty?
return ActiveRecord::Import::Result.new([], 0) if args.last.empty?
return ActiveRecord::Import::Result.new([], 0, nil) if args.last.empty?
# supports 2-element array and array
elsif args.size == 2 and args.first.is_a?( Array ) and args.last.is_a?( Array )
column_names, array_of_attributes = args
@ -218,8 +219,8 @@ class ActiveRecord::Base
return_obj = if is_validating
import_with_validations( column_names, array_of_attributes, options )
else
num_inserts = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
ActiveRecord::Import::Result.new([], num_inserts)
[num_inserts, last_inserted_id] = import_without_validations_or_callbacks( column_names, array_of_attributes, options )
ActiveRecord::Import::Result.new([], num_inserts, last_inserted_id)
end
if options[:synchronize]
@ -260,9 +261,13 @@ class ActiveRecord::Base
end
end
array_of_attributes.compact!
num_inserts = array_of_attributes.empty? ? 0 : import_without_validations_or_callbacks( column_names, array_of_attributes, options )
ActiveRecord::Import::Result.new(failed_instances, num_inserts)
num_inserts, last_inserted_id = if array_of_attributes.empty? || options[:all_or_none] && failed_instances.any?
[0, nil]
else
import_without_validations_or_callbacks( column_names, array_of_attributes, options )
end
ActiveRecord::Import::Result.new(failed_instances, num_inserts, last_inserted_id)
end
# Imports the passed in +column_names+ and +array_of_attributes+
@ -272,7 +277,21 @@ class ActiveRecord::Base
# information on +column_names+, +array_of_attributes_ and
# +options+.
def import_without_validations_or_callbacks( column_names, array_of_attributes, options={} )
columns = column_names.map { |name| columns_hash[name.to_s] }
number_inserted, last_inserted_id = 0, nil
scope_columns, scope_values = scope_attributes.to_a.transpose
unless scope_columns.blank?
column_names.concat scope_columns
array_of_attributes.each { |a| a.concat scope_values }
end
columns = column_names.each_with_index.map do |name, i|
column = columns_hash[name.to_s]
raise ActiveRecord::Import::MissingColumnError.new(name.to_s, i) if column.nil?
column
end
columns_sql = "(#{column_names.map{|name| connection.quote_column_name(name) }.join(',')})"
insert_sql = "INSERT #{options[:ignore] ? 'IGNORE ':''}INTO #{quoted_table_name} #{columns_sql} VALUES "
@ -288,11 +307,11 @@ class ActiveRecord::Base
post_sql_statements = connection.post_sql_statements( quoted_table_name, options )
# perform the inserts
number_inserted = connection.insert_many( [ insert_sql, post_sql_statements ].flatten,
number_inserted, last_inserted_id = connection.insert_many( [ insert_sql, post_sql_statements ].flatten,
values_sql,
"#{self.class.name} Create Many Without Validations Or Callbacks" )
end
number_inserted
[number_inserted, last_inserted_id]
end
private
@ -300,16 +319,22 @@ class ActiveRecord::Base
# Returns SQL the VALUES for an INSERT statement given the passed in +columns+
# and +array_of_attributes+.
def values_sql_for_columns_and_attributes(columns, array_of_attributes) # :nodoc:
# connection gets called a *lot* in this high intensity loop.
# Reuse the same one w/in the loop, otherwise it would keep being re-retreived (= lots of time for large imports)
connection_memo = connection
array_of_attributes.map do |arr|
my_values = arr.each_with_index.map do |val,j|
column = columns[j]
raise ActiveRecord::Import::MissingColumnError.new(j) if column.nil?
if val.nil? && !sequence_name.blank? && column.name == primary_key
connection.next_value_for_sequence(sequence_name)
# be sure to query sequence_name *last*, only if cheaper tests fail, because it's costly
if val.nil? && column.name == primary_key && !sequence_name.blank?
connection_memo.next_value_for_sequence(sequence_name)
else
connection.quote(column.type_cast(val), column)
if serialized_attributes.include?(column.name)
connection_memo.quote(serialized_attributes[column.name].dump(val), column)
else
connection_memo.quote(val, column)
end
end
end
"(#{my_values.join(',')})"

View File

@ -43,6 +43,10 @@ module ActiveRecord # :nodoc:
instance.clear_aggregation_cache
instance.clear_association_cache
instance.instance_variable_set '@attributes', matched_instance.attributes
# Since the instance now accurately reflects the record in
# the database, ensure that instance.persisted? is true.
instance.instance_variable_set '@new_record', false
instance.instance_variable_set '@destroyed', false
end
end
end
@ -52,4 +56,4 @@ module ActiveRecord # :nodoc:
self.class.synchronize(instances, key)
end
end
end
end

View File

@ -50,3 +50,13 @@ describe "ActiveRecord::ConnectionAdapter::AbstractAdapter" do
end
end
describe "ActiveRecord::Import DB-specific adapter class" do
context "when ActiveRecord::Import is in use" do
it "should appear in the AR connection adapter class's ancestors" do
connection = ActiveRecord::Base.connection
import_class_name = 'ActiveRecord::Import::' + connection.class.name.demodulize
assert_includes connection.class.ancestors, import_class_name.constantize
end
end
end

View File

@ -1 +1 @@
ENV["ARE_DB"] = "mysql2"
ENV["ARE_DB"] = "mysql2"

View File

@ -0,0 +1 @@
ENV["ARE_DB"] = "mysql2spatial"

View File

@ -0,0 +1 @@
ENV["ARE_DB"] = "mysqlspatial"

1
test/adapters/postgis.rb Normal file
View File

@ -0,0 +1 @@
ENV["ARE_DB"] = "postgis"

View File

@ -0,0 +1 @@
ENV["ARE_DB"] = "seamless_database_pool"

View File

@ -0,0 +1 @@
ENV["ARE_DB"] = "spatialite"

View File

@ -13,12 +13,28 @@ mysql2:
<<: *common
adapter: mysql2
mysqlspatial:
<<: *mysql
mysqlspatial2:
<<: *mysql2
seamless_database_pool:
<<: *common
adapter: seamless_database_pool
pool_adapter: mysql2
master:
host: localhost
postgresql:
<<: *common
username: postgres
adapter: postgresql
min_messages: warning
postgis:
<<: *postgresql
oracle:
<<: *common
adapter: oracle
@ -31,3 +47,6 @@ sqlite:
sqlite3:
adapter: sqlite3
database: test.db
spatialite:
<<: *sqlite3

View File

@ -81,7 +81,45 @@ describe "#import" do
end
end
end
context "with :all_or_none option" do
let(:columns) { %w(title author_name) }
let(:valid_values) { [[ "LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
let(:invalid_values) { [[ "The RSpec Book", ""], ["Agile+UX", ""]] }
let(:mixed_values) { valid_values + invalid_values }
context "with validation checks turned on" do
it "should import valid data" do
assert_difference "Topic.count", +2 do
result = Topic.import columns, valid_values, :all_or_none => true
end
end
it "should not import invalid data" do
assert_no_difference "Topic.count" do
result = Topic.import columns, invalid_values, :all_or_none => true
end
end
it "should not import valid data when mixed with invalid data" do
assert_no_difference "Topic.count" do
result = Topic.import columns, mixed_values, :all_or_none => true
end
end
it "should report the failed instances" do
results = Topic.import columns, mixed_values, :all_or_none => true
assert_equal invalid_values.size, results.failed_instances.size
results.failed_instances.each { |e| assert_kind_of Topic, e }
end
it "should report the zero inserts" do
results = Topic.import columns, mixed_values, :all_or_none => true
assert_equal 0, results.num_inserts
end
end
end
context "with :synchronize option" do
context "synchronizing on new records" do
let(:new_topics) { Build(3, :topics) }
@ -96,8 +134,18 @@ describe "#import" do
let(:new_topics) { Build(3, :topics) }
it "reloads data for existing in-memory instances" do
Topic.import(new_topics, :synchronize => new_topics, :synchronize_key => [:title] )
assert new_topics.all?(&:new_record?), "Records should have been reloaded"
Topic.import(new_topics, :synchronize => new_topics, :synchronize_keys => [:title] )
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
end
end
context "synchronizing on destroyed records with explicit conditions" do
let(:new_topics) { Generate(3, :topics) }
it "reloads data for existing in-memory instances" do
new_topics.each &:destroy
Topic.import(new_topics, :synchronize => new_topics, :synchronize_keys => [:title] )
assert new_topics.all?(&:persisted?), "Records should have been reloaded"
end
end
end
@ -255,4 +303,18 @@ describe "#import" do
assert_equal "2010/05/14".to_date, Topic.last.last_read.to_date
end
end
end
context "importing through an association scope" do
[ true, false ].each do |b|
context "when validation is " + (b ? "enabled" : "disabled") do
it "should automatically set the foreign key column" do
books = [[ "David Chelimsky", "The RSpec Book" ], [ "Chad Fowler", "Rails Recipes" ]]
topic = Factory.create :topic
topic.books.import [ :author_name, :title ], books, :validate => b
assert_equal 2, topic.books.count
assert topic.books.all? { |b| b.topic_id == topic.id }
end
end
end
end
end

View File

@ -0,0 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/assertions')
require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
should_support_mysql_import_functionality

View File

@ -0,0 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/assertions')
require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
should_support_mysql_import_functionality

View File

@ -0,0 +1,4 @@
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
should_support_postgresql_import_functionality

View File

@ -1,20 +1,4 @@
require File.expand_path('../../test_helper', __FILE__)
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
describe "#supports_imports?" do
it "should support import" do
assert ActiveRecord::Base.supports_import?
end
end
describe "#import" do
it "should import with a single insert" do
# see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
assert_difference "Topic.count", +10 do
result = Topic.import Build(3, :topics)
assert_equal 1, result.num_inserts
result = Topic.import Build(7, :topics)
assert_equal 1, result.num_inserts
end
end
end
should_support_postgresql_import_functionality

View File

@ -0,0 +1,21 @@
# encoding: UTF-8
def should_support_postgresql_import_functionality
describe "#supports_imports?" do
it "should support import" do
assert ActiveRecord::Base.supports_import?
end
end
describe "#import" do
it "should import with a single insert" do
# see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
assert_difference "Topic.count", +10 do
result = Topic.import Build(3, :topics)
assert_equal 1, result.num_inserts
result = Topic.import Build(7, :topics)
assert_equal 1, result.num_inserts
end
end
end
end

View File

@ -44,3 +44,8 @@ adapter_schema = test_dir.join("schema/#{adapter}_schema.rb")
require adapter_schema if File.exists?(adapter_schema)
Dir[File.dirname(__FILE__) + "/models/*.rb"].each{ |file| require file }
# Prevent this deprecation warning from breaking the tests.
module Rake::DeprecatedObjectDSL
remove_method :import
end