n対nの関係で中間テーブルにid以外のカラムを持たせ登録・編集を行う方法
Ruby On Railsでのアソシエーションの応用のお話。
下記のようなUserモデルとGroupモデルにn対nの関係を持たせ、UserにGroupの所有者情報を持たせたい時、中間テーブルに所有者情報を持たせた方がDB設計的になんとなく綺麗な気がする。
しかし、通常中間テーブルのレコードには直接アクセス出来ないため、モデルのアソシエーションで一工夫する必要がある。
n対nのアソシエーション
通常のn対nの関係は、以下のように表現する。まずはschema.rbから、
db/schema.rb ActiveRecord::Schema.define(version: 2019_08_26_011656) do create_table "groups", force: :cascade do |t| t.string "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| t.string "name", null: false t.string "email", null: false t.string "password_digest", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "group_users", force: :cascade do |t| t.bigint "group_id" t.bigint "user_id" t.index ["group_id"], name: "index_group_users_on_group_id" t.index ["user_id"], name: "index_group_users_on_user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end
次にモデル。has_many
のthrough
オプションに中間テーブルのシンボルを渡す事でn対nを表現している。
app/models/group.rb class Group < ApplicationRecord has_many :group_users, dependent: :destroy has_many :users, through: :group_users end app/models/user.rb class User < ApplicationRecord has_many :tasks, dependent: :destroy has_many :group_users, dependent: :destroy end app/models/group_user.rb class GroupUser < ApplicationRecord belongs_to :group belongs_to :user end
中間テーブルにカラムを持たせる方法
上記で作成したGroup_userにGroupの所有者、つまりowner
を持たせたい。
その場合、Groupモデルに下記のように記述を加えていく。
app/models/group.rb class Group < ApplicationRecord has_many :group_users, dependent: :destroy has_many :users, through: :group_users has_one :owner_group_user, -> {where(owner: true)}, class_name: 'GroupUser' has_one :owner_user, through: :owner_group_user, source: :user end # migrationファイルも忘れずに実行する db/schema.rb ActiveRecord::Schema.define(version: 2019_08_26_011656) do create_table "group_users", force: :cascade do |t| # ~ 中略 ~ t.boolean "owner", default: false, null: false end end
中間テーブルであるowner_group_user
でownerがtrueなレコードを取得し、それを使ってUserモデルからowner_user
を取得している。
class_name: 'GroupUser'
で用いるモデルはGroupUserであることを明記し、source: :user
でUserモデルの情報を参照することを明記している。
これでViewで、
group.owner_user.name
と書くと、Groupの所有者名を取得することが出来る。
またController側でも、Viewから送られてくるparamsにowner_user
の値をmerge
すればそのままcreate
やupdate
にparamsを渡す事で、owner_user
を格納できる。
def group_params params.require(:group).permit( :name, { user_ids: [] } ).merge(owner_user: current_user) end
以上!