diff options
Diffstat (limited to 'guides/source/association_basics.md')
-rw-r--r-- | guides/source/association_basics.md | 139 |
1 files changed, 90 insertions, 49 deletions
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 860a1e1cba..a60ce7fb32 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -1,4 +1,4 @@ -**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.** +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.** Active Record Associations ========================== @@ -96,7 +96,7 @@ end ![belongs_to Association Diagram](images/association_basics/belongs_to.png) -NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `author` association in the `Book` model, you would be told that there was an "uninitialized constant Book::Authors". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too. +NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `author` association in the `Book` model and tried to create the instance by `Book.create(authors: @author)`, you would be told that there was an "uninitialized constant Book::Authors". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too. The corresponding migration might look like this: @@ -109,7 +109,7 @@ class CreateBooks < ActiveRecord::Migration[5.0] end create_table :books do |t| - t.belongs_to :author, index: true + t.belongs_to :author t.datetime :published_at t.timestamps end @@ -140,7 +140,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0] end create_table :accounts do |t| - t.belongs_to :supplier, index: true + t.belongs_to :supplier t.string :account_number t.timestamps end @@ -184,7 +184,7 @@ class CreateAuthors < ActiveRecord::Migration[5.0] end create_table :books do |t| - t.belongs_to :author, index: true + t.belongs_to :author t.datetime :published_at t.timestamps end @@ -231,8 +231,8 @@ class CreateAppointments < ActiveRecord::Migration[5.0] end create_table :appointments do |t| - t.belongs_to :physician, index: true - t.belongs_to :patient, index: true + t.belongs_to :physician + t.belongs_to :patient t.datetime :appointment_date t.timestamps end @@ -312,13 +312,13 @@ class CreateAccountHistories < ActiveRecord::Migration[5.0] end create_table :accounts do |t| - t.belongs_to :supplier, index: true + t.belongs_to :supplier t.string :account_number t.timestamps end create_table :account_histories do |t| - t.belongs_to :account, index: true + t.belongs_to :account t.integer :credit_rating t.timestamps end @@ -358,8 +358,8 @@ class CreateAssembliesAndParts < ActiveRecord::Migration[5.0] end create_table :assemblies_parts, id: false do |t| - t.belongs_to :assembly, index: true - t.belongs_to :part, index: true + t.belongs_to :assembly + t.belongs_to :part end end end @@ -384,7 +384,7 @@ end The corresponding migration might look like this: ```ruby -class CreateSuppliers < ActiveRecord::Migration[5.0] +class CreateSuppliers < ActiveRecord::Migration[5.2] def change create_table :suppliers do |t| t.string :name @@ -392,7 +392,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0] end create_table :accounts do |t| - t.integer :supplier_id + t.bigint :supplier_id t.string :account_number t.timestamps end @@ -402,7 +402,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0] end ``` -NOTE: Using `t.integer :supplier_id` makes the foreign key naming obvious and explicit. In current versions of Rails, you can abstract away this implementation detail by using `t.references :supplier` instead. +NOTE: Using `t.bigint :supplier_id` makes the foreign key naming obvious and explicit. In current versions of Rails, you can abstract away this implementation detail by using `t.references :supplier` instead. ### Choosing Between `has_many :through` and `has_and_belongs_to_many` @@ -439,7 +439,7 @@ end The simplest rule of thumb is that you should set up a `has_many :through` relationship if you need to work with the relationship model as an independent entity. If you don't need to do anything with the relationship model, it may be simpler to set up a `has_and_belongs_to_many` relationship (though you'll need to remember to create the joining table in the database). -You should use `has_many :through` if you need validations, callbacks or extra attributes on the join model. +You should use `has_many :through` if you need validations, callbacks, or extra attributes on the join model. ### Polymorphic Associations @@ -466,11 +466,11 @@ Similarly, you can retrieve `@product.pictures`. If you have an instance of the `Picture` model, you can get to its parent via `@picture.imageable`. To make this work, you need to declare both a foreign key column and a type column in the model that declares the polymorphic interface: ```ruby -class CreatePictures < ActiveRecord::Migration[5.0] +class CreatePictures < ActiveRecord::Migration[5.2] def change create_table :pictures do |t| t.string :name - t.integer :imageable_id + t.bigint :imageable_id t.string :imageable_type t.timestamps end @@ -487,7 +487,7 @@ class CreatePictures < ActiveRecord::Migration[5.0] def change create_table :pictures do |t| t.string :name - t.references :imageable, polymorphic: true, index: true + t.references :imageable, polymorphic: true t.timestamps end end @@ -505,7 +505,7 @@ class Employee < ApplicationRecord has_many :subordinates, class_name: "Employee", foreign_key: "manager_id" - belongs_to :manager, class_name: "Employee" + belongs_to :manager, class_name: "Employee", optional: true end ``` @@ -517,7 +517,7 @@ In your migrations/schema, you will add a references column to the model itself. class CreateEmployees < ActiveRecord::Migration[5.0] def change create_table :employees do |t| - t.references :manager, index: true + t.references :manager t.timestamps end end @@ -600,7 +600,7 @@ NOTE: If you wish to [enforce referential integrity at the database level](/acti #### Creating Join Tables for `has_and_belongs_to_many` Associations -If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical book of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering. +If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering. WARNING: The precedence between model names is calculated using the `<=>` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings). @@ -619,11 +619,11 @@ end These need to be backed up by a migration to create the `assemblies_parts` table. This table should be created without a primary key: ```ruby -class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0] +class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.2] def change create_table :assemblies_parts, id: false do |t| - t.integer :assembly_id - t.integer :part_id + t.bigint :assembly_id + t.bigint :part_id end add_index :assemblies_parts, :assembly_id @@ -868,7 +868,7 @@ While Rails uses intelligent defaults that will work well in most situations, th ```ruby class Book < ApplicationRecord - belongs_to :author, dependent: :destroy, + belongs_to :author, touch: :books_updated_at, counter_cache: true end ``` @@ -1048,8 +1048,7 @@ There may be times when you wish to customize the query used by `belongs_to`. Su ```ruby class Book < ApplicationRecord - belongs_to :author, -> { where active: true }, - dependent: :destroy + belongs_to :author, -> { where active: true } end ``` @@ -1075,13 +1074,13 @@ end You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models: ```ruby -class LineItem < ApplicationRecord +class Chapter < ApplicationRecord belongs_to :book end class Book < ApplicationRecord belongs_to :author - has_many :line_items + has_many :chapters end class Author < ApplicationRecord @@ -1089,16 +1088,16 @@ class Author < ApplicationRecord end ``` -If you frequently retrieve authors directly from line items (`@line_item.book.author`), then you can make your code somewhat more efficient by including authors in the association from line items to books: +If you frequently retrieve authors directly from chapters (`@chapter.book.author`), then you can make your code somewhat more efficient by including authors in the association from chapters to books: ```ruby -class LineItem < ApplicationRecord +class Chapter < ApplicationRecord belongs_to :book, -> { includes :author } end class Book < ApplicationRecord belongs_to :author - has_many :line_items + has_many :chapters end class Author < ApplicationRecord @@ -1258,8 +1257,8 @@ Controls what happens to the associated object when its owner is destroyed: * `:destroy` causes the associated object to also be destroyed * `:delete` causes the associated object to be deleted directly from the database (so callbacks will not execute) -* `:nullify` causes the foreign key to be set to `NULL`. Callbacks are not executed. -* `:restrict_with_exception` causes an exception to be raised if there is an associated record +* `:nullify` causes the foreign key to be set to `NULL`. Polymorphic type column is also nullified on polymorphic associations. Callbacks are not executed. +* `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there is an associated record * `:restrict_with_error` causes an error to be added to the owner if there is an associated object It's necessary not to set or leave `:nullify` option for those associations @@ -1306,6 +1305,21 @@ The `:source` option specifies the source association name for a `has_one :throu The `:source_type` option specifies the source association type for a `has_one :through` association that proceeds through a polymorphic association. +```ruby +class Book < ApplicationRecord + has_one :format, polymorphic: true + has_one :dust_jacket, through: :format, source: :dust_jacket, source_type: "Hardback" +end + +class Paperback < ApplicationRecord; end + +class Hardback < ApplicationRecord + has_one :dust_jacket +end + +class DustJacket < ApplicationRecord; end +``` + ##### `:through` The `:through` option specifies a join model through which to perform the query. `has_one :through` associations were discussed in detail [earlier in this guide](#the-has-one-through-association). @@ -1545,7 +1559,7 @@ The `collection.size` method returns the number of objects in the collection. ##### `collection.find(...)` The `collection.find` method finds objects within the collection. It uses the same syntax and options as -[`ActiveRecord::Base.find`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). +[`ActiveRecord::Base.find`](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). ```ruby @available_book = @author.books.find(1) @@ -1564,7 +1578,7 @@ The `collection.where` method finds objects within the collection based on the c The `collection.exists?` method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as -[`ActiveRecord::Base.exists?`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). +[`ActiveRecord::Base.exists?`](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). ##### `collection.build(attributes = {}, ...)` @@ -1659,10 +1673,12 @@ Controls what happens to the associated objects when their owner is destroyed: * `:destroy` causes all the associated objects to also be destroyed * `:delete_all` causes all the associated objects to be deleted directly from the database (so callbacks will not execute) -* `:nullify` causes the foreign keys to be set to `NULL`. Callbacks are not executed. -* `:restrict_with_exception` causes an exception to be raised if there are any associated records +* `:nullify` causes the foreign key to be set to `NULL`. Polymorphic type column is also nullified on polymorphic associations. Callbacks are not executed. +* `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there are any associated records * `:restrict_with_error` causes an error to be added to the owner if there are any associated objects +The `:destroy` and `:delete_all` options also affect the semantics of the `collection.delete` and `collection=` methods by causing them to destroy associated objects when they are removed from the collection. + ##### `:foreign_key` By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: @@ -1716,6 +1732,20 @@ The `:source` option specifies the source association name for a `has_many :thro The `:source_type` option specifies the source association type for a `has_many :through` association that proceeds through a polymorphic association. +```ruby +class Author < ApplicationRecord + has_many :books + has_many :paperbacks, through: :books, source: :format, source_type: "Paperback" +end + +class Book < ApplicationRecord + has_one :format, polymorphic: true +end + +class Hardback < ApplicationRecord; end +class Paperback < ApplicationRecord; end +``` + ##### `:through` The `:through` option specifies a join model through which to perform the query. `has_many :through` associations provide a way to implement many-to-many relationships, as discussed [earlier in this guide](#the-has-many-through-association). @@ -1779,8 +1809,8 @@ The `group` method supplies an attribute name to group the result set by, using ```ruby class Author < ApplicationRecord - has_many :line_items, -> { group 'books.id' }, - through: :books + has_many :chapters, -> { group 'books.id' }, + through: :books end ``` @@ -1795,27 +1825,27 @@ end class Book < ApplicationRecord belongs_to :author - has_many :line_items + has_many :chapters end -class LineItem < ApplicationRecord +class Chapter < ApplicationRecord belongs_to :book end ``` -If you frequently retrieve line items directly from authors (`@author.books.line_items`), then you can make your code somewhat more efficient by including line items in the association from authors to books: +If you frequently retrieve chapters directly from authors (`@author.books.chapters`), then you can make your code somewhat more efficient by including chapters in the association from authors to books: ```ruby class Author < ApplicationRecord - has_many :books, -> { includes :line_items } + has_many :books, -> { includes :chapters } end class Book < ApplicationRecord belongs_to :author - has_many :line_items + has_many :chapters end -class LineItem < ApplicationRecord +class Chapter < ApplicationRecord belongs_to :book end ``` @@ -2076,7 +2106,7 @@ The `collection.size` method returns the number of objects in the collection. ##### `collection.find(...)` The `collection.find` method finds objects within the collection. It uses the same syntax and options as -[`ActiveRecord::Base.find`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). +[`ActiveRecord::Base.find`](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). ```ruby @assembly = @part.assemblies.find(1) @@ -2094,7 +2124,7 @@ The `collection.where` method finds objects within the collection based on the c The `collection.exists?` method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as -[`ActiveRecord::Base.exists?`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). +[`ActiveRecord::Base.exists?`](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). ##### `collection.build(attributes = {})` @@ -2349,6 +2379,17 @@ end If a `before_add` callback throws an exception, the object does not get added to the collection. Similarly, if a `before_remove` callback throws an exception, the object does not get removed from the collection. +NOTE: These callbacks are called only when the associated objects are added or removed through the association collection: + +```ruby +# Triggers `before_add` callback +author.books << book +author.books = [book, book2] + +# Does not trigger the `before_add` callback +book.update(author_id: 1) +``` + ### Association Extensions You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example: @@ -2391,7 +2432,7 @@ Single Table Inheritance ------------------------ Sometimes, you may want to share fields and behavior between different models. -Let's say we have Car, Motorcycle and Bicycle models. We will want to share +Let's say we have Car, Motorcycle, and Bicycle models. We will want to share the `color` and `price` fields and some methods for all of them, but having some specific behavior for each, and separated controllers too. |