aboutsummaryrefslogtreecommitdiffstats
path: root/guides/source/association_basics.md
diff options
context:
space:
mode:
Diffstat (limited to 'guides/source/association_basics.md')
-rw-r--r--guides/source/association_basics.md143
1 files changed, 89 insertions, 54 deletions
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 9b80a65a44..c9e0fcd939 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -40,7 +40,7 @@ end
@customer.destroy
```
-With Active Record associations, we can streamline these — and other — operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders:
+With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders:
```ruby
class Customer < ActiveRecord::Base
@@ -69,7 +69,7 @@ To learn more about the different types of associations, read the next section o
The Types of Associations
-------------------------
-In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain Primary Key–Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of associations:
+In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain Primary Key-Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of associations:
* `belongs_to`
* `has_one`
@@ -105,7 +105,7 @@ class CreateOrders < ActiveRecord::Migration
end
create_table :orders do |t|
- t.belongs_to :customer
+ t.belongs_to :customer, index: true
t.datetime :order_date
t.timestamps
end
@@ -136,7 +136,7 @@ class CreateSuppliers < ActiveRecord::Migration
end
create_table :accounts do |t|
- t.belongs_to :supplier
+ t.belongs_to :supplier, index: true
t.string :account_number
t.timestamps
end
@@ -169,7 +169,7 @@ class CreateCustomers < ActiveRecord::Migration
end
create_table :orders do |t|
- t.belongs_to :customer
+ t.belongs_to :customer, index:true
t.datetime :order_date
t.timestamps
end
@@ -216,8 +216,8 @@ class CreateAppointments < ActiveRecord::Migration
end
create_table :appointments do |t|
- t.belongs_to :physician
- t.belongs_to :patient
+ t.belongs_to :physician, index: true
+ t.belongs_to :patient, index: true
t.datetime :appointment_date
t.timestamps
end
@@ -261,7 +261,10 @@ With `through: :sections` specified, Rails will now understand:
### The `has_one :through` Association
-A `has_one :through` association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this:
+A `has_one :through` association sets up a one-to-one connection with another model. This association indicates
+that the declaring model can be matched with one instance of another model by proceeding _through_ a third model.
+For example, if each supplier has one account, and each account is associated with one account history, then the
+supplier model could look like this:
```ruby
class Supplier < ActiveRecord::Base
@@ -292,13 +295,13 @@ class CreateAccountHistories < ActiveRecord::Migration
end
create_table :accounts do |t|
- t.belongs_to :supplier
+ t.belongs_to :supplier, index: true
t.string :account_number
t.timestamps
end
create_table :account_histories do |t|
- t.belongs_to :account
+ t.belongs_to :account, index: true
t.integer :credit_rating
t.timestamps
end
@@ -337,9 +340,9 @@ class CreateAssembliesAndParts < ActiveRecord::Migration
t.timestamps
end
- create_table :assemblies_parts do |t|
- t.belongs_to :assembly
- t.belongs_to :part
+ create_table :assemblies_parts, id: false do |t|
+ t.belongs_to :assembly, index: true
+ t.belongs_to :part, index: true
end
end
end
@@ -376,6 +379,8 @@ class CreateSuppliers < ActiveRecord::Migration
t.string :account_number
t.timestamps
end
+
+ add_index :accounts, :supplier_id
end
end
```
@@ -452,6 +457,8 @@ class CreatePictures < ActiveRecord::Migration
t.string :imageable_type
t.timestamps
end
+
+ add_index :pictures, :imageable_id
end
end
```
@@ -463,7 +470,7 @@ class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
- t.references :imageable, polymorphic: true
+ t.references :imageable, polymorphic: true, index: true
t.timestamps
end
end
@@ -487,6 +494,19 @@ end
With this setup, you can retrieve `@employee.subordinates` and `@employee.manager`.
+In your migrations/schema, you will add a references column to the model itself.
+
+```ruby
+class CreateEmployees < ActiveRecord::Migration
+ def change
+ create_table :employees do |t|
+ t.references :manager, index: true
+ t.timestamps
+ end
+ end
+end
+```
+
Tips, Tricks, and Warnings
--------------------------
@@ -545,6 +565,8 @@ class CreateOrders < ActiveRecord::Migration
t.string :order_number
t.integer :customer_id
end
+
+ add_index :orders, :customer_id
end
end
```
@@ -555,7 +577,7 @@ If you create an association some time after you build the underlying model, you
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 customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" 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).
+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).
Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations:
@@ -578,6 +600,9 @@ class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
t.integer :assembly_id
t.integer :part_id
end
+
+ add_index :assemblies_parts, :assembly_id
+ add_index :assemblies_parts, :part_id
end
end
```
@@ -715,7 +740,7 @@ The `belongs_to` association creates a one-to-one match with another model. In d
#### Methods Added by `belongs_to`
-When you declare a `belongs_to` association, the declaring class automatically gains four methods related to the association:
+When you declare a `belongs_to` association, the declaring class automatically gains five methods related to the association:
* `association(force_reload = false)`
* `association=(associate)`
@@ -861,8 +886,12 @@ end
Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
##### `:dependent`
+If you set the `:dependent` option to:
-If you set the `:dependent` option to `:destroy`, then deleting this object will call the `destroy` method on the associated object to delete that object. If you set the `:dependent` option to `:delete`, then deleting this object will delete the associated object _without_ calling its `destroy` method. If you set the `:dependent` option to `:restrict`, then attempting to delete this object will result in a `ActiveRecord::DeleteRestrictionError` if there are any associated objects.
+* `:destroy`, when the object is destroyed, `destroy` will be called on its
+associated objects.
+* `:delete`, when the object is destroyed, all its associated objects will be
+deleted directly from the database without calling their `destroy` method.
WARNING: You should not specify this option on a `belongs_to` association that is connected with a `has_many` association on the other class. Doing so can lead to orphaned records in your database.
@@ -1019,7 +1048,7 @@ The `has_one` association creates a one-to-one match with another model. In data
#### Methods Added by `has_one`
-When you declare a `has_one` association, the declaring class automatically gains four methods related to the association:
+When you declare a `has_one` association, the declaring class automatically gains five methods related to the association:
* `association(force_reload = false)`
* `association=(associate)`
@@ -1111,7 +1140,7 @@ The `has_one` association supports these options:
##### `:as`
-Setting the `:as` option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>.
+Setting the `:as` option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail [earlier in this guide](#polymorphic-associations).
##### `:autosave`
@@ -1137,6 +1166,12 @@ Controls what happens to the associated object when its owner is destroyed:
* `:restrict_with_exception` causes an 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
+that have `NOT NULL` database constraints. If you don't set `dependent` to
+destroy such associations you won't be able to change the associated object
+because initial associated object foreign key will be set to unallowed `NULL`
+value.
+
##### `: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:
@@ -1177,7 +1212,7 @@ The `:source_type` option specifies the source association type for a `has_one :
##### `:through`
-The `:through` option specifies a join model through which to perform the query. `has_one :through` associations were discussed in detail <a href="#the-has-one-through-association">earlier in this guide</a>.
+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).
##### `:validate`
@@ -1280,15 +1315,15 @@ The `has_many` association creates a one-to-many relationship with another model
#### Methods Added by `has_many`
-When you declare a `has_many` association, the declaring class automatically gains 13 methods related to the association:
+When you declare a `has_many` association, the declaring class automatically gains 16 methods related to the association:
* `collection(force_reload = false)`
* `collection<<(object, ...)`
* `collection.delete(object, ...)`
* `collection.destroy(object, ...)`
-* `collection=objects`
+* `collection=(objects)`
* `collection_singular_ids`
-* `collection_singular_ids=ids`
+* `collection_singular_ids=(ids)`
* `collection.clear`
* `collection.empty?`
* `collection.size`
@@ -1364,7 +1399,7 @@ The `collection.destroy` method removes one or more objects from the collection
WARNING: Objects will _always_ be removed from the database, ignoring the `:dependent` option.
-##### `collection=objects`
+##### `collection=(objects)`
The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate.
@@ -1376,7 +1411,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects
@order_ids = @customer.order_ids
```
-##### `collection_singular_ids=ids`
+##### `collection_singular_ids=(ids)`
The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate.
@@ -1471,7 +1506,7 @@ The `has_many` association supports these options:
##### `:as`
-Setting the `:as` option indicates that this is a polymorphic association, as discussed <a href="#polymorphic-associations">earlier in this guide</a>.
+Setting the `:as` option indicates that this is a polymorphic association, as discussed [earlier in this guide](#polymorphic-associations).
##### `:autosave`
@@ -1553,7 +1588,7 @@ The `:source_type` option specifies the source association type for a `has_many
##### `: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 <a href="#the-has-many-through-association">earlier in this guide</a>.
+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).
##### `:validate`
@@ -1606,7 +1641,7 @@ If you use a hash-style `where` option, then record creation via this associatio
##### `extending`
-The `extending` method specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>.
+The `extending` method specifies a named module to extend the association proxy. Association extensions are discussed in detail [later in this guide](#association-extensions).
##### `group`
@@ -1699,58 +1734,58 @@ mostly useful together with the `:through` option.
```ruby
class Person < ActiveRecord::Base
has_many :readings
- has_many :posts, through: :readings
+ has_many :articles, through: :readings
end
person = Person.create(name: 'John')
-post = Post.create(name: 'a1')
-person.posts << post
-person.posts << post
-person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]
-Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]
+article = Article.create(name: 'a1')
+person.articles << article
+person.articles << article
+person.articles.inspect # => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">]
+Reading.all.inspect # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]
```
-In the above case there are two readings and `person.posts` brings out both of
-them even though these records are pointing to the same post.
+In the above case there are two readings and `person.articles` brings out both of
+them even though these records are pointing to the same article.
Now let's set `distinct`:
```ruby
class Person
has_many :readings
- has_many :posts, -> { distinct }, through: :readings
+ has_many :articles, -> { distinct }, through: :readings
end
person = Person.create(name: 'Honda')
-post = Post.create(name: 'a1')
-person.posts << post
-person.posts << post
-person.posts.inspect # => [#<Post id: 7, name: "a1">]
-Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]
+article = Article.create(name: 'a1')
+person.articles << article
+person.articles << article
+person.articles.inspect # => [#<Article id: 7, name: "a1">]
+Reading.all.inspect # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]
```
-In the above case there are still two readings. However `person.posts` shows
-only one post because the collection loads only unique records.
+In the above case there are still two readings. However `person.articles` shows
+only one article because the collection loads only unique records.
If you want to make sure that, upon insertion, all of the records in the
persisted association are distinct (so that you can be sure that when you
inspect the association that you will never find duplicate records), you should
add a unique index on the table itself. For example, if you have a table named
-`person_posts` and you want to make sure all the posts are unique, you could
+`person_articles` and you want to make sure all the articles are unique, you could
add the following in a migration:
```ruby
-add_index :person_posts, :post, unique: true
+add_index :person_articles, :article, unique: true
```
Note that checking for uniqueness using something like `include?` is subject
to race conditions. Do not attempt to use `include?` to enforce distinctness
-in an association. For instance, using the post example from above, the
+in an association. For instance, using the article example from above, the
following code would be racy because multiple users could be attempting this
at the same time:
```ruby
-person.posts << post unless person.posts.include?(post)
+person.articles << article unless person.articles.include?(article)
```
#### When are Objects Saved?
@@ -1769,15 +1804,15 @@ The `has_and_belongs_to_many` association creates a many-to-many relationship wi
#### Methods Added by `has_and_belongs_to_many`
-When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 13 methods related to the association:
+When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 16 methods related to the association:
* `collection(force_reload = false)`
* `collection<<(object, ...)`
* `collection.delete(object, ...)`
* `collection.destroy(object, ...)`
-* `collection=objects`
+* `collection=(objects)`
* `collection_singular_ids`
-* `collection_singular_ids=ids`
+* `collection_singular_ids=(ids)`
* `collection.clear`
* `collection.empty?`
* `collection.size`
@@ -1860,7 +1895,7 @@ The `collection.destroy` method removes one or more objects from the collection
@part.assemblies.destroy(@assembly1)
```
-##### `collection=objects`
+##### `collection=(objects)`
The `collection=` method makes the collection contain only the supplied objects, by adding and deleting as appropriate.
@@ -1872,7 +1907,7 @@ The `collection_singular_ids` method returns an array of the ids of the objects
@assembly_ids = @part.assembly_ids
```
-##### `collection_singular_ids=ids`
+##### `collection_singular_ids=(ids)`
The `collection_singular_ids=` method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate.
@@ -2056,7 +2091,7 @@ If you use a hash-style `where`, then record creation via this association will
##### `extending`
-The `extending` method specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>.
+The `extending` method specifies a named module to extend the association proxy. Association extensions are discussed in detail [later in this guide](#association-extensions).
##### `group`