This project is archived and is in readonly mode.

#2953 ✓stale
Conrad Taylor

without_exclusive_scope and default_scope issues

Reported by Conrad Taylor | July 25th, 2009 @ 05:55 AM | in 3.x

I'm seeing the following issues with both without_exclusive_scope and default_scope in Rails 2.3.3. For example, given the following:


class Message < ActiveRecord::Base
  
  default_scope :conditions => { :ignored => false }, :order => "rating DESC"
  named_scope :positive, :conditions => { :rating => 'positive' }
  named_scope :neutral, :conditions =>  { :rating => 'neutral' }
  named_scope :negative, :conditions => { :rating => 'negative' }
  named_scope :ignored, :conditions =>  { :ignored => true }
  
end

In the console,


> Message.ignored

=>  SELECT * FROM "messages" WHERE ("messages"."ignored" = 'f') ORDER BY rating DESC

Next, when trying to use the 'with_exclusive_scope method, the following message is generated:


>> Message.with_exclusive_scope { find( :all ) }
NoMethodError: protected method `with_exclusive_scope' called for #<Class:0x793538>
    from /opt/local/lib/ruby/gems/1.9.1/gems/activerecord-2.3.3/lib/active_record/base.rb:1959:in `method_missing'
    from (irb):9
    from /opt/local/bin/irb:12:in `<main>'

Comments and changes to this ticket

  • Matt Jones

    Matt Jones July 26th, 2009 @ 12:47 AM

    As to the second half, with_exclusive_scope was made protected along with with_scope a while back. You're probably better off wrapping the with_exclusive_scope call inside a model method.

    There's definitely something weird going on with the first case - if you create a second model class, identical except for the default scope and try:

    MessageWithoutDefault.scoped(:conditions => { :ignored => false }).ignored
    
    ...then you get the correct behavior.

    I'm investigating and will post an update (hopefully) soon.

  • Matt Jones

    Matt Jones July 26th, 2009 @ 03:38 AM

    On further investigation, here's the situation:

    Chaining two named_scopes does something (somewhat) unexpected - they nest in the reverse order.

    In other words, this:

    MessageWithoutDefault.scoped(:conditions => { :ignored => false }).ignored.all
    
    executes as:
    MessageWithoutDefault.with_scope([PARAMS FOR IGNORED]) do
      MessageWithoutDefault.with_scope([PARAMS FOR IGNORED => FALSE]) do

    MessageWithoutDefault.find(:all)
    
    
    
    
    end end
    would if with_scope was public. The outer with_scope parameters take precedence. This works fine normally, but adding a default_scope is equivalent to this:
    MessageWithoutDefault.with_scope([PARAMS FOR DEFAULT SCOPE]) do # NOTE: not actually executed - default scope is inserted differently
      MessageWithoutDefault.with_scope([PARAMS FOR IGNORED]) do

    MessageWithoutDefault.with_scope([PARAMS FOR IGNORED =&gt; FALSE]) do
      MessageWithoutDefault.find(:all)
    end
    
    
    
    
    end end
    which produces the (observed) incorrect results.

    There's a bigger issue here, though; with_scope tends to flatten Hash conditions to strings, making it hard to override :conditions in default_scope. For instance, doing this:

    Message.ignored.scoped(:conditions => ['rating LIKE ?', 'p%'])

    => SELECT * FROM "messages" WHERE ((("messages"."ignored" = 'f') AND (rating LIKE 'p%')) AND ("messages"."ignored" = 't')) ORDER BY rating DESC

    
    
    
    which will never return any records. The issue here is that the first part of the query (before the last AND) is generated as a string by merge_conditions, so there's no chance to override it.

    Maybe some of the ActiveRelation stuff going on in the GSoC will help with this?

  • Chuck Hoffman

    Chuck Hoffman November 24th, 2009 @ 05:24 PM

    I seem to be experiencing this same issue. Here's a description I posted earlier at https://gist.github.com/ea8322b9c7383835101c

    class MyModel < ActiveRecord::Base
    
      belongs_to :parent_model
    
      # ...
    
      default_scope :conditions => { :is_hidden => false }
      named_scope :primary, :conditions => { :is_primary => true },
        :order => "position"
      named_scope :secondary, :conditions => { :is_primary => false },
        :order => "position"
      
    
    p = ParentModel.first
    p.my_models            #<-- works fine
    p.my_models.primary    #<-- works fine
    p.my_models.secondary  #<-- works fine
    
    # what I need is a finder method/scope to find on the condition { :is_hidden => true }
    # preferably one that also works through the belongs_to :parent association.
    
    # here are some things I've tried:
    
      named_scope :hidden, :conditions => { :is_hidden => true }
    # p.my_models.hidden seems to end up acting identically to p.my_models.secondary in this
    # case.  Might also be the case if called on the class as MyModel.hidden
    # (see https://rails.lighthouseapp.com/projects/8994/tickets/2953-without_exclusive_scope-and-default_scope-issues)
    
      def self.find_with_hidden(*args)
        self.with_exclusive_scope { find(*args) }
      end
    # here I'm adapting the find_with_destroyed method shown on
    # http://blog.semanticart.com/using_default_scope_to_recreate_acts_as_paranoid/
    # unfortunately, it won't work correctly through an association: calling
    # p.my_models.find_with_hidden(:all) returns all MyModels, not just those that
    # belong to p -- in other words, with_exclusive_scope is TOO exclusive because
    # it loses the association scope.
    
    # But if I try passing the above more conditions to get the results I want, I get errors:
    
      MyModel.find_with_hidden(:conditions => { :parent_model_id => 1 })
    # gets this error:
    # ActiveRecord::RecordNotFound: Couldn't find MyModel without an ID
    #   from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:1567:in `find_from_ids'
    #   from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:616:in `find'
    #   from /home/chuck/projects/diyseo/app/models/page_keyword.rb:34:in `find_with_business_name'
    #   from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2143:in `with_scope'
    #   from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2151:in `with_exclusive_scope'
    #   from /home/chuck/projects/diyseo/app/models/page_keyword.rb:34:in `find_with_business_name'
    
      MyModel.find_with_hidden(:conditions => { :is_hidden => true })
    # ActiveRecord::RecordNotFound: Couldn't find MyModel without an ID
    #   from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:1567:in `find_from_ids'
    #   from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:616:in `find'
    #   from /home/chuck/projects/diyseo/app/models/page_keyword.rb:34:in `find_with_business_name'
    #   from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2143:in `with_scope'
    #   from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2151:in `with_exclusive_scope'
    #   from /home/chuck/projects/diyseo/app/models/page_keyword.rb:34:in `find_with_business_name'
    
    # Likewise if I try calling the above from an instance method on MyModel like this:
      def self.hidden_for(parent)
        MyModel.find_with_hidden(:conditions => {
                                   :parent_model_id => parent.id,
                                   :is_hidden       => true
                                 })
      end
    # calling:
      p.my_models.hidden_for(p)
    # or:
      MyModel.hidden_for(p)
    # both result in the "Couldn't find MyModel without an ID" error above
    
  • Jeremy Kemper

    Jeremy Kemper May 4th, 2010 @ 06:48 PM

    • Milestone changed from 2.x to 3.x
  • Rohit Arondekar

    Rohit Arondekar October 9th, 2010 @ 04:01 AM

    • State changed from “new” to “stale”
    • Importance changed from “” to “”

    Marking ticket as stale. If this is still an issue please leave a comment with suggested changes, creating a patch with tests, rebasing an existing patch or just confirming the issue on a latest release or master/branches.

  • bingbing

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

Attachments

Pages