fk: add docs

This commit is contained in:
Yves Senn 2014-06-12 08:42:00 +02:00
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 @@ def remove_reference(table_name, ref_name, options = {})
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 @@ def add_foreign_key(from_table, to_table, options = {})
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
------------------------