aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md11
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb10
-rw-r--r--activerecord/lib/active_record/core.rb8
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb5
-rw-r--r--activerecord/lib/active_record/transactions.rb12
-rw-r--r--activerecord/test/cases/adapters/mysql/charset_collation_test.rb54
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb54
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb2
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb14
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb14
17 files changed, 203 insertions, 48 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 780c34147d..6312960819 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,14 @@
+* MySQL: `:charset` and `:collation` support for string and text columns.
+
+ Example:
+
+ create_table :foos do |t|
+ t.string :string_utf8_bin, charset: 'utf8', collation: 'utf8_bin'
+ t.text :text_ascii, charset: 'ascii'
+ end
+
+ *Ryuta Kamizono*
+
* Foreign key related methods in the migration DSL respect
`ActiveRecord::Base.pluralize_table_names = false`.
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 88531205a1..6caadb4ce8 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -370,6 +370,8 @@ module ActiveRecord
replace_common_records_in_memory(other_array, original_target)
if other_array != original_target
transaction { replace_records(other_array, original_target) }
+ else
+ other_array
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 4897ec44e9..29e8a0edc1 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -135,7 +135,7 @@ module ActiveRecord
if scope.klass.primary_key
count = scope.destroy_all.length
else
- scope.each(&:_run_destroy_callbacks)
+ scope.each { |record| record.run_callbacks :destroy }
arel = scope.arel
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index f44e5af5de..2fcba8e309 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -289,25 +289,24 @@ module ActiveRecord
end
def destroy #:nodoc:
- _run_destroy_callbacks { super }
+ run_callbacks(:destroy) { super }
end
def touch(*) #:nodoc:
- _run_touch_callbacks { super }
+ run_callbacks(:touch) { super }
end
private
-
def create_or_update(*) #:nodoc:
- _run_save_callbacks { super }
+ run_callbacks(:save) { super }
end
def _create_record #:nodoc:
- _run_create_callbacks { super }
+ run_callbacks(:create) { super }
end
def _update_record(*) #:nodoc:
- _run_update_callbacks { super }
+ run_callbacks(:update) { super }
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index d99dc9a5db..8c50f3d1a3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -358,7 +358,7 @@ module ActiveRecord
synchronize do
owner = conn.owner
- conn._run_checkin_callbacks do
+ conn.run_callbacks :checkin do
conn.expire
end
@@ -449,7 +449,7 @@ module ActiveRecord
end
def checkout_and_verify(c)
- c._run_checkout_callbacks do
+ c.run_callbacks :checkout do
c.verify!
end
c
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index c1b4c936be..ab1b098e53 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -377,6 +377,9 @@ module ActiveRecord
# [<tt>:force</tt>]
# Set to +:cascade+ to drop dependent objects as well.
# Defaults to false.
+ # [<tt>:if_exists</tt>]
+ # Set to +true+ to only drop the table if it exists.
+ # Defaults to false.
#
# Although this command ignores most +options+ and the block if one is given,
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 51c41cd588..76aee452ca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -13,6 +13,10 @@ module ActiveRecord
end
end
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
+ attr_accessor :charset, :collation
+ end
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
@@ -23,8 +27,16 @@ module ActiveRecord
column.type = :integer
column.auto_increment = true
end
+ column.charset = options[:charset]
+ column.collation = options[:collation]
column
end
+
+ private
+
+ def create_column_definition(name, type)
+ ColumnDefinition.new(name, type)
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
@@ -60,6 +72,23 @@ module ActiveRecord
add_column_position!(change_column_sql, column_options(o.column))
end
+ def column_options(o)
+ column_options = super
+ column_options[:charset] = o.charset
+ column_options[:collation] = o.collation
+ column_options
+ end
+
+ def add_column_options!(sql, options)
+ if options[:charset]
+ sql << " CHARACTER SET #{options[:charset]}"
+ end
+ if options[:collation]
+ sql << " COLLATE #{options[:collation]}"
+ end
+ super
+ end
+
def add_column_position!(sql, options)
if options[:first]
sql << " FIRST"
@@ -99,9 +128,18 @@ module ActiveRecord
spec = super
spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
spec.delete(:limit) if :boolean === column.type
+ if column.collation && table_name = column.instance_variable_get(:@table_name)
+ @collation_cache ||= {}
+ @collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
+ spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name]
+ end
spec
end
+ def migration_keys
+ super + [:collation]
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
@@ -568,11 +606,15 @@ module ActiveRecord
# Set to +:cascade+ to drop dependent objects as well.
# Defaults to false.
# [<tt>:if_exists</tt>]
- # Set to +true+ to make drop table command fail safe when table does not exists.
+ # Set to +true+ to only drop the table if it exists.
# Defaults to false.
# [<tt>:temporary</tt>]
# Set to +true+ to drop temporary table.
# Defaults to false.
+ #
+ # Although this command ignores most +options+ and the block if one is given,
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
+ # In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index a67127bd71..f4dda5154e 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -28,6 +28,7 @@ module ActiveRecord
@null = null
@default = default
@default_function = default_function
+ @table_name = nil
end
def has_default?
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 652ae1ed63..168180cfd3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -87,15 +87,7 @@ module ActiveRecord
SQL
end
- # Drops a table from the database.
- #
- # [<tt>:force</tt>]
- # Set to +:cascade+ to drop dependent objects as well.
- # Defaults to false.
- # [<tt>:if_exists</tt>]
- # Set to +true+ to make drop table command fail safe when table does not exists.
- # Defaults to false.
- def drop_table(table_name, options = {})
+ def drop_table(table_name, options = {}) # :nodoc:
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 9b7cba08de..1ad910c4bc 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -302,7 +302,7 @@ module ActiveRecord
assign_attributes(attributes) if attributes
yield self if block_given?
- _run_initialize_callbacks
+ run_callbacks :initialize
end
# Initialize an empty model object from +coder+. +coder+ should be
@@ -329,8 +329,8 @@ module ActiveRecord
self.class.define_attribute_methods
- _run_find_callbacks
- _run_initialize_callbacks
+ run_callbacks :find
+ run_callbacks :initialize
self
end
@@ -366,7 +366,7 @@ module ActiveRecord
@attributes = @attributes.dup
@attributes.reset(self.class.primary_key)
- _run_initialize_callbacks
+ run_callbacks(:initialize)
@new_record = true
@destroyed = false
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index da95920571..eaeaf0321b 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -105,7 +105,10 @@ HEADER
end
def table(table, stream)
- columns = @connection.columns(table)
+ columns = @connection.columns(table).map do |column|
+ column.instance_variable_set(:@table_name, table)
+ column
+ end
begin
tbl = StringIO.new
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 2293d1b258..311dacb449 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -319,8 +319,8 @@ module ActiveRecord
end
def before_committed! # :nodoc:
- _run_before_commit_without_transaction_enrollment_callbacks
- _run_before_commit_callbacks
+ run_callbacks :before_commit_without_transaction_enrollment
+ run_callbacks :before_commit
end
# Call the +after_commit+ callbacks.
@@ -329,8 +329,8 @@ module ActiveRecord
# but call it after the commit of a destroyed object.
def committed!(should_run_callbacks: true) #:nodoc:
if should_run_callbacks && destroyed? || persisted?
- _run_commit_without_transaction_enrollment_callbacks
- _run_commit_callbacks
+ run_callbacks :commit_without_transaction_enrollment
+ run_callbacks :commit
end
ensure
force_clear_transaction_record_state
@@ -340,8 +340,8 @@ module ActiveRecord
# state should be rolled back to the beginning or just to the last savepoint.
def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
if should_run_callbacks
- _run_rollback_without_transaction_enrollment_callbacks
- _run_rollback_callbacks
+ run_callbacks :rollback
+ run_callbacks :rollback_without_transaction_enrollment
end
ensure
restore_transaction_record_state(force_restore_state)
diff --git a/activerecord/test/cases/adapters/mysql/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql/charset_collation_test.rb
new file mode 100644
index 0000000000..71a3756a90
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/charset_collation_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class CharsetCollationTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_fixtures = false
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :charset_collations, force: true do |t|
+ t.string :string_ascii_bin, charset: 'ascii', collation: 'ascii_bin'
+ t.text :text_ucs2_unicode_ci, charset: 'ucs2', collation: 'ucs2_unicode_ci'
+ end
+ end
+
+ teardown do
+ @connection.drop_table :charset_collations, if_exists: true
+ end
+
+ test "string column with charset and collation" do
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' }
+ assert_equal :string, column.type
+ assert_equal 'ascii_bin', column.collation
+ end
+
+ test "text column with charset and collation" do
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'text_ucs2_unicode_ci' }
+ assert_equal :text, column.type
+ assert_equal 'ucs2_unicode_ci', column.collation
+ end
+
+ test "add column with charset and collation" do
+ @connection.add_column :charset_collations, :title, :string, charset: 'utf8', collation: 'utf8_bin'
+
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'utf8_bin', column.collation
+ end
+
+ test "change column with charset and collation" do
+ @connection.add_column :charset_collations, :description, :string, charset: 'utf8', collation: 'utf8_unicode_ci'
+ @connection.change_column :charset_collations, :description, :text, charset: 'utf8', collation: 'utf8_general_ci'
+
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'utf8_general_ci', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("charset_collations")
+ assert_match %r{t.string\s+"string_ascii_bin",\s+limit: 255,\s+collation: "ascii_bin"$}, output
+ assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
new file mode 100644
index 0000000000..71a3756a90
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class CharsetCollationTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_fixtures = false
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :charset_collations, force: true do |t|
+ t.string :string_ascii_bin, charset: 'ascii', collation: 'ascii_bin'
+ t.text :text_ucs2_unicode_ci, charset: 'ucs2', collation: 'ucs2_unicode_ci'
+ end
+ end
+
+ teardown do
+ @connection.drop_table :charset_collations, if_exists: true
+ end
+
+ test "string column with charset and collation" do
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' }
+ assert_equal :string, column.type
+ assert_equal 'ascii_bin', column.collation
+ end
+
+ test "text column with charset and collation" do
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'text_ucs2_unicode_ci' }
+ assert_equal :text, column.type
+ assert_equal 'ucs2_unicode_ci', column.collation
+ end
+
+ test "add column with charset and collation" do
+ @connection.add_column :charset_collations, :title, :string, charset: 'utf8', collation: 'utf8_bin'
+
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'utf8_bin', column.collation
+ end
+
+ test "change column with charset and collation" do
+ @connection.add_column :charset_collations, :description, :string, charset: 'utf8', collation: 'utf8_unicode_ci'
+ @connection.change_column :charset_collations, :description, :text, charset: 'utf8', collation: 'utf8_general_ci'
+
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'utf8_general_ci', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("charset_collations")
+ assert_match %r{t.string\s+"string_ascii_bin",\s+limit: 255,\s+collation: "ascii_bin"$}, output
+ assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
+ end
+end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 290b2a0d6b..171cfbde44 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1503,6 +1503,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(0, ignore_none: true) do
firm.clients = []
end
+
+ assert_equal [], firm.send('clients=', [])
end
def test_transactions_when_replacing_on_persisted
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index d6fd0c4ab0..52d3290c84 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -24,6 +24,11 @@ ActiveRecord::Schema.define do
add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
+ create_table :collation_tests, id: false, force: true do |t|
+ t.string :string_cs_column, limit: 1, collation: 'utf8_bin'
+ t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci'
+ end
+
ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
@@ -35,15 +40,6 @@ BEGIN
END
SQL
- ActiveRecord::Base.connection.drop_table "collation_tests", if_exists: true
-
- ActiveRecord::Base.connection.execute <<-SQL
-CREATE TABLE collation_tests (
- string_cs_column VARCHAR(1) COLLATE utf8_bin,
- string_ci_column VARCHAR(1) COLLATE utf8_general_ci
-) CHARACTER SET utf8 COLLATE utf8_general_ci
-SQL
-
ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
ActiveRecord::Base.connection.execute <<-SQL
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index b5378341b5..90f5a60d7b 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -24,6 +24,11 @@ ActiveRecord::Schema.define do
add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
+ create_table :collation_tests, id: false, force: true do |t|
+ t.string :string_cs_column, limit: 1, collation: 'utf8_bin'
+ t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci'
+ end
+
ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
@@ -46,15 +51,6 @@ BEGIN
END
SQL
- ActiveRecord::Base.connection.drop_table "collation_tests", if_exists: true
-
- ActiveRecord::Base.connection.execute <<-SQL
-CREATE TABLE collation_tests (
- string_cs_column VARCHAR(1) COLLATE utf8_bin,
- string_ci_column VARCHAR(1) COLLATE utf8_general_ci
-) CHARACTER SET utf8 COLLATE utf8_general_ci
-SQL
-
ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
ActiveRecord::Base.connection.execute <<-SQL