前提環境
$ ruby -v
ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x64-mingw32]
$ rails -v
Rails 7.0.1
問題
ツイッターのクローンアプリを学習用に作っていて、ツイート系のモデルがTweet < ApplicationRecord
、NormalTweet < Tweet
、Reply < NormalTweet
となる設計で実装しました。
※Tweetはツイートの作成者author
、NormalTweetはツイート内容text
、Replyは返信先reply_to
を持ちます。
それで開発中NormalTweet.find()
にその子クラスであるReply
が引っかるときと引っかからない時がある!(Couldn't find NormalTweet with 'id'=n (ActiveRecord::RecordNotFound)
)という問題が発生。
原因
Rails(のデフォルトではdevelopment環境)では、クラスが使用されるときにロードされるようになっています。(lazy loading
)
その時、NormalTweet
クラスは読み込まれるものの、その子クラスであるReply
クラスは読み込まれていないことが原因でした。
対処
STIクラスをプリロードするようにします。
問題が起きたため、Railsガイドのものを少しだけ変更しています。
-
constantize
をsafe_constantize
に変更 - プリロード中にプリロードが起こらないように変更
lib/sti_preload.rb
としてファイルを作成
module StiPreload
unless Rails.application.config.eager_load
extend ActiveSupport::Concern
included do
cattr_accessor :preloaded, instance_accessor: false
cattr_accessor :preloading, instance_accessor: false
end
class_methods do
def descendants
preload_sti unless preloaded
super
end
# Constantizes all types present in the database. There might be more on
# disk, but that does not matter in practice as far as the STI API is
# concerned.
#
# Assumes store_full_sti_class is true, the default.
def preload_sti
return if self.preloading
self.preloading = true
types_in_db = \
base_class.
unscoped.
select(inheritance_column).
distinct.
pluck(inheritance_column).
compact
types_in_db.each do |type|
logger.debug("Preloading STI type #{type}")
type.safe_constantize # Railsガイドのものから変更: safeでないconstantizeを使うと例外が発生して正常に動作しない
end
self.preloading = false
self.preloaded = true
end
end
end
end
モデル(ルートのみ)にrequire文、include文を追加
require "sti_preload"
class Tweet < ApplicationRecord
belongs_to :author, class_name: "User"
# このincludeは一番下!!!!!!!絶対です!!!!!
include StiPreload
end
ここでinclude
は一番下に置くことに注意!
これが上にあると、その下のアソシエーションなどが設定されない状態でクラスがロードされてしまうので、Association named 'xxx' was not found on xxx; perhaps you misspelled it? (ActiveRecord::AssociationNotFoundError)
などのエラーが発生してハマります!!!
この問題の原因、対処方法などは https://ruby-jp.slack.com/ で教えて頂きました。
回答して頂いた方々にはこの場をお借りして感謝申し上げます。ありがとうございました。