LoginSignup
28
27

More than 3 years have passed since last update.

has_manyのidsを使って、簡単に中間テーブルの関連付けを行う

Last updated at Posted at 2019-07-07

概要

チェックボックスを複数選択し、その情報を配列で受け取って、中間テーブルの関連付けを行います。

E-R図

スクリーンショット 2019-07-07 22.57.39.png
※今回説明で使用するアプリとカラム数があっていませんが、中間テーブルの使い方として掲載します。

事前準備

ToDo系アプリを想定して進めます。
画像のようなタスクを新規作成する画面を用意しました。
今回の話で使用するのは、Labelの複数のチェックボックスから値を取ってくる方法です。
スクリーンショット 2019-07-07 23.13.00.png

view

app/views/_form.html.erb
<tr>
  <th>Label</th>
  <td>
    <% Label.all.each do |label| %>
      <%= form.check_box :label_ids, { multiple: true, checked: @task.labels.find_by(id: label.id).present?, include_hidden: false }, label[:id] %>
      <label class='badge badge-secondary'><%= label.name %></label>
    <% end %>
  </td>
</tr>

form.check_box :label_idsと記述すると、paramsの値として、"label_ids"=>["7", "8", "10"]このような形でハッシュのキーを持つ値を送れます。
multipleオプションを使用することで、複数のチェックボックスのパラメータを配列形式で送れます。
checked: @task.labels.find_by(id: label.id).present?これはeditで使用することを想定しており、編集するタスクに紐付いているラベルにチェックを付けるということをしています。
include_hidden: falseチェックしていない項目については、パラメータを送らないオプションです。

model

app/models/task.rb
class Task < ApplicationRecord
  has_many :labelings, dependent: :destroy
  has_many :labels, through: :labelings
end
app/models/labeling.rb
class Labeling < ApplicationRecord
  belongs_to :task
  belongs_to :label
end
app/models/label.rb
class Label < ApplicationRecord
  has_many :labelings, dependent: :destroy
end

controller

app/controllers/tasks_controller.rb
  def create
    @task = Task.new(task_params)
    if @task.save
      redirect_to root_path, notice: '新しいタスクを作成しました'
    else
      render :new
    end
  end

 #省略

  private

  def task_params
    params.require(:task).permit(:subject, :content, :expired_at, :state, :priority, :user_id, label_ids: [])
  end

label_ids: []複数チェックボックスの値を配列で渡すことを許可しています。

タスク新規作成時に、ラベルを複数つける処理の動き

もう一度タスク新規投稿の画面を貼ります。
スクリーンショット 2019-07-07 23.13.00.png
Labelの開発DB操作次回MTGまでにチェックを入れた状態で登録ボタンを押します。
この3つのチェックボックスの値を取得します。

まずparamsには次のような値が送られます

Parameters: { "utf8"=>"✓",
              "authenticity_token"=>"WoRUP+q21xCR760x9L3Y9lyLeQ+THkS9YOGISfGRyDRg0xaI1D2BNnRQLTuaHANpw+NCohg06QtB5U4RxruO5g==", 
              "task"=>{ "subject"=>"タスク新規作成", 
                        "content"=>"ラベルを複数登録", 
                        "expired_at(1i)"=>"2019", 
                        "expired_at(2i)"=>"7", 
                        "expired_at(3i)"=>"8", 
                        "expired_at(4i)"=>"10", 
                        "expired_at(5i)"=>"00", 
                        "user_id"=>"87", 
                        "state"=>"着手中", 
                        "priority"=>"high", 
                        "label_ids"=>["7", "8", "10"]}, 
              "commit"=>"登録する" }

次に、controllerのcreateアクション時にbinding.pryで値を確認します。

  def create
    @task = Task.new(task_params)
    binding.pry
    if @task.save
      redirect_to root_path, notice: '新しいタスクを作成しました'
    else
      render :new
    end
  end
# idsだけ確認
pry(#<TasksController>)> @task.label_ids
=> [7, 8, 10]

# newメソッドで作られたlabelsを確認
pry(#<TasksController>)> @task.labels
=> [#<Label:0x00007f92cfe32628 id: 7, name: "開発", created_at: Fri, 05 Jul 2019 15:28:18 JST +09:00, updated_at: Sun, 07 Jul 2019 23:10:17 JST +09:00>,
 #<Label:0x00007f92cfe32470 id: 8, name: "DB操作", created_at: Fri, 05 Jul 2019 15:28:18 JST +09:00, updated_at: Sun, 07 Jul 2019 23:10:24 JST +09:00>,
 #<Label:0x00007f92cfe322e0 id: 10, name: "次回MTGまで", created_at: Fri, 05 Jul 2019 15:28:18 JST +09:00, updated_at: Sun, 07 Jul 2019 23:11:06 JST +09:00>]

pry(#<TasksController>)> @task.save
   (0.9ms)  BEGIN
   (pry):5
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 87], ["LIMIT", 1]]
   (pry):5
  Task Create (33.3ms)  INSERT INTO "tasks" ("subject", "content", "created_at", "updated_at", "expired_at", "state", "priority", "user_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING "id"  [["subject", "タスク新規作成"], ["content", "ラベルを複数登録"], ["created_at", "2019-07-07 23:59:15.509944"], ["updated_at", "2019-07-07 23:59:15.509944"], ["expired_at", "2019-07-08 10:00:00"], ["state", "着手中"], ["priority", 0], ["user_id", 87]]
   (pry):5
  Labeling Create (0.9ms)  INSERT INTO "labelings" ("task_id", "label_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["task_id", 212449], ["label_id", 7], ["created_at", "2019-07-07 23:59:15.553820"], ["updated_at", "2019-07-07 23:59:15.553820"]]
   (pry):5
  Labeling Create (0.3ms)  INSERT INTO "labelings" ("task_id", "label_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["task_id", 212449], ["label_id", 8], ["created_at", "2019-07-07 23:59:15.556142"], ["updated_at", "2019-07-07 23:59:15.556142"]]
   (pry):5
  Labeling Create (0.2ms)  INSERT INTO "labelings" ("task_id", "label_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["task_id", 212449], ["label_id", 10], ["created_at", "2019-07-07 23:59:15.557177"], ["updated_at", "2019-07-07 23:59:15.557177"]]
   (pry):5
   (0.8ms)  COMMIT
   (pry):5
=> true

pry(#<TasksController>)> @task.id
=> 212449
pry(#<TasksController>)> Labeling.where(task_id: 212449)
  Labeling Load (0.2ms)  SELECT "labelings".* FROM "labelings" WHERE "labelings"."task_id" = $1  [["task_id", 212449]]
   app/controllers/tasks_controller.rb:38
=> [#<Labeling:0x00007f92d142e9e0
  id: 11,
  task_id: 212449,
  label_id: 7,
  created_at: Sun, 07 Jul 2019 23:59:15 JST +09:00,
  updated_at: Sun, 07 Jul 2019 23:59:15 JST +09:00>,
 #<Labeling:0x00007f92d142e8a0
  id: 12,
  task_id: 212449,
  label_id: 8,
  created_at: Sun, 07 Jul 2019 23:59:15 JST +09:00,
  updated_at: Sun, 07 Jul 2019 23:59:15 JST +09:00>,
 #<Labeling:0x00007f92d142e760
  id: 13,
  task_id: 212449,
  label_id: 10,
  created_at: Sun, 07 Jul 2019 23:59:15 JST +09:00,
  updated_at: Sun, 07 Jul 2019 23:59:15 JST +09:00>]

無事1度のcreateアクションで、中間テーブルに3個保存されました!

タスク編集時に、複数のラベルを変更した時の、中間テーブルの動き

ここまで新規作成時の中間テーブルの関連付けの説明をしました。
では、更新する時はどうやるの?
外すラベルのid調べてテーブルからdeleteしてinsertするの?といった疑問が浮かぶと思います。
実はdeleteとかはいい感じに勝手にやってくれます!
実際の動きを確認してみました

スクリーンショット 2019-07-08 0.07.20.png
新規作成時に開発DB操作次回MTGまでにチェックを入れてます。
viewにchecked: @task.labels.find_by(id: label.id).present?を記載しているので、デフォルトでチェックが入っています

スクリーンショット 2019-07-08 0.07.35.png
DB操作次回MTGまでのチェックボックスを外し、
新たに検討中にチェックしました。
更新するボタンを押します。

   (0.2ms)  BEGIN
   app/controllers/tasks_controller.rb:52
  Label Load (0.3ms)  SELECT "labels".* FROM "labels" WHERE "labels"."id" IN ($1, $2)  [["id", 7], ["id", 9]]
   app/controllers/tasks_controller.rb:52
  Labeling Destroy (0.3ms)  DELETE FROM "labelings" WHERE "labelings"."task_id" = $1 AND "labelings"."label_id" IN ($2, $3)  [["task_id", 212449], ["label_id", 8], ["label_id", 10]]
   app/controllers/tasks_controller.rb:52
  Labeling Create (0.4ms)  INSERT INTO "labelings" ("task_id", "label_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["task_id", 212449], ["label_id", 9], ["created_at", "2019-07-08 00:08:31.021375"], ["updated_at", "2019-07-08 00:08:31.021375"]]
   app/controllers/tasks_controller.rb:52
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 87], ["LIMIT", 1]]
   app/controllers/tasks_controller.rb:52
   (2.1ms)  COMMIT
   app/controllers/tasks_controller.rb:52

すると、中間テーブルからチェックボックスを外したlabel_idがDestroyされた後にCreateされています。

28
27
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
28
27