default_scope
を持つModelへの belongs_to
では勝手に default_scope
が使われるので自分で頑張ってオーバーライドする必要があります。
例えば公開・非公開設定を持つ Article
とそれに紐づく Comment
があるとします。
app/models/article.rb
class Article < ActiveRecord::Base
default_scope -> { where(private: false) } # private な記事を排除する
end
app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :article
end
このとき Article
の default_scope
が思わぬ形で影響する場合があります。
# 公開ArticleへのCommentからは問題なく辿れる
public_article = Article.create(private: false)
public_comment = Comment.create(article: public_article)
Comment.find(public_comment.id).article #=> Article
# 非公開ArticleへのCommentからはたどることができない
private_article = Article.create(private: true)
private_comment = Comment.create(article: private_article)
comment = Comment.find(private_comment.id)
comment.article_id #=> 2
comment.article #=> nil
comment.article.to_sql #=> ... WHERE items.private = 0 AND items.id = 2
これを回避するために Comment#article
を上書きしたりします
app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :article
def article
Article.unscoped { super }
end
end
で、複数のModelが Article
に紐づくとこれを毎回書くのはカッコ悪いし、 super
を使うのもなんか嫌だったのでこちらの記事を参考に concern にしてみました。
app/models/concerns/unscoped_associations.rb
module UnscopedAssociations
extend ActiveSupport::Concern
module ClassMethods
def belongs_to_with_unscoped(model, options = {})
belongs_to_without_unscoped(model, options.reject { |k, v| k == :unscoped })
if options[:unscoped]
define_method "#{model}_with_unscoped" do
model.to_s.camelize.constantize.unscoped do
send("#{model}_without_unscoped")
end
end
alias_method_chain model, :unscoped
end
end
end
included do
class << self
alias_method_chain :belongs_to, :unscoped
end
end
end
こんな感じで使います
app/models/comment.rb
class Comment < ActiveRecord::Base
include UnscopedAssociations
belongs_to :article, unscoped: true
end
便利
comment = Comment.find(private_comment.id)
comment.article #=> Article
comment.article_without_unscoped #=> nil