0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Rails】記事投稿に紐づく複数のタグを保存する実装

Posted at

概要

何らかの記事投稿機能があるとして、各記事に紐づくタグを複数設定したいとします。
今回、アンチパターンとともに、サンプルをご紹介します。

テーブル定義

まず以下のようにテーブル定義されているとします。
投稿機能.png

基本的な設計です。
ユーザーは複数の投稿ができる。
投稿は1人のユーザーに従属する。
1 対 多の関係ですね。

そこに、「投稿にタグを複数付けられるようにしたい」という要件が発生しました。
そこで以下のようなテーブル定義を検討したいと思います。
タグ機能.png
1つの投稿は、複数のタグを持てる。
タグは、1つの投稿に従属する。
こちらも1 対 多の関係になります。

関連付け

class User < ApplicationRecord
  has_many :posts, dependent: :delete_all
end

class Post < ApplicationRecord
  belongs_to :user
  has_many :tags, dependent: :delete_all
end

class Tag < ApplicationRecord
  belongs_to :post
end

マイグレーションファイル

※ posts, users は省略

create_table "tags", force: true do |t|
  t.references :post, null: false, foreign_key: true
  t.string :tag, null: false
  t.datetime "created_at"
  t.datetime "updated_at"
end

実装の手順

posts_controllercreateアクションが実行される際に、タグも同時に保存するようにします。

コントローラー

posts_controller.rb
def create
  post = Post.new(post_params)
  post.save!
  post.save_tags(tag_params[:tags]) # <= 追加
  post = post.as_json(methods: [:tags])

  render json: { post: }, status: :ok

  rescue ActiveRecord::RecordInvalid => e
    render json: { errors: e.record.errors }, status: :unprocessable_entity
  end
end

private

def post_params
  params.permit(:content)
end

def tag_params
  params.permit(tags: [])
end

Postモデルにsave_tagsメソッドを追加します。
引数はArrayで渡し、それぞれINSERTしていくイメージです。

update_tagsメソッドはタグを編集する際に使用する想定です。

モデル

post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :tags, dependent: :delete_all

  def save_tags(set_tags)
    return tags if set_tags.blank?

    results = []

    set_tags.each do |set_tag|
      tag = tags.new(tag: set_tag)
      tag.save!
      results << tag
    end

    results
  end

  def update_tags(set_tags)
    return tags if set_tags.blank?

    tags.destroy_all
    save_tags(set_tags)
  end

  def tags
    tags.pluck(:tag)
  end
end

このように実装することで、以下のように投稿に紐づくタグを設定できます。

id post_id tag
1 1 新宿
2 1 渋谷
3 1 池袋
4 2 六本木
5 2 麻布
6 2 富ヶ谷

アンチパターン

それぞれの投稿に紐づくタグを追加する要件が発生したとき、ぱっと思い浮かんだ構造は以下でした。

id content tags
1 こんにちは。 ["新宿","渋谷","池袋"]

ちなみに Active Record の機能の一つに serializeがあり、まさに上のtagsカラムのように配列を格納できるようになります。

「めっちゃ便利やん。。」と思うかもしれませんが、インデックスが貼れなかったり、仕様変更に弱いなど多くの副作用があります。

基本的にはserializeは使用すべきでないでしょう。

以下の記事が参考になるので、詳しくはこちらをご確認ください。
ActiveRecord serialize / store の甘い誘惑を断ち切ろう

というわけで、最初の実装とした次第です。

まとめ

ひとつひとつの値は個別の列と行に格納しましょう。

引用:SQLアンチパターン

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?