Rails API - 複数テーブル(親子テーブル)を結合して必要なカラムだけをJSONで返却したい

今回のゴールは次のようなレスポンスを返すことです。

期待するレスポンス

{
  "data": [
    {
      "id": 1,
      "title": "Rails APIについて",
      "body": "RailsをAPIモードで作成します。",
      "user": "山田太郎"
    },
    {
      "id": 2,
      "title": "DockerでRails環境を構築",
      "body": "Docker+docker-composeでRails6の環境を構築します。",
      "user": "鈴木一郎"
    },
    {
      "id": 3,
      "title": "MySQLをRials環境で使う",
      "body": "MySQLをRails環境で使用します。",
      "user": "田中三郎"
    }
  ]
}

UsersテーブルとPostsテーブル

users posts table


背景

ブログ記事(Post)一覧をGETで取得するする際にブログ記事を書いたユーザー(User)も一緒にJSONで返したいとなったときに使います。


ルーティングの設定

GET/postsにリクエストが来ることを想定しているので、indexでルーティングの設定をします。

Rails.application.routes.draw do
  resources :posts, only: %i[index]
end

エンドポイント

http://localhost:3000/posts

今回レスポンスとして返したい情報は次のようなpostsテーブルのid, title, bodyusersテーブルのnameのみとします。そのためcreated_atupdated_atuser.idなどは不要な情報なので省きたいです。

モデルの作成

UserモデルとPostモデルを作成します。

$ rails g model user name:string
$ rails g model post title:string body:text user:references

$ rails db:migrate

リレーションを設定

ユーザーは複数のブログ記事を持ち、ブログ記事は1人のユーザーに紐づくため、1対多のリレーションを設定します。

# user.rb(Userモデル)
class User < ApplicationRecord
  has_many :posts
end


# post.rb(Postモデル)
class Post < ApplicationRecord
  belongs_to :user
end

seedでデータを作成

記事一覧を呼び出したいので、ユーザーとそれに紐づくブログ記事を作成します。

  • seed.rb
User.create(name: "山田太郎").posts.create(title: "Rails APIについて", body: "RailsをAPIモードで作成します。")
User.create(name: "鈴木一郎").posts.create(title: "DockerでRails環境を構築", body: "Docker+docker-composeでRails6の環境を構築します。")
User.create(name: "田中三郎").posts.create(title: "MySQLをRials環境で使う", body: "MySQLをRails環境で使用します。")

$ rails db:seed

postsコントローラーを作成

postsコントローラとindexアクションを作成します。

$ rails g controller posts index

まずは、全てのブログ記事を取得して、jsonで返却してみます。

class PostsController < ApplicationController
  def index
    posts = Post.all

    render json: { data: posts }
  end
end

※postmanを使用して確認します

Postmanをお持ちでない方はインストールして確認してみてください。

postman get posts

GET: http://localhost:3000/postsとして「send」をクリックします。

すると次のように、ブログ記事一覧が表示されます。

posts get request

ここでは、postsテーブルの全てのカラム情報がレスポンスとして帰ってきており、ユーザー名もないので必要な情報を入れて、不要な情報はレスポンスとして帰ってこないようにしたいです。


本題「必要な情報だけを返却したい」

  • posts_controller.rb
class PostsController < ApplicationController
  def index
    posts = User.joins(:posts).select('posts.id, title, body, name AS user')

    render json: { data: posts }
  end
end

postsコントローラを上のように修正しました。まずは結果を確認してみましょう。

after posts get request

無事に必要な情報だけを持つ、ブログ記事一覧の取得ができました。


joinsメソッドとselectメソッド


joins

joinsメソッドは2つのテーブルを内部結合することができるメソッドです。

# モデル.joins(:関連モデル)
User.joins(:posts)

joins、内部結合に関しては次の記事が参考になります。 Rails における内部結合、外部結合まとめ

joinsメソッドを使って、usersテーブルとpostsテーブルを内部結合させます。


select

内部結合させたあとに、selectメソッドを使って、必要なカラムを指定して取得します。

.select('posts.id, title, body, name AS user')
  • ここでposts.idとなっているのは、テーブル同士でカラム名が重複している場合、テーブルを指定してidを取得しています。titlebodyなどの重複していないカラムはそのまま指定することができます。
  • name AS userとしているのは、usersテーブルのnameカラム名を表示用にuserと変更しています。postsテーブルのカラムと一緒に返却しているためnameだと分かりづらいためuserに変更しています。

実行されたSQLを確認

SELECT posts.id, title, body, name AS user FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id`

Written by@Ryutaro
日々学習した技術系のアウトプットをしていきます。学習内容: Ruby, Ruby on Rails, Go, TypeScript, Docker

GitHub