Rails APIモードで中間テーブルを用いた複数タグ投稿
やったこと
今回はRailsのAPIモードを使って、投稿機能の中にタグを複数投稿できる機能を作成しました。
投稿時に1つのpostsテーブルのcontentカラムと複数のtagsテーブルのnameカラムを入力してそれぞれをDBに保存できる形で投稿できるAPIを作っていきます。
今回は1つの文字列入力欄にタグをスペース区切りで入力すると配列で保存するようにします。
このAPIを用いてReactで投稿機能を作ったのが以下のリンクになります。
使用技術
Ruby 2.6.6
Rails 6.1.4
ER図
手順
- プロジェクト作成
- HTTP通信設定&ポート番号設定
- モデル作成
- コントローラー作成
- ルーティング設定
- API通信確認
1. プロジェクト作成
任意のディレクトリに移動した後、Railsプロジェクトを作成します。
$ 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
がコメントアウトされていると思うのでコメントアウトを外してください。
gem 'rack-cors'
忘れずに$ bundle install
をすることで無事インストールが完了します。
今後のRailsプロジェクトのコマンドはプロジェクトディレクトリ配下で実行しましょう。
$ cd api
$ bundle install
CORS設定
まずはCORSの設定をしていきます。
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
ファイルの以下の部分を修正していきます。
# 修正 3000→3001
port ENV.fetch("PORT") { 3001 }
この後$ rails s
をしてlocalhost:3000
でサーバーが起動したら成功です。
$ rails s
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モデル
has_many :post_tags, dependent: :destroy
has_many :tags, through: :post_tags
-
through
オプションによって、post_tags
テーブルを通してtags
テーブルとの関連付けを行っています。こうすることで、Post.tags
とすればPost
に紐付けられたTag
の取得が可能になります。これは投稿詳細画面などでその投稿に付けられているタグを取得して表示させるなどの際に使えます。 -
through
オプションを使う場合、先にその中間テーブルとの関連付けを行う必要があります。 - 中間テーブルに
dependent: :destroy
オプションを付けることで、Post
が削除されると同時にPost
とTag
の関係が削除されます。
tagモデル
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モデル
belongs_to :post
belongs_to :tag
- 複数の
Post
、複数のTag
に所有されるのでbelongs_to
で関連付け。
4. コントローラー作成
作成したDBを基にAPIコントローラーを作成してAPI通信で多対多の複数タグを投稿できる投稿機能を作成していきます。
コントローラー作成コマンドを実行
まずはコントローラーファイルを作成していきます。
$ rails g controller api/v1/posts
このコマンドによって、app/controllers/api/v1/posts_controller.rb
ファイルが作成される。
- api/v1ディレクトリ内にposts_controller.rbファイルを作成するのはAPIを作る上でのバージョン1という意味であり、ルールとして覚えておけばいい。
APIコントローラー記述
以下のようににコントローラーを記述します。
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. ルーティング設定
先程作成したコントローラーのルーティングを設定していきます。
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で投稿機能を実際に作成していきましょう。
以下のリンクで作成できるので見てみてください。
Discussion