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.md175
1 files changed, 102 insertions, 73 deletions
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 9867d2dc3f..dab885a8fb 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record Associations
==========================
@@ -101,13 +103,13 @@ class CreateOrders < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :orders do |t|
- t.belongs_to :customer
+ t.belongs_to :customer, index: true
t.datetime :order_date
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -132,13 +134,13 @@ class CreateSuppliers < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
- t.belongs_to :supplier
+ t.belongs_to :supplier, index: true
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -165,13 +167,13 @@ class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :orders do |t|
- t.belongs_to :customer
+ t.belongs_to :customer, index:true
t.datetime :order_date
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -207,19 +209,19 @@ class CreateAppointments < ActiveRecord::Migration
def change
create_table :physicians do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :patients do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
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
+ t.timestamps null: false
end
end
end
@@ -291,19 +293,19 @@ class CreateAccountHistories < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
- t.belongs_to :supplier
+ t.belongs_to :supplier, index: true
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
create_table :account_histories do |t|
- t.belongs_to :account
+ t.belongs_to :account, index: true
t.integer :credit_rating
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -332,17 +334,17 @@ class CreateAssembliesAndParts < ActiveRecord::Migration
def change
create_table :assemblies do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :parts do |t|
t.string :part_number
- t.timestamps
+ t.timestamps null: false
end
create_table :assemblies_parts, id: false do |t|
- t.belongs_to :assembly
- t.belongs_to :part
+ t.belongs_to :assembly, index: true
+ t.belongs_to :part, index: true
end
end
end
@@ -371,14 +373,16 @@ class CreateSuppliers < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
t.integer :supplier_id
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
+
+ add_index :accounts, :supplier_id
end
end
```
@@ -453,8 +457,10 @@ class CreatePictures < ActiveRecord::Migration
t.string :name
t.integer :imageable_id
t.string :imageable_type
- t.timestamps
+ t.timestamps null: false
end
+
+ add_index :pictures, :imageable_id
end
end
```
@@ -466,8 +472,8 @@ class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
- t.references :imageable, polymorphic: true
- t.timestamps
+ t.references :imageable, polymorphic: true, index: true
+ t.timestamps null: false
end
end
end
@@ -490,6 +496,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 null: false
+ end
+ end
+end
+```
+
Tips, Tricks, and Warnings
--------------------------
@@ -548,6 +567,8 @@ class CreateOrders < ActiveRecord::Migration
t.string :order_number
t.integer :customer_id
end
+
+ add_index :orders, :customer_id
end
end
```
@@ -558,7 +579,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:
@@ -581,6 +602,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
```
@@ -734,7 +758,7 @@ class Order < ActiveRecord::Base
end
```
-Each instance of the order model will have these methods:
+Each instance of the `Order` model will have these methods:
```ruby
customer
@@ -857,10 +881,12 @@ class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: :count_of_orders
end
class Customer < ActiveRecord::Base
- has_many :orders
+ has_many :orders, counter_cache: :count_of_orders
end
```
+NOTE: You only need to specify the :counter_cache option on the "has_many side" of the association when using a custom name for the counter cache.
+
Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
##### `:dependent`
@@ -1118,7 +1144,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`
@@ -1190,7 +1216,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`
@@ -1299,9 +1325,9 @@ When you declare a `has_many` association, the declaring class automatically gai
* `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`
@@ -1320,16 +1346,16 @@ class Customer < ActiveRecord::Base
end
```
-Each instance of the customer model will have these methods:
+Each instance of the `Customer` model will have these methods:
```ruby
orders(force_reload = false)
orders<<(object, ...)
orders.delete(object, ...)
orders.destroy(object, ...)
-orders=objects
+orders=(objects)
order_ids
-order_ids=ids
+order_ids=(ids)
orders.clear
orders.empty?
orders.size
@@ -1377,7 +1403,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.
@@ -1389,7 +1415,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.
@@ -1473,6 +1499,7 @@ The `has_many` association supports these options:
* `:as`
* `:autosave`
* `:class_name`
+* `:counter_cache`
* `:dependent`
* `:foreign_key`
* `:inverse_of`
@@ -1484,7 +1511,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`
@@ -1500,6 +1527,10 @@ class Customer < ActiveRecord::Base
end
```
+##### `:counter_cache`
+
+This option can be used to configure a custom named `:counter_cache`. You only need this option when you customized the name of your `:counter_cache` on the [belongs_to association](#options-for-belongs-to).
+
##### `:dependent`
Controls what happens to the associated objects when their owner is destroyed:
@@ -1510,8 +1541,6 @@ Controls what happens to the associated objects when their owner is destroyed:
* `:restrict_with_exception` causes an 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
-NOTE: This option is ignored when you use the `:through` option on the association.
-
##### `: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:
@@ -1566,7 +1595,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`
@@ -1619,7 +1648,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`
@@ -1712,58 +1741,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?
@@ -1788,9 +1817,9 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au
* `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`
@@ -1809,16 +1838,16 @@ class Part < ActiveRecord::Base
end
```
-Each instance of the part model will have these methods:
+Each instance of the `Part` model will have these methods:
```ruby
assemblies(force_reload = false)
assemblies<<(object, ...)
assemblies.delete(object, ...)
assemblies.destroy(object, ...)
-assemblies=objects
+assemblies=(objects)
assembly_ids
-assembly_ids=ids
+assembly_ids=(ids)
assemblies.clear
assemblies.empty?
assemblies.size
@@ -1873,7 +1902,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.
@@ -1885,7 +1914,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.
@@ -2069,7 +2098,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`