mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
fk: add docs
This commit is contained in:
parent
24e1aefb4b
commit
a5b3f372ab
4 changed files with 165 additions and 57 deletions
|
@ -1,3 +1,15 @@
|
|||
* Support for adding and removing foreign keys. Foreign keys are now
|
||||
a part of `schema.rb`. This is supported by Mysql2Adapter, MysqlAdapter
|
||||
and PostgreSQLAdapter.
|
||||
|
||||
Example:
|
||||
|
||||
# within your migrations:
|
||||
add_foreign_key :articles, :authors
|
||||
remove_foreign_key :articles, :authors
|
||||
|
||||
*Yves Senn*
|
||||
|
||||
* Fix subtle bugs regarding attribute assignment on models with no primary
|
||||
key. `'id'` will no longer be part of the attributes hash.
|
||||
|
||||
|
|
|
@ -642,10 +642,54 @@ module ActiveRecord
|
|||
end
|
||||
alias :remove_belongs_to :remove_reference
|
||||
|
||||
# Returns an array of foreign keys for the given table.
|
||||
# The foreign keys are represented as +ForeignKeyDefinition+ objects.
|
||||
def foreign_keys(table_name)
|
||||
raise NotImplementedError, "foreign_keys is not implemented"
|
||||
end
|
||||
|
||||
# Adds a new foreign key. +from_table+ is the table with the key column,
|
||||
# +to_table+ contains the referenced primary key.
|
||||
#
|
||||
# The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
|
||||
# +identifier+ is a 10 character long random string. A custom name can be specified with
|
||||
# the <tt>:name</tt> option.
|
||||
#
|
||||
# ====== Creating a simple foreign key
|
||||
#
|
||||
# add_foreign_key :articles, :authors
|
||||
#
|
||||
# generates:
|
||||
#
|
||||
# ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
|
||||
#
|
||||
# ====== Creating a foreign key on a specific column
|
||||
#
|
||||
# add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
|
||||
#
|
||||
# generates:
|
||||
#
|
||||
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
|
||||
#
|
||||
# ====== Creating a cascading foreign key
|
||||
#
|
||||
# add_foreign_key :articles, :authors, on_delete: :cascade
|
||||
#
|
||||
# generates:
|
||||
#
|
||||
# ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
|
||||
#
|
||||
# The +options+ hash can include the following keys:
|
||||
# [<tt>:column</tt>]
|
||||
# The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
|
||||
# [<tt>:primary_key</tt>]
|
||||
# The primary key column name on +to_table+. Defaults to +id+.
|
||||
# [<tt>:name</tt>]
|
||||
# The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
|
||||
# [<tt>:on_delete</tt>]
|
||||
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
|
||||
# [<tt>:on_update</tt>]
|
||||
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
|
||||
def add_foreign_key(from_table, to_table, options = {})
|
||||
return unless supports_foreign_keys?
|
||||
|
||||
|
@ -664,6 +708,20 @@ module ActiveRecord
|
|||
execute schema_creation.accept(at)
|
||||
end
|
||||
|
||||
# Removes the given foreign key from the table.
|
||||
#
|
||||
# Removes the foreign key on +accounts.branch_id+.
|
||||
#
|
||||
# remove_foreign_key :accounts, :branches
|
||||
#
|
||||
# Removes the foreign key on +accounts.owner_id+.
|
||||
#
|
||||
# remove_foreign_key :accounts, column: :owner_id
|
||||
#
|
||||
# Removes the foreign key named +special_fk_name+ on the +accounts+ table.
|
||||
#
|
||||
# remove_foreign_key :accounts, name: :special_fk_name
|
||||
#
|
||||
def remove_foreign_key(from_table, options_or_to_table = {})
|
||||
return unless supports_foreign_keys?
|
||||
|
||||
|
|
|
@ -25,6 +25,31 @@ guide.
|
|||
Major Features
|
||||
--------------
|
||||
|
||||
### Foreign key support
|
||||
|
||||
The migration DSL now supports adding and removing foreign keys. They are dumped
|
||||
to `schema.rb` as well. At this time, only the `mysql`, `mysql2` and `postgresql`
|
||||
adapters support foreign keys.
|
||||
|
||||
```ruby
|
||||
# add a foreign key to `articles.author_id` referencing `authors.id`
|
||||
add_foreign_key :articles, :authors
|
||||
|
||||
# add a foreign key to `articles.author_id` referencing `users.lng_id`
|
||||
add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
|
||||
|
||||
# remove the foreign key on `accounts.branch_id`
|
||||
remove_foreign_key :accounts, :branches
|
||||
|
||||
# remove the foreign key on `accounts.owner_id`
|
||||
remove_foreign_key :accounts, column: :owner_id
|
||||
```
|
||||
|
||||
See the API documentation on
|
||||
[add_foreign_key](http://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key)
|
||||
and
|
||||
[remove_foreign_key](http://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_foreign_key)
|
||||
for a full description.
|
||||
|
||||
|
||||
Railties
|
||||
|
|
|
@ -452,6 +452,40 @@ Column modifiers can be applied when creating or changing a column:
|
|||
Some adapters may support additional options; see the adapter specific API docs
|
||||
for further information.
|
||||
|
||||
### Foreign Keys
|
||||
|
||||
While it's not required you might want to add foreign key constraints to
|
||||
[guarantee referential integrity](#active-record-and-referential-integrity).
|
||||
|
||||
```ruby
|
||||
add_foreign_key :articles, :authors
|
||||
```
|
||||
|
||||
This adds a new foreign key to the `author_id` column of the `articles`
|
||||
table. The key references the `id` column of the `articles` table. If the
|
||||
column names can not be derived from the table names, you can use the
|
||||
`:column` and `:primary_key` options.
|
||||
|
||||
Rails will generate a name for every foreign key starting with
|
||||
`fk_rails_` followed by 10 random characters.
|
||||
There is a `:name` option to specify a different name if needed.
|
||||
|
||||
NOTE: Active Record only supports single column foreign keys. `execute` and
|
||||
`structure.sql` are required to use composite foreign keys.
|
||||
|
||||
Removing a foreign key is easy as well:
|
||||
|
||||
```ruby
|
||||
# let Active Record figure out the column name
|
||||
remove_foreign_key :accounts, :branches
|
||||
|
||||
# remove foreign key for a specific column
|
||||
remove_foreign_key :accounts, column: :owner_id
|
||||
|
||||
# remove foreign key by name
|
||||
remove_foreign_key :accounts, name: :special_fk_name
|
||||
```
|
||||
|
||||
### When Helpers aren't Enough
|
||||
|
||||
If the helpers provided by Active Record aren't enough you can use the `execute`
|
||||
|
@ -482,6 +516,7 @@ definitions:
|
|||
* `add_index`
|
||||
* `add_reference`
|
||||
* `add_timestamps`
|
||||
* `add_foreign_key`
|
||||
* `create_table`
|
||||
* `create_join_table`
|
||||
* `drop_table` (must supply a block)
|
||||
|
@ -507,24 +542,23 @@ migration what else to do when reverting it. For example:
|
|||
```ruby
|
||||
class ExampleMigration < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :products do |t|
|
||||
t.references :category
|
||||
create_table :distributors do |t|
|
||||
t.string :zipcode
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
#add a foreign key
|
||||
# add a CHECK constraint
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
ADD CONSTRAINT fk_products_categories
|
||||
FOREIGN KEY (category_id)
|
||||
REFERENCES categories(id)
|
||||
ALTER TABLE distributors
|
||||
ADD CONSTRAINT zipchk
|
||||
CHECK (char_length(zipcode) = 5) NO INHERIT;
|
||||
SQL
|
||||
end
|
||||
dir.down do
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
DROP FOREIGN KEY fk_products_categories
|
||||
ALTER TABLE distributors
|
||||
DROP CONSTRAINT zipchk
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
@ -538,7 +572,7 @@ end
|
|||
Using `reversible` will ensure that the instructions are executed in the
|
||||
right order too. If the previous example migration is reverted,
|
||||
the `down` block will be run after the `home_page_url` column is removed and
|
||||
right before the table `products` is dropped.
|
||||
right before the table `distributors` is dropped.
|
||||
|
||||
Sometimes your migration will do something which is just plain irreversible; for
|
||||
example, it might destroy some data. In such cases, you can raise
|
||||
|
@ -561,16 +595,15 @@ made in the `up` method. The example in the `reversible` section is equivalent t
|
|||
```ruby
|
||||
class ExampleMigration < ActiveRecord::Migration
|
||||
def up
|
||||
create_table :products do |t|
|
||||
t.references :category
|
||||
create_table :distributors do |t|
|
||||
t.string :zipcode
|
||||
end
|
||||
|
||||
# add a foreign key
|
||||
# add a CHECK constraint
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
ADD CONSTRAINT fk_products_categories
|
||||
FOREIGN KEY (category_id)
|
||||
REFERENCES categories(id)
|
||||
ALTER TABLE distributors
|
||||
ADD CONSTRAINT zipchk
|
||||
CHECK (char_length(zipcode) = 5);
|
||||
SQL
|
||||
|
||||
add_column :users, :home_page_url, :string
|
||||
|
@ -582,11 +615,11 @@ class ExampleMigration < ActiveRecord::Migration
|
|||
remove_column :users, :home_page_url
|
||||
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
DROP FOREIGN KEY fk_products_categories
|
||||
ALTER TABLE distributors
|
||||
DROP CONSTRAINT zipchk
|
||||
SQL
|
||||
|
||||
drop_table :products
|
||||
drop_table :distributors
|
||||
end
|
||||
end
|
||||
```
|
||||
|
@ -617,43 +650,27 @@ end
|
|||
The `revert` method also accepts a block of instructions to reverse.
|
||||
This could be useful to revert selected parts of previous migrations.
|
||||
For example, let's imagine that `ExampleMigration` is committed and it
|
||||
is later decided it would be best to serialize the product list instead.
|
||||
One could write:
|
||||
is later decided it would be best to use Active Record validations,
|
||||
in place of the `CHECK` constraint, to verify the zipcode.
|
||||
|
||||
```ruby
|
||||
class SerializeProductListMigration < ActiveRecord::Migration
|
||||
class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :categories, :product_list
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
# transfer data from Products to Category#product_list
|
||||
end
|
||||
dir.down do
|
||||
# create Products from Category#product_list
|
||||
end
|
||||
end
|
||||
|
||||
revert do
|
||||
# copy-pasted code from ExampleMigration
|
||||
create_table :products do |t|
|
||||
t.references :category
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
#add a foreign key
|
||||
# add a CHECK constraint
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
ADD CONSTRAINT fk_products_categories
|
||||
FOREIGN KEY (category_id)
|
||||
REFERENCES categories(id)
|
||||
ALTER TABLE distributors
|
||||
ADD CONSTRAINT zipchk
|
||||
CHECK (char_length(zipcode) = 5);
|
||||
SQL
|
||||
end
|
||||
dir.down do
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
DROP FOREIGN KEY fk_products_categories
|
||||
ALTER TABLE distributors
|
||||
DROP CONSTRAINT zipchk
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
@ -918,10 +935,10 @@ that Active Record supports. This could be very useful if you were to
|
|||
distribute an application that is able to run against multiple databases.
|
||||
|
||||
There is however a trade-off: `db/schema.rb` cannot express database specific
|
||||
items such as foreign key constraints, triggers, or stored procedures. While in
|
||||
a migration you can execute custom SQL statements, the schema dumper cannot
|
||||
reconstitute those statements from the database. If you are using features like
|
||||
this, then you should set the schema format to `:sql`.
|
||||
items such as triggers, or stored procedures. While in a migration you can
|
||||
execute custom SQL statements, the schema dumper cannot reconstitute those
|
||||
statements from the database. If you are using features like this, then you
|
||||
should set the schema format to `:sql`.
|
||||
|
||||
Instead of using Active Record's schema dumper, the database's structure will
|
||||
be dumped using a tool specific to the database (via the `db:structure:dump`
|
||||
|
@ -948,7 +965,7 @@ Active Record and Referential Integrity
|
|||
---------------------------------------
|
||||
|
||||
The Active Record way claims that intelligence belongs in your models, not in
|
||||
the database. As such, features such as triggers or foreign key constraints,
|
||||
the database. As such, features such as triggers or constraints,
|
||||
which push some of that intelligence back into the database, are not heavily
|
||||
used.
|
||||
|
||||
|
@ -957,14 +974,10 @@ which models can enforce data integrity. The `:dependent` option on
|
|||
associations allows models to automatically destroy child objects when the
|
||||
parent is destroyed. Like anything which operates at the application level,
|
||||
these cannot guarantee referential integrity and so some people augment them
|
||||
with foreign key constraints in the database.
|
||||
with [foreign key constraints](#foreign-keys) in the database.
|
||||
|
||||
Although Active Record does not provide any tools for working directly with
|
||||
such features, the `execute` method can be used to execute arbitrary SQL. You
|
||||
can also use a gem like
|
||||
[foreigner](https://github.com/matthuhiggins/foreigner) which adds foreign key
|
||||
support to Active Record (including support for dumping foreign keys in
|
||||
`db/schema.rb`).
|
||||
Although Active Record does not provide all the tools for working directly with
|
||||
such features, the `execute` method can be used to execute arbitrary SQL.
|
||||
|
||||
Migrations and Seed Data
|
||||
------------------------
|
||||
|
|
Loading…
Reference in a new issue