aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG.md13
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb36
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb14
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb28
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb30
-rw-r--r--activerecord/test/cases/migration/helper.rb2
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb111
8 files changed, 226 insertions, 40 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a965fe0494..32261ba9e6 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,18 @@
## Rails 4.0.0 (unreleased) ##
+* Add `add_reference` and `remove_reference` schema statements. Aliases, `add_belongs_to`
+ and `remove_belongs_to` are acceptable. References are reversible.
+ Examples:
+
+ # Create a user_id column
+ add_reference(:products, :user)
+ # Create a supplier_id, supplier_type columns and appropriate index
+ add_reference(:products, :supplier, polymorphic: true, index: true)
+ # Remove polymorphic reference
+ remove_reference(:products, :supplier, polymorphic: true)
+
+ *Aleksey Magusev*
+
* Add `:default` and `:null` options to `column_exists?`.
column_exists?(:testings, :taggable_id, :integer, null: false)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index df78ba6c5a..ef17dfbbc5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -259,7 +259,7 @@ module ActiveRecord
end # end
EOV
end
-
+
# Adds index options to the indexes hash, keyed by column name
# This is primarily used to track indexes that need to be created after the table
#
@@ -282,7 +282,7 @@ module ActiveRecord
index_options = options.delete(:index)
args.each do |col|
column("#{col}_id", :integer, options)
- column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
@@ -441,17 +441,13 @@ module ActiveRecord
# Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
#
- # t.references(:goat)
- # t.references(:goat, :polymorphic => true)
- # t.belongs_to(:goat)
+ # t.references(:user)
+ # t.belongs_to(:supplier, polymorphic: true)
+ #
def references(*args)
options = args.extract_options!
- polymorphic = options.delete(:polymorphic)
- index_options = options.delete(:index)
- args.each do |col|
- @base.add_column(@table_name, "#{col}_id", :integer, options)
- @base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
- @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ args.each do |ref_name|
+ @base.add_reference(@table_name, ref_name, options)
end
end
alias :belongs_to :references
@@ -459,18 +455,16 @@ module ActiveRecord
# Removes a reference. Optionally removes a +type+ column.
# <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
#
- # t.remove_references(:goat)
- # t.remove_references(:goat, :polymorphic => true)
- # t.remove_belongs_to(:goat)
+ # t.remove_references(:user)
+ # t.remove_belongs_to(:supplier, polymorphic: true)
+ #
def remove_references(*args)
options = args.extract_options!
- polymorphic = options.delete(:polymorphic)
- args.each do |col|
- @base.remove_column(@table_name, "#{col}_id")
- @base.remove_column(@table_name, "#{col}_type") unless polymorphic.nil?
+ args.each do |ref_name|
+ @base.remove_reference(@table_name, ref_name, options)
end
end
- alias :remove_belongs_to :remove_references
+ alias :remove_belongs_to :remove_references
# Adds a column or columns of a specified type
#
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 2b0ba2f479..65c7ef0153 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -441,6 +441,42 @@ module ActiveRecord
indexes(table_name).detect { |i| i.name == index_name }
end
+ # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
+ # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
+ #
+ # ====== Create a user_id column
+ # add_reference(:products, :user)
+ #
+ # ====== Create a supplier_id and supplier_type columns
+ # add_belongs_to(:products, :supplier, polymorphic: true)
+ #
+ # ====== Create a supplier_id, supplier_type columns and appropriate index
+ # add_reference(:products, :supplier, polymorphic: true, index: true)
+ #
+ def add_reference(table_name, ref_name, options = {})
+ polymorphic = options.delete(:polymorphic)
+ index_options = options.delete(:index)
+ add_column(table_name, "#{ref_name}_id", :integer, options)
+ add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
+ add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ end
+ alias :add_belongs_to :add_reference
+
+ # Removes the reference(s). Also removes a +type+ column if one exists.
+ # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
+ #
+ # ====== Remove the reference
+ # remove_reference(:products, :user, index: true)
+ #
+ # ====== Remove polymorphic reference
+ # remove_reference(:products, :supplier, polymorphic: true)
+ #
+ def remove_reference(table_name, ref_name, options = {})
+ remove_column(table_name, "#{ref_name}_id")
+ remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
+ end
+ alias :remove_belongs_to :remove_reference
+
# Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
# entire structure of the database.
def structure_dump
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 96b62fdd61..95f4360578 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -51,13 +51,15 @@ module ActiveRecord
super || delegate.respond_to?(*args)
end
- [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
+ [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default, :add_reference, :remove_reference].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args) # def create_table(*args)
record(:"#{method}", args) # record(:create_table, args)
end # end
EOV
end
+ alias :add_belongs_to :add_reference
+ alias :remove_belongs_to :remove_reference
private
@@ -102,6 +104,16 @@ module ActiveRecord
[:remove_timestamps, args]
end
+ def invert_add_reference(args)
+ [:remove_reference, args]
+ end
+ alias :invert_add_belongs_to :invert_add_reference
+
+ def invert_remove_reference(args)
+ [:add_reference, args]
+ end
+ alias :invert_remove_belongs_to :invert_remove_reference
+
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
@delegate.send(method, *args, &block)
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 063209389f..4614be9650 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -30,61 +30,57 @@ module ActiveRecord
def test_references_column_type_adds_id
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ @connection.expect :add_reference, nil, [:delete_me, :customer, {}]
t.references :customer
end
end
def test_remove_references_column_type_removes_id
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ @connection.expect :remove_reference, nil, [:delete_me, :customer, {}]
t.remove_references :customer
end
end
def test_add_belongs_to_works_like_add_references
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ @connection.expect :add_reference, nil, [:delete_me, :customer, {}]
t.belongs_to :customer
end
end
def test_remove_belongs_to_works_like_remove_references
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ @connection.expect :remove_reference, nil, [:delete_me, :customer, {}]
t.remove_belongs_to :customer
end
end
def test_references_column_type_with_polymorphic_adds_type
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {}]
- @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {}]
- t.references :taggable, :polymorphic => true
+ @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true]
+ t.references :taggable, polymorphic: true
end
end
def test_remove_references_column_type_with_polymorphic_removes_type
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
- @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
- t.remove_references :taggable, :polymorphic => true
+ @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true]
+ t.remove_references :taggable, polymorphic: true
end
end
def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {:null => false}]
- @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {:null => false}]
- t.references :taggable, :polymorphic => true, :null => false
+ @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false]
+ t.references :taggable, polymorphic: true, null: false
end
end
def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
- @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
- t.remove_references :taggable, :polymorphic => true, :null => false
+ @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false]
+ t.remove_references :taggable, polymorphic: true, null: false
end
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 7d026961be..f2213ee6aa 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -110,9 +110,9 @@ module ActiveRecord
end
def test_invert_add_index_with_name
- @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}]
- remove = @recorder.inverse.first
- assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove
+ @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove
end
def test_invert_add_index_with_no_options
@@ -138,6 +138,30 @@ module ActiveRecord
add = @recorder.inverse.first
assert_equal [:add_timestamps, [:table]], add
end
+
+ def test_invert_add_reference
+ @recorder.record :add_reference, [:table, :taggable, { polymorphic: true }]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }]], remove
+ end
+
+ def test_invert_add_belongs_to_alias
+ @recorder.record :add_belongs_to, [:table, :user]
+ remove = @recorder.inverse.first
+ assert_equal [:remove_reference, [:table, :user]], remove
+ end
+
+ def test_invert_remove_reference
+ @recorder.record :remove_reference, [:table, :taggable, { polymorphic: true }]
+ add = @recorder.inverse.first
+ assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }]], add
+ end
+
+ def test_invert_remove_belongs_to_alias
+ @recorder.record :remove_belongs_to, [:table, :user]
+ add = @recorder.inverse.first
+ assert_equal [:add_reference, [:table, :user]], add
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
index 20f26786f0..768ebc5861 100644
--- a/activerecord/test/cases/migration/helper.rb
+++ b/activerecord/test/cases/migration/helper.rb
@@ -14,7 +14,7 @@ module ActiveRecord
module TestHelper
attr_reader :connection, :table_name
- CONNECTION_METHODS = %w[add_column remove_column rename_column add_index change_column rename_table]
+ CONNECTION_METHODS = %w[add_column remove_column rename_column add_index change_column rename_table column_exists? index_exists? add_reference add_belongs_to remove_reference remove_references remove_belongs_to]
class TestModel < ActiveRecord::Base
self.table_name = :test_models
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
new file mode 100644
index 0000000000..144302bd4a
--- /dev/null
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -0,0 +1,111 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class ReferencesStatementsTest < ActiveRecord::TestCase
+ include ActiveRecord::Migration::TestHelper
+
+ self.use_transactional_fixtures = false
+
+ def setup
+ super
+ @table_name = :test_models
+
+ add_column table_name, :supplier_id, :integer
+ add_index table_name, :supplier_id
+ end
+
+ def test_creates_reference_id_column
+ add_reference table_name, :user
+ assert column_exists?(table_name, :user_id, :integer)
+ end
+
+ def test_does_not_create_reference_type_column
+ add_reference table_name, :taggable
+ refute column_exists?(table_name, :taggable_type, :string)
+ end
+
+ def test_creates_reference_type_column
+ add_reference table_name, :taggable, polymorphic: true
+ assert column_exists?(table_name, :taggable_type, :string)
+ end
+
+ def test_creates_reference_id_index
+ add_reference table_name, :user, index: true
+ assert index_exists?(table_name, :user_id)
+ end
+
+ def test_does_not_create_reference_id_index
+ add_reference table_name, :user
+ refute index_exists?(table_name, :user_id)
+ end
+
+ def test_creates_polymorphic_index
+ add_reference table_name, :taggable, polymorphic: true, index: true
+ assert index_exists?(table_name, [:taggable_id, :taggable_type])
+ end
+
+ def test_creates_reference_type_column_with_default
+ add_reference table_name, :taggable, polymorphic: { default: 'Photo' }, index: true
+ assert column_exists?(table_name, :taggable_type, :string, default: 'Photo')
+ end
+
+ def test_creates_named_index
+ add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id' }
+ assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id')
+ end
+
+ def test_deletes_reference_id_column
+ remove_reference table_name, :supplier
+ refute column_exists?(table_name, :supplier_id, :integer)
+ end
+
+ def test_deletes_reference_id_index
+ remove_reference table_name, :supplier
+ refute index_exists?(table_name, :supplier_id)
+ end
+
+ def test_does_not_delete_reference_type_column
+ with_polymorphic_column do
+ remove_reference table_name, :supplier
+
+ refute column_exists?(table_name, :supplier_id, :integer)
+ assert column_exists?(table_name, :supplier_type, :string)
+ end
+ end
+
+ def test_deletes_reference_type_column
+ with_polymorphic_column do
+ remove_reference table_name, :supplier, polymorphic: true
+ refute column_exists?(table_name, :supplier_type, :string)
+ end
+ end
+
+ def test_deletes_polymorphic_index
+ with_polymorphic_column do
+ remove_reference table_name, :supplier, polymorphic: true
+ refute index_exists?(table_name, [:supplier_id, :supplier_type])
+ end
+ end
+
+ def test_add_belongs_to_alias
+ add_belongs_to table_name, :user
+ assert column_exists?(table_name, :user_id, :integer)
+ end
+
+ def test_remove_belongs_to_alias
+ remove_belongs_to table_name, :supplier
+ refute column_exists?(table_name, :supplier_id, :integer)
+ end
+
+ private
+
+ def with_polymorphic_column
+ add_column table_name, :supplier_type, :string
+ add_index table_name, [:supplier_id, :supplier_type]
+
+ yield
+ end
+ end
+ end
+end