From 075104a94475ffa56aa9a3d0d767ab41f4b95b7e Mon Sep 17 00:00:00 2001 From: Zach Dennis Date: Sat, 13 Mar 2010 22:01:01 -0500 Subject: [PATCH] Finished porting over MySQL import functionality. * removed assertions on #num_inserts, that is db-specific and is handled in details by abstract_adapter_test. --- .../adapters/abstract_adapter.rb | 2 +- lib/ar-extensions/import.rb | 1 - test/import_test.rb | 59 +++++-------- test/mysql/import_test.rb | 83 +++++++------------ test/schema/generic_schema.rb | 2 + test/support/mysql/assertions.rb | 55 ++++++++++++ test/test_helper.rb | 16 ++++ 7 files changed, 123 insertions(+), 95 deletions(-) create mode 100644 test/support/mysql/assertions.rb diff --git a/lib/ar-extensions/active_record/adapters/abstract_adapter.rb b/lib/ar-extensions/active_record/adapters/abstract_adapter.rb index a088ccb..2325dd6 100644 --- a/lib/ar-extensions/active_record/adapters/abstract_adapter.rb +++ b/lib/ar-extensions/active_record/adapters/abstract_adapter.rb @@ -45,7 +45,7 @@ module ActiveRecord # :nodoc: sql2insert = base_sql + values.join( ',' ) + post_sql insert( sql2insert, *args ) end - end + end number_of_inserts end diff --git a/lib/ar-extensions/import.rb b/lib/ar-extensions/import.rb index e682668..3c66aab 100644 --- a/lib/ar-extensions/import.rb +++ b/lib/ar-extensions/import.rb @@ -289,7 +289,6 @@ class ActiveRecord::Base values_sql, "#{self.class.name} Create Many Without Validations Or Callbacks" ) end - number_inserted end diff --git a/test/import_test.rb b/test/import_test.rb index e4f1e22..80610aa 100644 --- a/test/import_test.rb +++ b/test/import_test.rb @@ -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 diff --git a/test/mysql/import_test.rb b/test/mysql/import_test.rb index 818334f..976399f 100644 --- a/test/mysql/import_test.rb +++ b/test/mysql/import_test.rb @@ -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 diff --git a/test/schema/generic_schema.rb b/test/schema/generic_schema.rb index 93ee116..02bd23e 100644 --- a/test/schema/generic_schema.rb +++ b/test/schema/generic_schema.rb @@ -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| diff --git a/test/support/mysql/assertions.rb b/test/support/mysql/assertions.rb new file mode 100644 index 0000000..7fb0bf7 --- /dev/null +++ b/test/support/mysql/assertions.rb @@ -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 \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 8842d20..f377bfd 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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