提交 6a054b00 编写于 作者: J Jeremy Kemper

Merge pull request #5262 from joshuap/references_index

Automatically create indexes for references/belongs_to statements in migrations.
## Rails 4.0.0 (unreleased) ##
* Added an :index option to automatically create indexes for references
and belongs_to statements in migrations.
The `references` and `belongs_to` methods now support an `index`
option that receives either a boolean value or an options hash
that is identical to options available to the add_index method:
create_table :messages do |t|
t.references :person, :index => true
end
Is the same as:
create_table :messages do |t|
t.references :person
end
add_index :messages, :person_id
Generators have also been updated to use the new syntax.
[Joshua Wood]
* Added bang methods for mutating `ActiveRecord::Relation` objects.
For example, while `foo.where(:bar)` will return a new object
leaving `foo` unchanged, `foo.where!(:bar)` will mutate the foo
......
......@@ -65,11 +65,12 @@ def add_column_options!(sql, options)
class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :columns
attr_accessor :columns, :indexes
def initialize(base)
@columns = []
@columns_hash = {}
@indexes = {}
@base = base
end
......@@ -212,19 +213,22 @@ def [](name)
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
# options, these will be used when creating the <tt>_type</tt> column. So what can be written like this:
# options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option
# will also create an index, similar to calling <tt>add_index</tt>. So what can be written like this:
#
# create_table :taggings do |t|
# t.integer :tag_id, :tagger_id, :taggable_id
# t.string :tagger_type
# t.string :taggable_type, :default => 'Photo'
# end
# add_index :taggings, :tag_id, :name => 'index_taggings_on_tag_id'
# add_index :taggings, [:tagger_id, :tagger_type]
#
# Can also be written as follows using references:
#
# create_table :taggings do |t|
# t.references :tag
# t.references :tagger, :polymorphic => true
# t.references :tag, :index => { :name => 'index_taggings_on_tag_id' }
# t.references :tagger, :polymorphic => true, :index => true
# t.references :taggable, :polymorphic => { :default => 'Photo' }
# end
def column(name, type, options = {})
......@@ -255,6 +259,14 @@ def #{column_type}(*args) # def string(*args)
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
# === Examples
# index(:account_id, :name => 'index_projects_on_account_id')
def index(column_name, options = {})
indexes[column_name] = options
end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table.
......@@ -267,9 +279,11 @@ def timestamps(*args)
def references(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
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?
index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
alias :belongs_to :references
......@@ -435,9 +449,11 @@ def rename(column_name, new_column_name)
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
end
end
alias :belongs_to :references
......
......@@ -171,6 +171,7 @@ def create_table(table_name, options = {})
create_sql << td.to_sql
create_sql << ") #{options[:options]}"
execute create_sql
td.indexes.each_pair { |c,o| add_index table_name, c, o }
end
# Creates a new join table with the name created using the lexical order of the first two
......
......@@ -14,6 +14,7 @@ class ModelGenerator < Base
def create_migration_file
return unless options[:migration] && options[:parent].nil?
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
migration_template "migration.rb", "db/migrate/create_#{table_name}.rb"
end
......@@ -27,7 +28,7 @@ def create_module_file
end
def attributes_with_index
attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) }
attributes.select { |a| !a.reference? && a.has_index? }
end
def accessible_attributes
......
require 'cases/helper'
module ActiveRecord
class Migration
class ReferencesIndexTest < ActiveRecord::TestCase
attr_reader :connection, :table_name
def setup
super
@connection = ActiveRecord::Base.connection
@table_name = :testings
end
def teardown
super
connection.drop_table :testings rescue nil
end
def test_creates_index
connection.create_table table_name do |t|
t.references :foo, :index => true
end
assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_does_not_create_index
connection.create_table table_name do |t|
t.references :foo
end
refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_does_not_create_index_explicit
connection.create_table table_name do |t|
t.references :foo, :index => false
end
refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_creates_index_with_options
connection.create_table table_name do |t|
t.references :foo, :index => {:name => :index_testings_on_yo_momma}
t.references :bar, :index => {:unique => true}
end
assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma)
assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true)
end
def test_creates_polymorphic_index
connection.create_table table_name do |t|
t.references :foo, :polymorphic => true, :index => true
end
assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type)
end
def test_creates_index_for_existing_table
connection.create_table table_name
connection.change_table table_name do |t|
t.references :foo, :index => true
end
assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_does_not_create_index_for_existing_table
connection.create_table table_name
connection.change_table table_name do |t|
t.references :foo
end
refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_does_not_create_index_for_existing_table_explicit
connection.create_table table_name
connection.change_table table_name do |t|
t.references :foo, :index => false
end
refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_creates_polymorphic_index_for_existing_table
connection.create_table table_name
connection.change_table table_name do |t|
t.references :foo, :polymorphic => true, :index => true
end
assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type)
end
end
end
end
......@@ -404,6 +404,7 @@ def test_drop_index_from_table_named_values
class ChangeTableMigrationsTest < ActiveRecord::TestCase
def setup
@connection = Person.connection
@connection.stubs(:add_index)
@connection.create_table :delete_me, :force => true do |t|
end
end
......
......@@ -475,7 +475,16 @@ end
</ruby>
will add an +attachment_id+ column and a string +attachment_type+ column with
a default value of 'Photo'.
a default value of 'Photo'. +references+ also allows you to define an
index directly, instead of using +add_index+ after the +create_table+ call:
<ruby>
create_table :products do |t|
t.references :category, :index => true
end
</ruby>
will create an index identical to calling `add_index :products, :category_id`.
NOTE: The +references+ helper does not actually create foreign key constraints
for you. You will need to use +execute+ or a plugin that adds "foreign key
......
......@@ -21,6 +21,10 @@ def parse(column_definition)
has_index, type = type, nil if INDEX_OPTIONS.include?(type)
type, attr_options = *parse_type_and_options(type)
references_index = (type.in?(%w(references belongs_to)) and UNIQ_INDEX_OPTIONS.include?(has_index) ? {:unique => true} : true)
attr_options.merge!({:index => references_index}) if references_index
new(name, type, has_index, attr_options)
end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册