はい、辻(@dim0627)です。
最近はVimを8にしたせいでなんか調子悪くなってどったんばったん大騒ぎしてます。 2期、うまくいくといいですね。
さて、今日はevilと言われるdefault_scopeのことを書きます。
Railsのdefault_scopeは本当にevilなのか?
まあrails_best_practicesを使ってると怒られますよね、弊社でも導入してますし、default_scope
は使ってません。
有名なのはこの記事ですかね。
Railsのdefault_scopeは悪だ!(default_scope is evil) ということらしい - Qiita
ま、弊社ではdefault_scope
は使ってないとはいえ、個人で開発してるときには使ってみたくなるじゃないですか、だってevilですよ、かっこいいもん。
そしたら痛い目を見たのでその知見を共有したいと思います。
前提
とりあえずこんな感じのModelがあるとしましょう。
これを前提に話を進めますけど、仮定の話なのでコードに矛盾が生じるかもしれませんし、そもそも動かないかもしれません。許してください。
class User < ApplicationRecord has_many :organizations, dependent: :destroy default_scope -> { where violated: false } : :
class Organization < ApplicationRecord default_scope -> { where finished: true } : :
ユーザが複数の組織を持つ感じね。
ユーザは違反したかどうかのviolated
カラムを持っていて、組織はfinished
っていう現役かどうかのカラムを持ってるありがちなやつ。
ちなみにviolated
カラムは今回活躍しません。
じゃあ書くなよって思った!?しょうがないじゃん!手元のコードがそうなってるんだもん!
ActiveRecordRelationを呼んだときにunscopedをかけると親とのwhereも消える
僕がまずぶち当たったのはこいつですね、まじかよと思った。
まずunscoped
しないパターンね。
irb(main):089:0> User.first.organizations User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."violated" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["violated", "f"], ["LIMIT", 1]] Organization Load (0.8ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."finished" = $1 AND "organizations"."user_id" = $2 LIMIT $3 [["finished", "t"], ["user_id", 2], ["LIMIT", 11]]
うんうん。まあそうだよね。でもorganizations
のfinished
は見なくていっかなーってなるとunscoped
するでしょ。
irb(main):090:0> User.first.organizations.unscoped User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."violated" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["violated", "f"], ["LIMIT", 1]] Organization Load (0.5ms) SELECT "organizations".* FROM "organizations" LIMIT $1 [["LIMIT", 11]]
うん?
Organization Load (0.5ms) SELECT "organizations".* FROM "organizations" LIMIT $1 [["LIMIT", 11]]
うわ!user_id
の絞り込みも消えてる!まじかよ!
回避策
たぶんこう。
Organization.unscoped.where(user_id: User.first.id)
has_manyかつdependent: :destroyで親を殺したときに子が見れなくてviolates foreign key constraintで死ぬ
最近じゃTwitterで「殺」って文字が入ってると凍結されるらしいですよ!気をつけて!
もうこれは掲題の通り。 僕は個人で開発するときは絶対に外部キー制約を付けるマンなので・・・。
Userをdestroy
したときに子をdependent: :destroy
で殺しに行くんだけど、そんときにdefault_scope
が効いて殺しに行けないよってやつ。
default_scope
ちゃんどいて!そいつ殺せない!
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: update or delete on table "users" violates foreign key constraint "fk_rails_7b93e0061c" on table "organizations"
ほんとこれはもう、ね。
回避策
class User < ApplicationRecord has_many :organizations, dependent: :destroy default_scope -> { where violated: false } before_destroy { Organization.unscoped.where(user_id: id).destroy_all } # add this code. : :
まとめ
いかがでしたでしょうか。
僕が一番キライなタイプのメディアって「まとめ」が最後にあって「いかがでしたでしょうか」で始まるメディアなんですよね。
あーいう記事は大体クソなので読まないでいいです。僕の経験上。
だいたいクラウドソーシングに投げた「2000文字でお願いします〜」みたいな記事の文字数稼ぎのセクションだから。
えーっと、この記事で何が言いたかったかというと「僕のスキルだと手に負えないのでdefault_scope
はorder
くらいにしとこうと思いました」ということです!
きっと他にもあると思うんですよね、default_scope
で痛い目を見る事例。でも僕はここでフルリファクタリングをかけたので!もうこれ以上は共有できません!
以上だ!
Ristaでは4人目のエンジニアを募集してます
株式会社リスタではたらいてみませんか。
あーあ、ES6も導入されちゃったし、yarn
だし、webpacker
だし、出遅れちゃいますよ。はやくジョインしないと。(ちなみに僕はyarn
もwebpacker
もよくわかってません!)
AndroidやiOSアプリもあるよ。出したばっかりなのでまだまだ裁量もってやれる範囲が大きいよ。
気になったら気軽にお話を聞きに来てね!