Finished porting over MySQL import functionality.

* removed assertions on #num_inserts, that is db-specific and is handled in details by abstract_adapter_test.
master
Zach Dennis 2010-03-13 22:01:01 -05:00
parent e8271778b7
commit 075104a944
7 changed files with 123 additions and 95 deletions

View File

@ -45,7 +45,7 @@ module ActiveRecord # :nodoc:
sql2insert = base_sql + values.join( ',' ) + post_sql
insert( sql2insert, *args )
end
end
end
number_of_inserts
end

View File

@ -289,7 +289,6 @@ class ActiveRecord::Base
values_sql,
"#{self.class.name} Create Many Without Validations Or Callbacks" )
end
number_inserted
end

View File

@ -1,17 +1,17 @@
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
describe "#import" do
it "should return the number of inserts performed" do
# see ActiveRecord::ConnectionAdapters::AbstractAdapter test for more specifics
assert_difference "Topic.count", +10 do
result = Topic.import Build(3, :topics)
assert_equal 3, result.num_inserts
assert result.num_inserts > 0
result = Topic.import Build(7, :topics)
assert_equal 7, result.num_inserts
assert result.num_inserts > 0
end
end
context "with :validation option" do
let(:columns) { %w(title author_name) }
let(:valid_values) { [[ "LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] }
@ -21,14 +21,12 @@ describe "#import" do
it "should import valid data" do
assert_difference "Topic.count", +2 do
result = Topic.import columns, valid_values, :validate => false
assert_equal 2, result.num_inserts
end
end
it "should import invalid data" do
assert_difference "Topic.count", +2 do
result = Topic.import columns, invalid_values, :validate => false
assert_equal 2, result.num_inserts
end
end
end
@ -37,33 +35,30 @@ describe "#import" do
it "should import valid data" do
assert_difference "Topic.count", +2 do
result = Topic.import columns, valid_values, :validate => true
assert_equal 2, result.num_inserts
end
end
it "should not import invalid data" do
assert_no_difference "Topic.count" do
result = Topic.import columns, invalid_values, :validate => true
assert_equal 0, result.num_inserts
end
end
it "should report the failed instances" do
results = Topic.import columns, invalid_values, :validate => true
assert_equal invalid_values.size, results.failed_instances.size
results.failed_instances.each{ |e| assert_kind_of Topic, e }
end
it "should import valid data when mixed with invalid data" do
assert_difference "Topic.count", +2 do
result = Topic.import columns, valid_values + invalid_values, :validate => true
assert_equal 2, result.num_inserts
end
assert_equal 0, Topic.find_all_by_title(invalid_values.map(&:first)).count
end
end
end
context "with an array of unsaved model instances" do
let(:topic) { Build(:topic, :title => "The RSpec Book", :author_name => "David Chelimsky")}
let(:topics) { Build(9, :topics) }
@ -72,13 +67,12 @@ describe "#import" do
it "should import records based on those model's attributes" do
assert_difference "Topic.count", +9 do
result = Topic.import topics
assert_equal 9, result.num_inserts
end
Topic.import [topic]
assert Topic.find_by_title_and_author_name("The RSpec Book", "David Chelimsky")
end
it "should not overwrite existing records" do
topic = Generate(:topic, :title => "foobar")
assert_no_difference "Topic.count" do
@ -96,14 +90,12 @@ describe "#import" do
it "should import valid models" do
assert_difference "Topic.count", +9 do
result = Topic.import topics, :validate => true
assert_equal 9, result.num_inserts
end
end
it "should not import invalid models" do
assert_no_difference "Topic.count" do
result = Topic.import invalid_topics, :validate => true
assert_equal 0, result.num_inserts
end
end
end
@ -112,7 +104,6 @@ describe "#import" do
it "should import invalid models" do
assert_difference "Topic.count", +7 do
result = Topic.import invalid_topics, :validate => false
assert_equal 7, result.num_inserts
end
end
end
@ -124,32 +115,29 @@ describe "#import" do
it "should import records populating the supplied columns with the corresponding model instance attributes" do
assert_difference "Topic.count", +2 do
result = Topic.import [:author_name, :title], topics
assert_equal 2, result.num_inserts
end
# imported topics should be findable by their imported attributes
assert Topic.find_by_author_name(topics.first.author_name)
assert Topic.find_by_author_name(topics.last.author_name)
end
it "should not populate fields for columns not imported" do
topics.first.author_email_address = "zach.dennis@gmail.com"
assert_difference "Topic.count", +2 do
result = Topic.import [:author_name, :title], topics
assert_equal 2, result.num_inserts
end
assert !Topic.find_by_author_email_address("zach.dennis@gmail.com")
end
end
context "ActiveRecord timestamps" do
context "when the timestamps columns are present" do
setup do
Delorean.time_travel_to("5 minutes ago") do
assert_difference "Book.count", +1 do
result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
assert_equal 1, result.num_inserts
end
end
@book = Book.last
@ -158,15 +146,15 @@ describe "#import" do
it "should set the created_at column for new records" do
assert_equal 5.minutes.ago.strftime("%H:%M"), @book.created_at.strftime("%H:%M")
end
it "should set the created_on column for new records" do
assert_equal 5.minutes.ago.strftime("%H:%M"), @book.created_on.strftime("%H:%M")
end
it "should set the updated_at column for new records" do
assert_equal 5.minutes.ago.strftime("%H:%M"), @book.updated_at.strftime("%H:%M")
end
it "should set the updated_on column for new records" do
assert_equal 5.minutes.ago.strftime("%H:%M"), @book.updated_on.strftime("%H:%M")
end
@ -179,39 +167,30 @@ describe "#import" do
Delorean.time_travel_to("5 minutes ago") do
assert_difference "Book.count", +1 do
result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]]
assert_equal 1, result.num_inserts
end
end
ActiveRecord::Base.default_timezone = original_timezone
@book = Book.last
end
it "should set the created_at column for new records respecting the time zone" do
ActiveRecord::Base.default_timezone
it "should set the created_at and created_on timestamps for new records" do
assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.created_at.strftime("%H:%M")
end
it "should set the created_on column for new records respecting the time zone" do
assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.created_on.strftime("%H:%M")
end
it "should set the updated_at column for new records respecting the time zone" do
it "should set the updated_at and updated_on timestamps for new records" do
assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.updated_at.strftime("%H:%M")
end
it "should set the updated_on column for new records respecting the time zone" do
assert_equal 5.minutes.ago.utc.strftime("%H:%M"), @book.updated_on.strftime("%H:%M")
end
end
end
context "importing with database reserved words" do
let(:group) { Build(:group, :order => "superx") }
it "should import just fine" do
assert_difference "Group.count", +1 do
result = Group.import [group]
assert_equal 1, result.num_inserts
end
assert_equal "superx", Group.first.order
end

View File

@ -2,36 +2,25 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
require "ar-extensions/import/mysql"
describe "#import with :on_duplicate_key_update option (mysql specific functionality)" do
extend ActiveSupport::TestCase::MySQLAssertions
asssertion_group(:should_support_on_duplicate_key_update) do
should_not_update_fields_not_mentioned
should_update_foreign_keys
should_not_update_created_at_on_timestamp_columns
should_update_updated_at_on_timestamp_columns
end
macro(:perform_import){ raise "supply your own #perform_import in a context below" }
assertion(:should_not_update_fields_not_mentioned) do
assert_equal "John Doe", @topic.reload.author_name
end
assertion(:should_update_fields_mentioned) do
perform_import
assert_equal "Book - 2nd Edition", @topic.reload.title
assert_equal "johndoe@example.com", @topic.reload.author_email_address
end
assertion(:should_update_fields_mentioned_with_hash_mappings) do
perform_import
assert_equal "johndoe@example.com", @topic.reload.title
assert_equal "Book - 2nd Edition", @topic.reload.author_email_address
end
assertion(:should_update_foreign_keys) do
perform_import
assert_equal 57, @topic.reload.parent_id
end
macro(:updated_topic){ Topic.find(@topic) }
context "given columns and values with :validation checks turned off" do
let(:columns){ %w( id title author_name author_email_address parent_id ) }
let(:values){ [ [ 99, "Book", "John Doe", "john@doe.com", 17 ] ] }
let(:updated_values){ [ [ 99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57 ] ] }
macro(:perform_import) do
Topic.import columns, updated_values, :on_duplicate_key_update => update_columns, :validate => false
macro(:perform_import) do |*opts|
Topic.import columns, updated_values, opts.extract_options!.merge(:on_duplicate_key_update => update_columns, :validate => false)
end
setup do
@ -41,54 +30,48 @@ describe "#import with :on_duplicate_key_update option (mysql specific functiona
context "using string column names" do
let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned
should_update_foreign_keys
end
context "using symbol column names" do
let(:update_columns){ [ :title, :author_email_address, :parent_id ] }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned
should_update_foreign_keys
end
context "using string hash map" do
let(:update_columns){ { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned
should_update_foreign_keys
end
context "using string hash map, but specifying column mismatches" do
let(:update_columns){ { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned_with_hash_mappings
should_update_foreign_keys
end
context "using symbol hash map" do
let(:update_columns){ { :title => :title, :author_email_address => :author_email_address, :parent_id => :parent_id } }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned
should_update_foreign_keys
end
context "using symbol hash map, but specifying column mismatches" do
let(:update_columns){ { :title => :author_email_address, :author_email_address => :title, :parent_id => :parent_id } }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned_with_hash_mappings
should_update_foreign_keys
end
end
context "given array of model instances with :validation checks turned off" do
macro(:perform_import) do
macro(:perform_import) do |*opts|
@topic.title = "Book - 2nd Edition"
@topic.author_name = "Author Should Not Change"
@topic.author_email_address = "johndoe@example.com"
@topic.parent_id = 57
Topic.import [@topic], :on_duplicate_key_update => update_columns, :validate => false
Topic.import [@topic], opts.extract_options!.merge(:on_duplicate_key_update => update_columns, :validate => false)
end
setup do
@ -97,44 +80,38 @@ describe "#import with :on_duplicate_key_update option (mysql specific functiona
context "using string column names" do
let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned
should_update_foreign_keys
end
context "using symbol column names" do
let(:update_columns){ [ :title, :author_email_address, :parent_id ] }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned
should_update_foreign_keys
end
context "using string hash map" do
let(:update_columns){ { "title" => "title", "author_email_address" => "author_email_address", "parent_id" => "parent_id" } }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned
should_update_foreign_keys
end
context "using string hash map, but specifying column mismatches" do
let(:update_columns){ { "title" => "author_email_address", "author_email_address" => "title", "parent_id" => "parent_id" } }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned_with_hash_mappings
should_update_foreign_keys
end
context "using symbol hash map" do
let(:update_columns){ { :title => :title, :author_email_address => :author_email_address, :parent_id => :parent_id } }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned
should_update_foreign_keys
end
context "using symbol hash map, but specifying column mismatches" do
let(:update_columns){ { :title => :author_email_address, :author_email_address => :title, :parent_id => :parent_id } }
should_not_update_fields_not_mentioned
should_support_on_duplicate_key_update
should_update_fields_mentioned_with_hash_mappings
should_update_foreign_keys
end
end

View File

@ -23,7 +23,9 @@ ActiveRecord::Schema.define do
t.column :parent_id, :integer
t.column :type, :string
t.column :created_at, :datetime
t.column :created_on, :datetime
t.column :updated_at, :datetime
t.column :updated_on, :datetime
end
create_table :projects, :force=>true do |t|

View File

@ -0,0 +1,55 @@
class ActiveSupport::TestCase
module MySQLAssertions
def self.extended(klass)
klass.instance_eval do
assertion(:should_not_update_created_at_on_timestamp_columns) do
Delorean.time_travel_to("5 minutes from now") do
perform_import
assert_equal @topic.created_at.to_i, updated_topic.created_at.to_i
assert_equal @topic.created_on.to_i, updated_topic.created_on.to_i
end
end
assertion(:should_update_updated_at_on_timestamp_columns) do
time = Chronic.parse("5 minutes from now")
Delorean.time_travel_to(time) do
perform_import
assert_equal time.to_i, updated_topic.updated_at.to_i
assert_equal time.to_i, updated_topic.updated_on.to_i
end
end
assertion(:should_not_update_timestamps) do
Delorean.time_travel_to("5 minutes from now") do
perform_import :timestamps => false
assert_equal @topic.created_at.to_i, updated_topic.created_at.to_i
assert_equal @topic.created_on.to_i, updated_topic.created_on.to_i
assert_equal @topic.updated_at.to_i, updated_topic.updated_at.to_i
assert_equal @topic.updated_on.to_i, updated_topic.updated_on.to_i
end
end
assertion(:should_not_update_fields_not_mentioned) do
assert_equal "John Doe", updated_topic.author_name
end
assertion(:should_update_fields_mentioned) do
perform_import
assert_equal "Book - 2nd Edition", updated_topic.title
assert_equal "johndoe@example.com", updated_topic.author_email_address
end
assertion(:should_update_fields_mentioned_with_hash_mappings) do
perform_import
assert_equal "johndoe@example.com", updated_topic.title
assert_equal "Book - 2nd Edition", updated_topic.author_email_address
end
assertion(:should_update_foreign_keys) do
perform_import
assert_equal 57, updated_topic.parent_id
end
end
end
end
end

View File

@ -30,6 +30,13 @@ class ActiveSupport::TestCase
end
end
end
def asssertion_group(name, &block)
mc = class << self ; self ; end
mc.class_eval do
define_method(name, &block)
end
end
def macro(name, &block)
class_eval do
@ -40,11 +47,20 @@ class ActiveSupport::TestCase
def describe(description, toplevel=nil, &blk)
text = toplevel ? description : "#{name} #{description}"
klass = Class.new(self)
klass.class_eval <<-RUBY_EVAL
def self.name
"#{text}"
end
RUBY_EVAL
# do not inherit test methods from the superclass
klass.class_eval do
instance_methods.grep(/^test.+/) do |method|
undef_method method
end
end
klass.instance_eval &blk
end
alias_method :context, :describe