diff --git a/Gemfile b/Gemfile index 77900d1..cc46482 100644 --- a/Gemfile +++ b/Gemfile @@ -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 diff --git a/Gemfile.lock b/Gemfile.lock index c1139c6..b3cf6b4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/Rakefile b/Rakefile index 38f7d17..1a50839 100644 --- a/Rakefile +++ b/Rakefile @@ -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." diff --git a/VERSION b/VERSION index d156ab4..f8112eb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.10 \ No newline at end of file +0.2.11 \ No newline at end of file diff --git a/activerecord-import.gemspec b/activerecord-import.gemspec new file mode 100644 index 0000000..2f0cf16 --- /dev/null +++ b/activerecord-import.gemspec @@ -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, ["~> 3.0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 1.4.0"]) + s.add_runtime_dependency(%q, ["~> 3.0"]) + else + s.add_dependency(%q, ["~> 3.0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 1.4.0"]) + s.add_dependency(%q, ["~> 3.0"]) + end + else + s.add_dependency(%q, ["~> 3.0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 1.4.0"]) + s.add_dependency(%q, ["~> 3.0"]) + end +end + diff --git a/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb b/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb index 14df303..b21ec63 100644 --- a/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +++ b/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb @@ -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 diff --git a/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb b/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb index 7f34e2f..74828b7 100644 --- a/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +++ b/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb @@ -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 diff --git a/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb b/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb new file mode 100644 index 0000000..388c618 --- /dev/null +++ b/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb @@ -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 diff --git a/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb b/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb index f0940c8..c50b8ca 100644 --- a/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +++ b/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb @@ -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 diff --git a/lib/activerecord-import/adapters/abstract_adapter.rb b/lib/activerecord-import/adapters/abstract_adapter.rb index 5f38001..3fcc353 100644 --- a/lib/activerecord-import/adapters/abstract_adapter.rb +++ b/lib/activerecord-import/adapters/abstract_adapter.rb @@ -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) diff --git a/lib/activerecord-import/adapters/mysql2_adapter.rb b/lib/activerecord-import/adapters/mysql2_adapter.rb new file mode 100644 index 0000000..71e6a64 --- /dev/null +++ b/lib/activerecord-import/adapters/mysql2_adapter.rb @@ -0,0 +1,5 @@ +require File.dirname(__FILE__) + "/mysql_adapter" + +module ActiveRecord::Import::Mysql2Adapter + include ActiveRecord::Import::MysqlAdapter +end \ No newline at end of file diff --git a/lib/activerecord-import/adapters/sqlite3_adapter.rb b/lib/activerecord-import/adapters/sqlite3_adapter.rb index 0d298a4..2fb673d 100644 --- a/lib/activerecord-import/adapters/sqlite3_adapter.rb +++ b/lib/activerecord-import/adapters/sqlite3_adapter.rb @@ -1,4 +1,4 @@ -module ActiveRecord::Import::Sqlite3Adapter +module ActiveRecord::Import::SQLite3Adapter def next_value_for_sequence(sequence_name) %{nextval('#{sequence_name}')} end diff --git a/lib/activerecord-import/base.rb b/lib/activerecord-import/base.rb index 8aeb27c..8498357 100644 --- a/lib/activerecord-import/base.rb +++ b/lib/activerecord-import/base.rb @@ -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 diff --git a/lib/activerecord-import/import.rb b/lib/activerecord-import/import.rb index c4e4c3c..ecb634a 100644 --- a/lib/activerecord-import/import.rb +++ b/lib/activerecord-import/import.rb @@ -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(',')})" diff --git a/lib/activerecord-import/synchronize.rb b/lib/activerecord-import/synchronize.rb index 60408e5..a5286d7 100644 --- a/lib/activerecord-import/synchronize.rb +++ b/lib/activerecord-import/synchronize.rb @@ -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 \ No newline at end of file +end diff --git a/test/active_record/connection_adapter_test.rb b/test/active_record/connection_adapter_test.rb index ad3234c..5b81e7d 100644 --- a/test/active_record/connection_adapter_test.rb +++ b/test/active_record/connection_adapter_test.rb @@ -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 \ No newline at end of file diff --git a/test/adapters/mysql2.rb b/test/adapters/mysql2.rb index 0ddab79..e67530f 100644 --- a/test/adapters/mysql2.rb +++ b/test/adapters/mysql2.rb @@ -1 +1 @@ -ENV["ARE_DB"] = "mysql2" \ No newline at end of file +ENV["ARE_DB"] = "mysql2" diff --git a/test/adapters/mysql2spatial.rb b/test/adapters/mysql2spatial.rb new file mode 100644 index 0000000..3bc9f6c --- /dev/null +++ b/test/adapters/mysql2spatial.rb @@ -0,0 +1 @@ +ENV["ARE_DB"] = "mysql2spatial" \ No newline at end of file diff --git a/test/adapters/mysqlspatial.rb b/test/adapters/mysqlspatial.rb new file mode 100644 index 0000000..03316d1 --- /dev/null +++ b/test/adapters/mysqlspatial.rb @@ -0,0 +1 @@ +ENV["ARE_DB"] = "mysqlspatial" \ No newline at end of file diff --git a/test/adapters/postgis.rb b/test/adapters/postgis.rb new file mode 100644 index 0000000..0902039 --- /dev/null +++ b/test/adapters/postgis.rb @@ -0,0 +1 @@ +ENV["ARE_DB"] = "postgis" \ No newline at end of file diff --git a/test/adapters/seamless_database_pool.rb b/test/adapters/seamless_database_pool.rb new file mode 100644 index 0000000..1f4a8b3 --- /dev/null +++ b/test/adapters/seamless_database_pool.rb @@ -0,0 +1 @@ +ENV["ARE_DB"] = "seamless_database_pool" diff --git a/test/adapters/spatialite.rb b/test/adapters/spatialite.rb new file mode 100644 index 0000000..1ff27f2 --- /dev/null +++ b/test/adapters/spatialite.rb @@ -0,0 +1 @@ +ENV["ARE_DB"] = "spatialite" \ No newline at end of file diff --git a/test/database.yml.sample b/test/database.yml.sample index 6e0c4ee..9bd8221 100644 --- a/test/database.yml.sample +++ b/test/database.yml.sample @@ -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 diff --git a/test/import_test.rb b/test/import_test.rb index 094dd97..cc06891 100644 --- a/test/import_test.rb +++ b/test/import_test.rb @@ -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 \ No newline at end of file + + 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 diff --git a/test/mysqlspatial/import_test.rb b/test/mysqlspatial/import_test.rb new file mode 100644 index 0000000..feaff67 --- /dev/null +++ b/test/mysqlspatial/import_test.rb @@ -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 \ No newline at end of file diff --git a/test/mysqlspatial2/import_test.rb b/test/mysqlspatial2/import_test.rb new file mode 100644 index 0000000..feaff67 --- /dev/null +++ b/test/mysqlspatial2/import_test.rb @@ -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 \ No newline at end of file diff --git a/test/postgis/import_test.rb b/test/postgis/import_test.rb new file mode 100644 index 0000000..436fc83 --- /dev/null +++ b/test/postgis/import_test.rb @@ -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 \ No newline at end of file diff --git a/test/postgresql/import_test.rb b/test/postgresql/import_test.rb index 45e97ef..436fc83 100644 --- a/test/postgresql/import_test.rb +++ b/test/postgresql/import_test.rb @@ -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 \ No newline at end of file diff --git a/test/support/postgresql/import_examples.rb b/test/support/postgresql/import_examples.rb new file mode 100644 index 0000000..fe7b61f --- /dev/null +++ b/test/support/postgresql/import_examples.rb @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb index 73f2802..a28e5b4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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