👌

Rails APIモードで中間テーブルを用いた複数タグ投稿

2022/02/11に公開

やったこと

今回はRailsのAPIモードを使って、投稿機能の中にタグを複数投稿できる機能を作成しました。

投稿時に1つのpostsテーブルのcontentカラムと複数のtagsテーブルのnameカラムを入力してそれぞれをDBに保存できる形で投稿できるAPIを作っていきます。

今回は1つの文字列入力欄にタグをスペース区切りで入力すると配列で保存するようにします。

このAPIを用いてReactで投稿機能を作ったのが以下のリンクになります。
https://zenn.dev/shogo_matsumoto/articles/1b0c2d3232bf5f

使用技術

Ruby 2.6.6
Rails 6.1.4

ER図

手順

  1. プロジェクト作成
  2. HTTP通信設定&ポート番号設定
  3. モデル作成
  4. コントローラー作成
  5. ルーティング設定
  6. API通信確認

1. プロジェクト作成

任意のディレクトリに移動した後、Railsプロジェクトを作成します。

terminal
$ rails new api --api
  • --apiオプションをつけることでRailsをAPIサーバーとして使う際に必要なディレクトリやファイルのみのプロジェクトを作成してくれます。
  • $ rails newの後に任意のプロジェクト名を指定します。今回はapiというプロジェクト名にしました。

2. HTTP通信設定&ポート番号設定

Reactと通信をする前提で作っているため、Reactのポート番号からのHTTP通信を許可していきます。

まずは、CORS(Cross-Origin Resource Sharing)を設定するためのGemをインストールしていきます。

CORSとは

CORSとは、クライアントが自分と違うオリジンをもつサーバーからのアクセスを許可する仕組みのことです。 WebクライアントとAPIを分ける場合などで、異なるオリジン間の通信を行うためにはCORSの設定をする必要があります。

Gem追加

Gemfileに以下のgemを記述します。
APIモードでプロジェクトを作成した際にGemfileにrack-corsがコメントアウトされていると思うのでコメントアウトを外してください。

Gemfile
gem 'rack-cors'

忘れずに$ bundle installをすることで無事インストールが完了します。
今後のRailsプロジェクトのコマンドはプロジェクトディレクトリ配下で実行しましょう。

terminal
$ cd api
$ bundle install

CORS設定

まずはCORSの設定をしていきます。
config/initializers/cors.rbファイルに以下を記述していきます。

config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    # Frontendサーバーのポート番号がlocalhost:3000なので、localhost:3000を許可する
    origins 'localhost:3000'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
  • Frontendサーバーのポート番号がlocalhost:3000なので、originsにlocalhost:3000を設定して許可します。

Railsのポート番号を変更

Frontend側のポート番号をlocalhost:3000に設定するのでRails側のポート番号をlocalhost:3001に変更していきます。(Railsのデフォルトのポート番号はlocalhost:3000

config/puma.rbファイルの以下の部分を修正していきます。

config/puma.rb
# 修正 3000→3001
port ENV.fetch("PORT") { 3001 }

この後$ rails sをしてlocalhost:3000でサーバーが起動したら成功です。

terminal
$ rails s

http://localhost:3001

3. モデル作成

テーブル作成

今回作成するテーブルは以下になります。
postsテーブルとtagsテーブルは多対多の関係になるため、post_tagsテーブルという中間テーブルを作ります。

  • posts
    • content:text
  • tags
    • name:string
  • post_tags
    • post_id:references
    • tag_id:references

なので、このテーブル設計に従ってコマンドでモデルを作成していきます。

postsテーブル

$ rails g model Post content:text

postsテーブルのマイグレーションファイルは以下のようになります。

  • contentカラムはtext型で作成します。
class CreatePosts < ActiveRecord::Migration[6.1]
  def change
    create_table :posts do |t|
      t.text :content

      t.timestamps
    end
  end
end

tagsテーブル

$ rails g model Tag name:string

tagsテーブルのマイグレーションファイルは以下のようになります。

  • nameカラムはstring型で作成します。
class CreateTags < ActiveRecord::Migration[6.1]
  def change
    create_table :tags do |t|
      t.string :name

      t.timestamps
    end
  end
end

post_tagsテーブル

$ rails g model PostTag post:references tag:references

post_tagsテーブルのマイグレーションファイルは以下のようになります。

  • 中間テーブルのため、post_idとtag_idカラムを作成します。
  • reference型を指定することで外部キーのカラムにすることができます。

外部キーとは?

テーブル同士の紐づけに用いるカラムのこと。
users テーブル と user_login_histories テーブル が合った時に、 user_login_histories テーブル に user_id があったら
user_login_histories.user_id の値は users テーブル において主キー、 user_login_histories テーブル では外部キーと呼ばれる。
主キーと外部キーはRDBにとって、それぞれのテーブルを関連付けるために使用するとても大切な機能。

最後に$ rails db:migrateでテーブルを作成します。

$ rails db:migrate

モデル設定

次に今回のテーブル設計に基づいたアソシエーションをモデルファイルに記述していきます。

Postモデル

app/models/post.rb
has_many :post_tags, dependent: :destroy
has_many :tags, through: :post_tags
  • throughオプションによって、post_tagsテーブルを通してtagsテーブルとの関連付けを行っています。こうすることで、Post.tagsとすればPostに紐付けられたTagの取得が可能になります。これは投稿詳細画面などでその投稿に付けられているタグを取得して表示させるなどの際に使えます。
  • throughオプションを使う場合、先にその中間テーブルとの関連付けを行う必要があります。
  • 中間テーブルにdependent: :destroyオプションを付けることで、Postが削除されると同時にPostTagの関係が削除されます。

tagモデル

app/models/tag.rb
has_many :post_tags, dependent: :destroy, foreign_key: 'tag_id'
has_many :posts, through: :post_tags
  • post_tagsテーブルとの関連付けを行ってから、post_tagsを通してpostsテーブルと関連づけています。Tag.postsとすれば、タグに紐付けられたPostを取得できる。これは特定のタグ、例えば「スポーツ」などといったタグを持った投稿を検索したい時などに使えます。

post_tagモデル

app/models/post_tag.rb
belongs_to :post
belongs_to :tag
  • 複数のPost、複数のTagに所有されるのでbelongs_toで関連付け。

4. コントローラー作成

作成したDBを基にAPIコントローラーを作成してAPI通信で多対多の複数タグを投稿できる投稿機能を作成していきます。

コントローラー作成コマンドを実行

まずはコントローラーファイルを作成していきます。

terminal
$ rails g controller api/v1/posts

このコマンドによって、app/controllers/api/v1/posts_controller.rbファイルが作成される。

  • api/v1ディレクトリ内にposts_controller.rbファイルを作成するのはAPIを作る上でのバージョン1という意味であり、ルールとして覚えておけばいい。

APIコントローラー記述

以下のようににコントローラーを記述します。

app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < ApplicationController
    def create
        # 投稿入力
        post = Post.new(content: params[:content])
	# タグ入力、文字列を半角スペース区切りで入力することで配列に格納
        tags = params[:tags].split(' ')
        if post.save
	       # 配列に入ったタグを一つずつeach文で回す
            tags.each do |tag|
		# tagsテーブルのnameカラムをpostsテーブルに紐付けて一つずつ作成
                post_tag = post.tags.create(name: tag)
            end
	    # レスポンスで返ってくるJSON情報を設定
            post_list = {
                content: post.content,
                tags: tags
            }
            render json: post_list
        else
            render json: post_list.errors, status: 422
        end
    end
end

一つずつ解説していきます。

post = Post.new(content: params[:content])
  • 入力したpostsテーブルのcontentカラムを作成できる。
tags = params[:tags].split(' ')
  • 今回はタグをスペース区切りで入力すると、それぞれを配列として入れるようにしているため、splitメソッドを使って.split(" ")とすることで1つの文字列を配列にして複数はいるようにしている。
  • params[:tags]とすることで、tagsというキーに値を指定することでtagsテーブルのnameカラムに値を入れるように設定する。

次に以下の部分を解説します。

if post.save
    tags.each do |tag|
	post_tag = post.tags.create(name: tag)
    end
    post_list = {
	content: post.content,
	tags: tags
    }
    render json: post_list
else
    render json: post_list.errors, status: 422
end

一つずつ分解して解説していきます。

if post.save
  • saveメソッドを使ってpost.saveとすることで、入力したpostsテーブルのcontentカラムを保存することができます。
  • 今回は「もし入力したpostsテーブルのcontentカラムの保存が成功したら」という条件分岐をしています。
  • もし失敗したら失敗したエラーメッセージを送るようにしています。
  • このifの後の記述には作成成功の記述を書いていきます。
tags.each do |tag|
    post_tag = post.tags.create(name: tag)
end
  • 先程条件分岐の前に記述したtagsという配列の変数を一つずつeach文で回しています。
  • each文で一つずつtagという変数に入れることで、ostモデルに紐付いたtagsテーブルとしてそれぞれDBに保存することができます。
post_list = {
    content: post.content,
    tags: tags
}
render json: post_list
  • この記述でPOSTメソッドでどのJSONが返されるのかを設定しています。
else
    render json: post_list.errors, status: 422
end
  • この記述で投稿の保存が失敗した際にエラーメッセージを送信することができます。

5. ルーティング設定

先程作成したコントローラーのルーティングを設定していきます。

config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :posts
    end
  end
end
  • api/v1/posts_controller.rbのルーティングなので、namespaceを使ってネストを合わせて設定しています。
  • こうすることで、http://localhost:3001/api/v1/postsというURLで先程のcreateが実行できるようになります。
  • 今回はcreateアクションしか作ってませんが、今後index, show, edit, update, destroyアクションを作る予定なのでresources :postsと記述しておきます。

6. API通信確認

最後にAPI通信の確認をして、本当に思い通りに1つのcontentと複数のtagが投稿できるのか確かめていきましょう。

ポストマンで確かめます。
以下のように設定してリクエストしましょう。

  • POSTメソッド
  • URL→http://localhost:3001/api/v1/posts
  • json→{"content": "content1", "tags": "tag1 tag2 tag3"}

以下のようなレスポンスが返ってきたら成功です。

"content": "content1",
"tags": [
    "tag1",
    "tag2",
    "tag3"
]

Rails APIで投稿機能用のAPIサーバーが作成できたら次はこのAPIを使ってReactで投稿機能を実際に作成していきましょう。

以下のリンクで作成できるので見てみてください。
https://zenn.dev/shogo_matsumoto/articles/1b0c2d3232bf5f

Discussion