概要
何らかの記事投稿機能があるとして、各記事に紐づくタグを複数設定したいとします。
今回、アンチパターンとともに、サンプルをご紹介します。
テーブル定義
基本的な設計です。
ユーザーは複数の投稿ができる。
投稿は1人のユーザーに従属する。
1 対 多
の関係ですね。
そこに、「投稿にタグを複数付けられるようにしたい」という要件が発生しました。
そこで以下のようなテーブル定義を検討したいと思います。
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_controller
でcreate
アクションが実行される際に、タグも同時に保存するようにします。
コントローラー
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
メソッドはタグを編集する際に使用する想定です。
モデル
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アンチパターン