Suppose I have a Post model, and a Comment model. Using a common pattern, Post has_many Comments.
If Comment has a default_scope set:
default_scope w
How about this?
# Use this scope by default
scope :active, -> { where(deleted_at: nil) }
# Use this whenever you want to include all comments regardless of their `deleted_at` value
scope :with_soft_deleted, -> { unscope(where: :deleted_at)
default_scope, -> { active }
post.comments
would fire this query:
SELECT "comments".* FROM "comments" WHERE "comments"."deleted_at" IS NULL AND "comments"."post_id" = $1;
post.comments.with_soft_deleted
would send this:
SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1;
Rails 4.1.1
Comment.unscope(where: :deleted_at) { Post.first.comments }
Or
Comment.unscoped { Post.first.comments.scope }
Note that I added .scope
, it seems like this block should return kind of ActiveRecord_AssociationRelation
(what .scope
does) not ActiveRecord_Associations_CollectionProxy
(without a .scope
)
class Comment
def post_comments(post_id)
with_exclusive_scope { find(all, :conditions => {:post_id => post_id}) }
end
end
Comment.post_comments(Post.first.id)
For some strange reasons,
Comment.unscoped { Post.last.comments }
includes the default_scope
of Comment
,
however,
Comment.unscoped { Post.last.comments.to_a }
Comment.unscoped { Post.last.comments.order }
do not include the default_scope
of Comment
.
I experienced this in a rails console
session with Rails 3.2.3
.
with_exlusive_scope
is deprecated as of Rails 3. See this commit.
Before (Rails 2):
Comment.with_exclusive_scope { Post.find(post_id).comments }
After (Rails 3):
Comment.unscoped { Post.find(post_id).comments }
This is indeed a very frustrating problem which violates the principle of least surprise.
For now, you can just write:
Comment.unscoped.where(post_id: Post.first)
This is the most elegant/simple solution IMO.
Or:
Post.first.comments.scoped.tap { |rel| rel.default_scoped = false }
The advantage of the latter:
class Comment < ActiveRecord::Base
# ...
def self.with_deleted
scoped.tap { |rel| rel.default_scoped = false }
end
end
Then you can make fun things:
Post.first.comments.with_deleted.order('created_at DESC')
Since Rails 4, Model.all returns an ActiveRecord::Relation , rather than an array of records.
So you can (and should) use all
instead of scoped
:
Post.first.comments.all.tap { |rel| rel.default_scoped = false }