はじめに
Ruby on Railsを学習しているのですが、画面読み込み時の処理(詳細は後述)で登録したレコードが消えるという現象が発生しました。
結論から言うと私の勉強不足、仕様の理解不足によるものでした。
解決にあたり、モデル、アソシエーションの理解が深まったところもあったので、記録として残すことにしました。
コードも冗長ですが、ご容赦ください。
開発環境
- Docker
- Ruby 3.3.3
- Rails 7.1.2
起:機能の実装
■機能の概要
ブックマークの管理機能を作成していた。
(グレーの背景がカテゴリ、グリーンの背景がフォルダの中身)
これらはカテゴリ、フォルダ、ブックマークで要素が構成されている。
また階層イメージとしては以下の2種類が挙げられる。
- カテゴリ > フォルダ > ブックマーク
→フォルダ内にブックマークが存在するパターン - カテゴリ > フォルダ、ブックマーク
→フォルダ内にブックマークが内包されていないパターン
■アソシエーション
・カテゴリはフォルダを複数持つ(一対多)
・カテゴリはブックマークを複数持つ(一対多)
・フォルダはブックマークを複数持つ(一対多)
カテゴリとフォルダにはそれぞれdepndent: :destroy
を設定している。
■データ取得処理を実装
以下の取得処理を作りました。
class DashboardsController < ApplicationController
before_action :require_login, only: %i[index]
def index
@home_layouts = current_user.home_layouts.order(position: :asc)
@home_layouts.each do |layout|
# カテゴリ情報取得
layout.categories = layout.categories.order(position: :asc)
layout.categories.each do |category|
#▼▼▼▼▼▼▼▼ここから下が本題▼▼▼▼▼▼▼▼
# フォルダなしのブックマークを取得
category.bookmarks = category.bookmarks.where(folder_id: nil).order(position: :asc)
# フォルダ情報取得
category.folders = category.folders.order(position: :asc)
category.folders.each do |folder|
# フォルダ内のブックマークを取得
folder.bookmarks = folder.bookmarks.order(position: :asc)
end
end
end
end
end
承:問題発生
データ登録処理を作成し正常に動作していた。
ログは何てことなくコミットまでされている。
2024-07-09 16:10:39 Bookmark Maximum (1.9ms) SELECT MAX("bookmarks"."position") FROM "bookmarks" WHERE "bookmarks"."category_id" = $1 AND "bookmarks"."folder_id" = $2 [["category_id", 3], ["folder_id", 1]]
2024-07-09 16:10:39 ↳ app/models/bookmark.rb:54:in `get_new_position_bookmark'
2024-07-09 16:10:39 TRANSACTION (0.4ms) BEGIN
2024-07-09 16:10:39 ↳ app/controllers/bookmarks_controller.rb:14:in `create'
2024-07-09 16:10:39 Bookmark Create (12.3ms) INSERT INTO "bookmarks" ("title", "url", "type", "folder_id", "category_id", "group_id", "user_id", "position", "description", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING "id" [["title", "Copilot"], ["url", "https://copilot.microsoft.com/?wlexpsignin=1#"], ["type", 0], ["folder_id", 1], ["category_id", 3], ["group_id", nil], ["user_id", 28], ["position", 1], ["description", ""], ["created_at", "2024-07-09 07:10:39.050132"], ["updated_at", "2024-07-09 07:10:39.050132"]]
2024-07-09 16:10:39 ↳ app/controllers/bookmarks_controller.rb:14:in `create'
2024-07-09 16:10:39 TRANSACTION (2.6ms) COMMIT
しかし登録後画面をリロードすると、登録したデータが画面に表示されていない。
DBからもレコードが消えている。(????)
■再現する
もう一度同じ手順で確認したところ、画面リロード時にログがでていた。
2024-07-09 16:10:51 Started GET "/dashboards" for 192.168.65.1 at 2024-07-09 07:10:51 +0000
2024-07-09 16:10:51 Cannot render console from 192.168.65.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
2024-07-09 16:10:51 Processing by DashboardsController#index as HTML
~~~省略~~~
2024-07-09 16:10:51 TRANSACTION (0.3ms) BEGIN
2024-07-09 16:10:51 ↳ app/controllers/dashboards_controller.rb:10:in `block (2 levels) in index'
2024-07-09 16:10:51 Bookmark Destroy (5.5ms) DELETE FROM "bookmarks" WHERE "bookmarks"."id" = $1 [["id", 12]]
2024-07-09 16:10:51 ↳ app/controllers/dashboards_controller.rb:10:in `block (2 levels) in index'
2024-07-09 16:10:51 TRANSACTION (0.9ms) COMMIT
2024-07-09 16:10:51 ↳ app/controllers/dashboards_controller.rb:10:in `block (2 levels) in index'
~~~省略~~~
なぜかDestroy
処理が走っている。
コードで見ると以下の処理が原因らしい。
# フォルダなしのブックマークを取得
category.bookmarks = category.bookmarks.where(folder_id: nil).order(position: :asc)
■調査
色々と調べてみたが、
・アソシエーションは適切に設定されている。
・callback等の記載はなし
・その他コントローラのdestroy
は呼び出されていない。
考えられるのはモデルに設定したdepndent: :destroy
によりアソシエーションの問題が発生した可能性である。
転:原因
原因はDestroy
処理の実行箇所である以下のコードの書き方が問題であった
category.bookmarks = category.bookmarks.where(folder_id: nil).order(position: :asc)
1.category.bookmarks
に直接値を設定している
2.アソシエーションによって関連づけられた、レコード(folder_id
がnull
ではない)が関連から切り離されてしまう。
Railsの仕様上has_many
の関連づけに対して、
直接値を設定することでCategory
から関連が切り離され、
結果としてfolder
と関連のあるbookmark
のレコードが削除されていた。
結:解決方法
Category
モデルに以下の記述を追加
attr_accessor
を使うことで、setter
getter
を自動定義してくれる。
# フォルダを持たないブックマークを格納
attr_accessor :bookmarks_without_folder
コントローラを以下のように修正
def index
@home_layouts = current_user.home_layouts.order(position: :asc)
@home_layouts.each do |layout|
# カテゴリ情報取得
layout.categories = layout.categories.order(position: :asc)
layout.categories.each do |category|
# フォルダなしのブックマークを取得
- category.bookmarks = category.bookmarks.where(folder_id: nil).order(position: :asc)
+ bookmarks_without_folder = category.bookmarks.where(folder_id: nil).order(position: :asc)
# フォルダなしのブックマークを格納する
+ category.bookmarks_without_folder = bookmarks_without_folder
# フォルダ情報取得
category.folders = category.folders.order(position: :asc)
category.folders.each do |folder|
# フォルダ内のブックマークを取得
folder.bookmarks = folder.bookmarks.order(position: :asc)
end
end
end
end
ここで私が勘違いしていたのは、Railsのモデルにはテーブルに存在しないカラムを定義できないのではないかと勘違いしていたことである。
最後に
アソシエーションについての理解が足りてないと改めて実感しました。
アソシエーションが苦手でもどうやって理解を深めていくのかがわからないので、課題です。
あとは冗長なコードを書いてしまっているので、書籍等で理解を深めていきたいと思います。