search
LoginSignup
45

More than 5 years have passed since last update.

posted at

updated at

default_scopeを持つModelへのbelongs_toでunscopedにするconcern

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

このとき Articledefault_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

関連する記事

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
What you can do with signing up
45