Skip to content

Commit

Permalink
[RF-DOCS]Active Record Associations Guide [ci-skip] (rails#52523)
Browse files Browse the repository at this point in the history
* The images used to show the relationships of the different types of
  associations have both the table declarations, as well as a code sample, but
  the code sample is also in the guide. We could probably simplify the images by
  cutting off the code samples from them and leaving them in the guide code.

* belongs_to section mentions bi-directional association and links to it within
  the same guide, but doesn't do the same for has_one and has_many, mentioned
  right after in the same paragraph.

* There's a note about using bigint :supplier_id in the section between
  belongs_to and has_one, but up until that point it's always been using
  belongs_to in migrations... it's probably something that doesn't need to be in
  this area of the guide.

* The has_many :through vs has_and_belongs_to_many section ends with a
  composite keys mention, we could link to the new guide there.

* Associations between models with composite PKs may be simplified.

* One example in bi-directional associations mentions Book belongs_to
  :writer, but then uses book.author a few lines below, which is incorrect.

* STI section could be expanded, it's very useful in certain situations.

* Add more info and make it clear

* Group sections better

* Amend the References section to be integrated with associations 

* Abstract and remove duplications for options and scopes

Co-authored-by: Petrik de Heus <[email protected]>
Co-authored-by: Rizwan Reza <[email protected]>
Co-authored-by: Matheus Richard <[email protected]>
Co-authored-by: Mike Stroming <[email protected]>
Co-authored-by: Harriet Oughton <[email protected]>
  • Loading branch information
6 people authored Sep 4, 2024
1 parent dd3f743 commit 69c86f9
Show file tree
Hide file tree
Showing 10 changed files with 2,297 additions and 2,057 deletions.
Binary file modified guides/assets/images/association_basics/belongs_to.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified guides/assets/images/association_basics/habtm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified guides/assets/images/association_basics/has_many.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified guides/assets/images/association_basics/has_many_through.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified guides/assets/images/association_basics/has_one.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified guides/assets/images/association_basics/has_one_through.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified guides/assets/images/association_basics/polymorphic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 41 additions & 16 deletions guides/source/active_record_composite_primary_keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,14 @@ guide to learn more.
Associations between Models with Composite Primary Keys
-------------------------------------------------------

Rails is often able to infer the primary key - foreign key information between
associated models with composite primary keys without needing extra information.
Take the following example:
Rails can often infer the primary key-foreign key relationships between
associated models. However, when dealing with composite primary keys, Rails
typically defaults to using only part of the composite key, usually the `id`
column, unless explicitly instructed otherwise. This default behavior only works
if the model's composite primary key contains the `:id` column, _and_ the column
is unique for all records.

Consider the following example:

```ruby
class Order < ApplicationRecord
Expand All @@ -138,29 +143,40 @@ class Book < ApplicationRecord
end
```

Here, Rails assumes that the `:id` column should be used as the primary key for
the association between an order and its books, just as with a regular
`has_many` / `belongs_to` association. It will infer that the foreign key column
on the `books` table is `:order_id`. Accessing a book's order:
In this setup, `Order` has a composite primary key consisting of `[:shop_id,
:id]`, and `Book` belongs to `Order`. Rails will assume that the `:id` column
should be used as the primary key for the association between an order and its
books. It will infer that the foreign key column on the books table is
`:order_id`.

Below we create an `Order` and a `Book` associated with it:

```ruby
order = Order.create!(id: [1, 2], status: "pending")
book = order.books.create!(title: "A Cool Book")
```

To access the book's order, we reload the association:

```ruby
book.reload.order
```

will generate the following SQL to access the order:
When doing so, Rails will generate the following SQL to access the order:

```sql
SELECT * FROM orders WHERE id = 2
```

This only works if the model's composite primary key contains the `:id` column,
_and_ the column is unique for all records. In order to use the full composite
primary key in associations, set the `foreign_key:` option on the
association. This option specifies a composite foreign key on the association,
meaning that all columns in the foreign key will be used to query the
You can see that Rails uses the order's `id` in its query, rather than both the
`shop_id` and the `id`. In this case, the `id` is sufficient because the model's
composite primary key does in fact contain the `:id` column, _and_ the column is
unique for all records.

However, if the above requirements are not met or you would like to use the full
composite primary key in associations, you can set the `foreign_key:` option on
the association. This option specifies a composite foreign key on the
association; all columns in the foreign key will be used when querying the
associated record(s). For example:

```ruby
Expand All @@ -174,16 +190,25 @@ class Book < ApplicationRecord
end
```

Accessing a book's author:
In this setup, `Author` has a composite primary key consisting of `[:first_name,
:last_name]`, and `Book` belongs to `Author` with a composite foreign key
`[:author_first_name, :author_last_name]`.

Create an `Author` and a `Book` associated with it:

```ruby
author = Author.create!(first_name: "Jane", last_name: "Doe")
book = author.books.create!(title: "A Cool Book")
book = author.books.create!(title: "A Cool Book", author_first_name: "Jane", author_last_name: "Doe")
```

To access the book's author, we reload the association:

```ruby
book.reload.author
```

will use `:first_name` _and_ `:last_name` in the SQL query:
Rails will now use the `:first_name` _and_ `:last_name` from the composite
primary key in the SQL query:

```sql
SELECT * FROM authors WHERE first_name = 'Jane' AND last_name = 'Doe'
Expand Down
12 changes: 11 additions & 1 deletion guides/source/active_record_migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,9 +559,19 @@ create_join_table :products, :categories, column_options: { null: true }
```

By default, the name of the join table comes from the union of the first two
arguments provided to create_join_table, in alphabetical order. In this case,
arguments provided to create_join_table, in lexical order. In this case,
the table would be named `categories_products`.

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).

To customize the name of the table, provide a `:table_name` option:

```ruby
Expand Down
Loading

0 comments on commit 69c86f9

Please sign in to comment.