About 85% through porting over MySQL on duplicate key functionality.
This commit is contained in:
parent
5836e449fd
commit
e8271778b7
50
lib/ar-extensions/import/mysql.rb
Normal file
50
lib/ar-extensions/import/mysql.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
module ActiveRecord::Extensions::ConnectionAdapters::MysqlAdapter # :nodoc:
|
||||||
|
|
||||||
|
include ActiveRecord::Extensions::Import::ImportSupport
|
||||||
|
include ActiveRecord::Extensions::Import::OnDuplicateKeyUpdateSupport
|
||||||
|
|
||||||
|
# Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
|
||||||
|
# in +args+.
|
||||||
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
||||||
|
sql = ' ON DUPLICATE KEY UPDATE '
|
||||||
|
arg = args.first
|
||||||
|
if arg.is_a?( Array )
|
||||||
|
sql << sql_for_on_duplicate_key_update_as_array( table_name, arg )
|
||||||
|
elsif arg.is_a?( Hash )
|
||||||
|
sql << sql_for_on_duplicate_key_update_as_hash( table_name, arg )
|
||||||
|
elsif arg.is_a?( String )
|
||||||
|
sql << arg
|
||||||
|
else
|
||||||
|
raise ArgumentError.new( "Expected Array or Hash" )
|
||||||
|
end
|
||||||
|
sql
|
||||||
|
end
|
||||||
|
|
||||||
|
def sql_for_on_duplicate_key_update_as_array( table_name, arr ) # :nodoc:
|
||||||
|
results = arr.map do |column|
|
||||||
|
qc = quote_column_name( column )
|
||||||
|
"#{table_name}.#{qc}=VALUES(#{qc})"
|
||||||
|
end
|
||||||
|
results.join( ',' )
|
||||||
|
end
|
||||||
|
|
||||||
|
def sql_for_on_duplicate_key_update_as_hash( table_name, hsh ) # :nodoc:
|
||||||
|
sql = ' ON DUPLICATE KEY UPDATE '
|
||||||
|
results = hsh.map do |column1, column2|
|
||||||
|
qc1 = quote_column_name( column1 )
|
||||||
|
qc2 = quote_column_name( column2 )
|
||||||
|
"#{table_name}.#{qc1}=VALUES( #{qc2} )"
|
||||||
|
end
|
||||||
|
results.join( ',')
|
||||||
|
end
|
||||||
|
|
||||||
|
#return true if the statement is a duplicate key record error
|
||||||
|
def duplicate_key_update_error?(exception)# :nodoc:
|
||||||
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
||||||
|
include ActiveRecord::Extensions::ConnectionAdapters::MysqlAdapter
|
||||||
|
end
|
|
@ -1,6 +1,7 @@
|
||||||
class Topic < ActiveRecord::Base
|
class Topic < ActiveRecord::Base
|
||||||
validates_presence_of :author_name
|
validates_presence_of :author_name
|
||||||
has_many :books
|
has_many :books
|
||||||
|
belongs_to :parent, :class_name => "Topic"
|
||||||
|
|
||||||
composed_of :description, :mapping => [ %w(title title), %w(author_name author_name)], :allow_nil => true, :class_name => "TopicDescription"
|
composed_of :description, :mapping => [ %w(title title), %w(author_name author_name)], :allow_nil => true, :class_name => "TopicDescription"
|
||||||
end
|
end
|
||||||
|
|
141
test/mysql/import_test.rb
Normal file
141
test/mysql/import_test.rb
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Topic.import columns, values, :validate => false
|
||||||
|
@topic = Topic.find 99
|
||||||
|
end
|
||||||
|
|
||||||
|
context "using string column names" do
|
||||||
|
let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
|
||||||
|
should_not_update_fields_not_mentioned
|
||||||
|
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_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_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_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_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_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
|
||||||
|
@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
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
@topic = Generate(:topic, :id => 99, :author_name => "John Doe", :parent_id => 17)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "using string column names" do
|
||||||
|
let(:update_columns){ [ "title", "author_email_address", "parent_id" ] }
|
||||||
|
should_not_update_fields_not_mentioned
|
||||||
|
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_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_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_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_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_update_fields_mentioned_with_hash_mappings
|
||||||
|
should_update_foreign_keys
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
32
test/schema/mysql_schema.rb
Normal file
32
test/schema/mysql_schema.rb
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
ActiveRecord::Schema.define do
|
||||||
|
|
||||||
|
create_table :test_myisam, :options=>'ENGINE=MyISAM', :force=>true do |t|
|
||||||
|
t.column :my_name, :string, :null=>false
|
||||||
|
t.column :description, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :test_innodb, :options=>'ENGINE=InnoDb', :force=>true do |t|
|
||||||
|
t.column :my_name, :string, :null=>false
|
||||||
|
t.column :description, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :test_memory, :options=>'ENGINE=Memory', :force=>true do |t|
|
||||||
|
t.column :my_name, :string, :null=>false
|
||||||
|
t.column :description, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :books, :options=>'ENGINE=MyISAM', :force=>true do |t|
|
||||||
|
t.column :title, :string, :null=>false
|
||||||
|
t.column :publisher, :string, :null=>false, :default => 'Default Publisher'
|
||||||
|
t.column :author_name, :string, :null=>false
|
||||||
|
t.column :created_at, :datetime
|
||||||
|
t.column :created_on, :datetime
|
||||||
|
t.column :updated_at, :datetime
|
||||||
|
t.column :updated_on, :datetime
|
||||||
|
t.column :publish_date, :date
|
||||||
|
t.column :topic_id, :integer
|
||||||
|
t.column :for_sale, :boolean, :default => true
|
||||||
|
end
|
||||||
|
execute "ALTER TABLE books ADD FULLTEXT( `title`, `publisher`, `author_name` )"
|
||||||
|
|
||||||
|
end
|
|
@ -22,6 +22,21 @@ class ActiveSupport::TestCase
|
||||||
self.use_transactional_fixtures = true
|
self.use_transactional_fixtures = true
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
def assertion(name, &block)
|
||||||
|
mc = class << self ; self ; end
|
||||||
|
mc.class_eval do
|
||||||
|
define_method(name) do
|
||||||
|
it(name, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def macro(name, &block)
|
||||||
|
class_eval do
|
||||||
|
define_method(name, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def describe(description, toplevel=nil, &blk)
|
def describe(description, toplevel=nil, &blk)
|
||||||
text = toplevel ? description : "#{name} #{description}"
|
text = toplevel ? description : "#{name} #{description}"
|
||||||
klass = Class.new(self)
|
klass = Class.new(self)
|
||||||
|
|
Loading…
Reference in a new issue