背景

自分は現在院生2回生で, 4回生のころから京都のゲーム・Web会社で働いている. 今も細々と続けている中で, Ruby on Railsに触れる機会があったのでその知見を貯めておく.

今回は多対多の関連付けと, その使い方を紹介する.

データ構造

このアプリケーションには, usergroupの定義があって, userは複数のgroupに所属することができて, groupは複数のuserを持つ. よって中間テーブルを用いて多対多の関連付けを定義する.

schema.rb

ActiveRecord::Schema.define(version: 20_190_219_052_352) do
  create_table "group_users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.bigint "user_id", null: false
    t.bigint "group_id", null: false
    t.index ["group_id"], name: "index_group_users_on_group_id"
    t.index %w[user_id group_id], name: "index_group_users_on_user_id_and_group_id"
    t.index ["user_id"], name: "index_group_users_on_user_id"
  end
  create_table "groups", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "name", null: false
    t.integer "status", default: 1, null: false
  end
  create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "name", null: false
  end
end

テーブル名をgroup_usersにして中間テーブルを作る. このテーブルをもとにgroupからuser, userからgroupを参照する. ここでindexが3つ貼られているが, 以下のようにして簡単に貼ることができる.

create_group_users.rb

class CreateGroupUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :group_users do |t|
      t.references :user, null: false, foreign_key: true
      t.references :group, null: false, foreign_key: true
      t.timestamps
    end
    add_index :group_users, %i[user_id group_id]
  end
end

関連付け

以下がそれぞれのモデルである.

group_user.rb

class GroupUser < ApplicationRecord
  validates :user_id,   presence: true
  validates :group_id,  presence: true
  belongs_to :user
  belongs_to :group
end

1行のgroup_userが1行のuserと1行のgroupに対応しているので, belongs_toで関連づける.

user.rb

class User < ApplicationRecord
  has_many :group_users, dependent: :nullify
  has_many :groups, through: :group_users
end
group.rb

class Group < ApplicationRecord
  has_many :group_users, dependent: :nullify
  has_many :users, through: :group_users
  def shape_response
    { id: id, name: name }
  end
end

1行が複数のgroup, 複数のuserと対応しているのでhas_many. throughで中間テーブルを利用する.

処理の書き方

自分が属するグループ全てを返す処理を以下に書く.

groups_controller.rb

def index
  groups = current_user.groups
  ok("groups": groups.map(&:shape_response))
end

current_userは自分のuserデータを返す. それに.groupsと書くだけで自分と紐づくグループが取ってこれる.

ここで, groupsのカラムであるstatusのみを除いたものを返したい場合は, 上記のように書ける. これは


# 冗長に書いた場合
ok("groups": groups.map { |group|
  group.shape_response
})

を省略して記述した場合である. モデルでメソッドを定義しておくとこんなに綺麗に書ける。

感想

関連付けを正確に行うことによって, めちゃめちゃ綺麗に書けるのがrailsの良さだと思った.