0
0

More than 3 years have passed since last update.

Rails6 のちょい足しな新機能を試す102(ActiveRecord Type Casting編)

Posted at

はじめに

Rails 6 に追加された新機能を試す第102段。 今回は、 ActiveRecord Type Casting編です。
Rails 6 では、 データベースの connection adapter の Type Casting のバグが修正されました。

Ruby 2.6.5, Rails 6.0.0, Rails 5.2.3 で確認しました。
データベースは、 PostgreSQL 10.7 を使っています。

$ rails --version
Rails 6.0.0

今回は、ちょっと複雑で長くなります。

モデルが、 Author, Publisher, Book の3つがあります。
Author は、Book を複数持ちます。
Publisher は、 Book を複数持ちます。

Author の 一覧画面と詳細画面を作ります。
Author の 詳細画面には、 Author が書いた本の出版社の一覧も表示します。

Rails プロジェクトを作る

$ rails new rails_sandbox
cd rails_sandbox

Author モデルを作る

$ bin/rails g model Author name

Publisher モデルを作る

$ bin/rails g model Publisher name

Book モデルを作る

Book モデルは、Author と Publisher に属します。

$ bin/rails g model Book title author:references publisher:references

Author モデルを変更する

Author モデルを変更します。
has_many を追加します。
to_param メソッドを定義します。(今回の事象を確認するために必要なメソッドです。)

app/models/author.rb
class Author < ApplicationRecord
  has_many :books

  def to_param
    "#{id}-#{name.parameterize}"
  end
end

Publisher モデルを変更する

Publisher モデルを変更します。
has_many を追加します。
scope を1つ追加し、Author の id から、紐づく出版社を取得します。

app/models/publisher.rb
class Publisher < ApplicationRecord
  has_many :books

  scope :with_author, ->(author_id) { distinct.joins(books: :author).where(authors: { id: author_id }) }
end

この scope の中の、 joins(books: :author).where(authors: { id: author_id }) の部分が今回のバグ修正の箇所になります。

なお、普通はこんなことしないで、 Author に以下の has_many を追加するのが通常だと思われます。

app/models/author.rb
class Author < ApplicationRecord
  ...
  has_many :publishers, ->{ distinct }, through: :books
  ...
end

seed データを作成する

seed データを作成します。今回は、最低限、必要なデータだけ用意します。

db/seeds.rb
pragmatic, addison = Publisher.create(
  [
    { name: 'Pragmatic Bookshelf' },
    { name: 'Addison-Wesley Professional' }
  ]
)

author = Author.create(name: 'Dave Thomas')

Book.create(
  [
    {
      title: 'Agile Web Development with Rails 6',
      publisher: pragmatic,
      author: author
    },
    {
      title: 'Agile Web Development with Rails 5.1',
      publisher: pragmatic,
      author: author
    },
    {
      title: 'Pragmatic Programmer',
      publisher: addison,
      author: author
    }
  ]
)

ルーティングを追加する

ルーティングとして、 author の一覧 (index) と詳細 (show) を追加します。

config/routes.rb
Rails.application.routes.draw do
  resources :authors, only: %i[index show]
end

AuthorsController を追加する

AuthorsController を追加します。
show メソッドでは、Publisher も取得するようにします。

app/controllers/authors_controller.rb
class AuthorsController < ApplicationController
  def index
    @authors = Author.all
  end

  def show
    @author = Author.find(params[:id])
    @publishers = Publisher.with_author(params[:id])
  end
end

ここで、 @publishers を取得するときに、 Publisher.with_author(@author.id) としていないところに注意してください。
そうしないと、今回のバグ修正が確認できません。

Author の一覧画面を作る

Author の一覧画面の View を作ります。

app/views/authors/index.html.erb
<h1>Author</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
    </tr>
  </thead>
  <tbody>
    <% @authors.each do |user| %>
      <tr>
        <td><%= link_to user.name, user %></td>
      </tr>
    <% end %>
  </tbody>
</table>

ここで、詳細画面のリンクを作成するために、 link_to user.name, user としているところがポイントになります。

Author の詳細画面を作成する

出版社を一緒に表示します。

app/views/authors/show.html.erb
<h1>Author: <%= @author.name %> </h1>

<table>
  <thead>
    <tr>
      <th>Publisher Name</th>
    </tr>
  </thead>
  <tbody>
    <% @publishers.each do |publisher| %>
      <tr>
        <td><%= publisher.name %></td>
      </tr>
    <% end %>
  </tbody>
</table>

seed データを登録する

データベースを作成し、マイグレーションを実行して seed データを登録します。

bin/rails db:create db:migrate db:seed

動作確認

rails server を実行して、 http://localhost:3000/authors にアクセスし、 Dave Thomas のリンクをクリックして詳細画面を表示します。
詳細画面を表示したときの URL が http://localhost:3000/authors/1-dave-thomas となっていることに注意してください。

Rails 5 では

詳細画面を表示したときに、 ActiveRecord::StatementInvalid のエラー画面が表示されます。

PG::InvalidTextRepresentation: ERROR:  invalid input syntax for integer: "1-dave-thomas"
: SELECT DISTINCT "publishers".* FROM "publishers" INNER JOIN "books" ON "books"."publisher_id" = "publishers"."id" INNER JOIN "authors" ON "authors"."id" = "books"."author_id" WHERE "authors"."id" = $1

何が発生しているかというと、

Publisher.with_author('1-dave-thomas')

がエラーになっています。

scope を使わずに、要点を抜き出して書くと

Publisher.joins(books: :author).where(authors: {id: '1-dave-thomas'})

を実行したときに、 authors.id の比較条件が 1-dave-thomas という文字列のため、エラーになっています。

Rails 6 では、 1-dave-thomas が authros.id の型 (integer) に合わせて 1 に変換される (Type Casting) ように修正されたため、エラーになりません。

試したソース

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

参考情報

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0