Rails6 のちょい足しな新機能を試す99（association extension編）

# はじめに

Rails 6 に追加された新機能を試す第99段。 今回は、 `association extension` 編です。
Rails 6 では、 `define_extensions` で定義される module が model 内の module として定義されるようになっています。
実際の例を見た方がわかりやすいです。

Ruby 2.6.5, Rails 6.0.0 で確認しました。

```shell
$ rails --version
Rails 6.0.0
```

今回は、 Author モデル と Book モデルを定義して、rails console で確認します。
Author は Book を複数持ちます( `has_many` )。

# Rails プロジェクトを作る

```
$ rails new rails_sandbox
cd rails_sandbox
```

# Author モデルを作る

`name` を持つ `Author` モデルを作ります。

```shell
$ bin/rails g model Author name
```

# Book モデルを作る

`title`, `published`, `author_id` を持つ `Book` モデルを作ります。

```shell
$ bin/rails g model Book title published:boolean author:references
```

# Author モデルを修正する

Author モデルを修正します。
2つの `has_many` アソシエーションを定義します。
1つ目の `has_many` では、 ブロックで `ordered` メソッドを定義します。
2つ目の scope `published_books` は `extending` を使って `ordered` メソッドを利用できるようにします。
extending の引数が `BooksAssociationExtension` になっていることに注意してください。

```ruby:app/models/author.rb
class Author < ApplicationRecord
  has_many :books do
    def ordered
      order(:title)
    end
  end

  has_many :published_books, -> { where(published: true).extending(BooksAssociationExtension) }, class_name: 'Book'
end
```

# seed データを作成する

Author と Book の seed データを作成します。

```ruby:db/seeds.rb
author = Author.create(
  {
    name: 'Dave Thomas'
  }
)

Book.create(
  [
    { title: 'Programming Ruby', author: author, published: true },
    { title: 'Pragmatic Programmer', author: author, published: true },
    { title: 'Agile Web Development with Rails 6', author: author, published: false }
  ]
)
```

# データベースを作成し、 seed データを登録する

```shell
$ bin/rails db:create db:migrate db:seed
```

# rails console で確認する

rails console で確認します。

まずは、1つ目の `has_many` を確認します。


```irb
irb(main):001:0> Author.first.books.ordered
  Author Load (0.3ms)  SELECT "authors".* FROM "authors" ORDER BY "authors"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Book Load (0.3ms)  SELECT "books".* FROM "books" WHERE "books"."author_id" = $1 ORDER BY "books"."title" ASC LIMIT $2  [["author_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Book id: 3, title: "Agile Web Development with Rails 6", published: false, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">, #<Book id: 2, title: "Pragmatic Programmer", published: true, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">, #<Book id: 1, title: "Programming Ruby", published: true, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">]>
```

2つ目の `has_many` を確認します。

```irb
irb(main):002:0> Author.first.published_books.ordered
  Author Load (0.8ms)  SELECT "authors".* FROM "authors" ORDER BY "authors"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Book Load (0.8ms)  SELECT "books".* FROM "books" WHERE "books"."author_id" = $1 AND "books"."published" = $2 ORDER BY "books"."title" ASC LIMIT $3  [["author_id", 1], ["published", true], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Book id: 2, title: "Pragmatic Programmer", published: true, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">, #<Book id: 1, title: "Programming Ruby", published: true, author_id: 1, created_at: "2019-10-12 09:09:58", updated_at: "2019-10-12 09:09:58">]>
```

`Author::BooksAssociationExtension` が定義されていることを確認します。


```irb
irb(main):003:0> Author::BooksAssociationExtension
=> Author::BooksAssociationExtension
```

# Rails 5 では

Rails 5.2.3 では、 `Author::BooksAssociationExtension` ではなく、 `AuthorBooksAssociationExtension` が定義されます。
以下のように `extending` の引数を `AuthorBooksAssociationExtension` と書く必要があります。

```ruby:app/models/author.rb
class Author < ApplicationRecord
  ...
  has_many :published_books, -> { where(published: true).extending(AuthorBooksAssociationExtension) }, class_name: 'Book'
end
```

Rails 5.2.3 ではグローバルなモジュールとして定義されるのに対し、Rails 6 では、 Author モデルの中のモジュールとして定義されるので、 Author モデルで利用するときは、Author をつけずに、 `BooksAssociationExtension` と簡潔に書くことができるようになっています。

# 試したソース

試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try099_association_extension

# 参考情報

* [Namespace association extension modules under the owner model](https://github.com/rails/rails/commit/d46a70892ff85b4d6b12b9ae5a6102ae07bb3dbb)
* [Association extensions](https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Association+extensions)