47

Given a model with default_scope to filter all outdated entries:

# == Schema Information
#
#  id          :integer(4)      not null, primary key
#  user_id     :integer(4)      not null, primary key
#  end_date    :datetime        

class Ticket < ActiveRecord::Base
  belongs_to :user
  default_scope :conditions => "tickets.end_date > NOW()"
end

Now I want to get any ticket. In this case with_exclusive_scope is the way to go, but is this method protected? Only this works:

 Ticket.send(:with_exclusive_scope) { find(:all) }

Kind of a hack, isn't? So what's the right way to use? Especially when dealing with associations, it's getting even worse (given a user has many tickets):

 Ticket.send(:with_exclusive_scope) { user.tickets.find(:all) }

That's so ugly!!! - can't be the rails-way!?

John Topley
  • 113,588
  • 46
  • 195
  • 237
RngTng
  • 1,229
  • 2
  • 12
  • 22
  • 1
    "why is using the rails `default_scope` such a bad idea?" http://stackoverflow.com/questions/25087336/why-is-using-the-rails-default-scope-often-recommend-against – MrYoshiji Aug 01 '14 at 19:38

3 Answers3

180

FYI for anyone looking for the Rails3 way of doing this, you can use the unscoped method:

Ticket.unscoped.all
brad
  • 31,987
  • 28
  • 102
  • 155
  • 2
    holy crap! I wish this was closer to the top! – Ramy Dec 09 '11 at 21:02
  • 63
    Beware of using the unscoped method though as it will remove all query constraints that may have been added prior. For example, assuming author.books.all returns all books by an author (ordered by id via default_scope), author.books.unscoped.order('books.title').all will actually return ALL books regardless of the author as the unscoped removes the constraint that books.author_id == author.id – douglasr Jan 09 '12 at 22:11
  • 1
    Adding to @douglasr: http://stackoverflow.com/questions/25087336/why-is-using-the-rails-default-scope-often-recommend-against – wrtsprt May 18 '16 at 12:35
  • 1
    As @douglasr already mentioned, be super careful with this. Use instead `unscope(where: :whatever_attribute_you_want_to_unscope)`. This will remove ONLY the scope you want to remove, and keep any association scopes in tact. More here: https://guides.rubyonrails.org/active_record_querying.html#overriding-conditions – Doug Oct 27 '19 at 01:44
36

Avoid default_scope if possible. I think you should really re-ask yourself why you need a default_scope. Countering a default_scope is often messier than it's worth and it should only be used in rare cases. Also, using default_scope isn't very revealing when ticket associations are accessed outside the Ticket model (e.g. "I called account.tickets. Why aren't my tickets there?"). This is part of the reason why with_exclusive_scope is protected. You should taste some syntactic vinegar when you need to use it.

As an alternative, use a gem/plugin like pacecar that automatically adds useful named_scopes to your models giving you more revealing code everywhere. For Example:

class Ticket < ActiveRecord::Base
  include Pacecar
  belongs_to :user
end

user.tickets.ends_at_in_future # returns all future tickets for the user
user.tickets                   # returns all tickets for the user

You can also decorate your User model to make the above code cleaner:

Class User < ActiveRecord::Base
  has_many :tickets

  def future_tickets
    tickets.ends_at_in_future
  end
end

user.future_tickets # returns all future tickets for the user
user.tickets        # returns all tickets for the user

Side Note: Also, consider using a more idiomatic datetime column name like ends_at instead of end_date.

Ryan McGeary
  • 235,892
  • 13
  • 95
  • 104
  • 1
    thx, yeah I know default_scope is evil, but in my specific case, which is a bit more complex as the given one, this is best solution (for now) – RngTng Oct 30 '09 at 13:08
  • More on `default_scope`: Why using the default_scope is recommended against? (http://stackoverflow.com/questions/25087336/why-is-using-the-rails-default-scope-often-recommend-against) – wrtsprt Jul 22 '15 at 13:16
20

You must encapsulate the protected method inside a model method, something like:

class Ticket < ActiveRecord::Base
  def self.all_tickets_from(user)
    with_exclusive_scope{user.tickets.find(:all)}
  end
end
Vlad Zloteanu
  • 8,464
  • 3
  • 41
  • 58
  • hm alright, well make kind of sense, i'll give it a try.. thanks! – RngTng Oct 30 '09 at 13:08
  • 5
    "with_exclusive_scope" is deprecated. In rails 3 and 4 use "unscoped" instead. For mor details and examples have a look at: http://github.com/rails/rails/commit/bd1666ad1de88598ed6f04ceffb8488a77be4385 – Konstantine Kalbazov Nov 20 '13 at 16:58