This project is archived and is in readonly mode.

#57 ✓invalid
MatthewRudy

:through option for "named_scope"

Reported by MatthewRudy | April 27th, 2008 @ 11:59 PM

The new "named_scope" functionality is great.

But it feels like it should have the option of specifying a :through scope name,

through which the scope will be defined.

(my git patch has a more full description)

Comments and changes to this ticket

  • MatthewRudy

    MatthewRudy April 28th, 2008 @ 12:00 AM

    ==============

    from the patch

    ==============

    Subject: [PATCH] adding :through option to "named_scope"

    named_scope is a welcome addition to rails core,

    and it's a treat to see examples like;

    Topic.approved.replied

    working right out of the box.

    My patch aims to make it easier to define a combined scope;

    named_scope :approved_and_replied, :conditions =>

    ['replies_count > 0'], :through => :approved

    My patch adds this functionality, as well as allowing arrays of :through

    scopes (the array case needs a test to be added, but I need some advice

    on how to extend the Topic class in test/models to allow this)

    A clear next step is to allow the following;

    :through => {:written_before => "2007-01-01", :approved => nil}

    which would call the "written_before" scope, passing it the value as

    argument

    A more amibitious aim is to allow arguments to accumulate from :through

    scopes,

    eg.

    named_scope :written_before_and_replied, :conditions => *blah*,

    :through => :written_before

    would yield a scope object which passed the first argument to

    :written_before

    Topic.written_before_and_replied(1.year.ago)

    same as Topic.written_before(1.year.ago).replied

  • MatthewRudy

    MatthewRudy April 28th, 2008 @ 10:24 PM

    • Assigned user set to “Pratik”
  • DHH

    DHH April 30th, 2008 @ 08:45 PM

    • State changed from “new” to “incomplete”

    Looking good, but missing docs and tests. Please change status to open when fixed.

  • MatthewRudy

    MatthewRudy May 2nd, 2008 @ 08:43 PM

    thanks David,

    will get to that tomorrow.

  • duncanbeevers

    duncanbeevers May 2nd, 2008 @ 11:02 PM

    I've been thinking about similar functionality, and thus far I've found it most flexible to simply specify composite scopes as proper methods.

    def self.written_before_and_replied time
      written_before(time).replied
    end
    

    I feel the added complexity of the implementation of a :through option that decomposes arguments for passing to various scopes outweighs the benefits of the slightly shorter syntax, especially in the case of named scopes with optional or splat arguments.

    http://rails.lighthouseapp.com/p...

  • MatthewRudy

    MatthewRudy May 3rd, 2008 @ 12:46 AM

    i think my current test example doesn't quite explain it well.

    the point is,

    why create a scope, just so you can combine it with another one.

    we want 1st condition,

    we want 1st and 2nd condition combined,

    but we may not want 2nd condition on its own...

    so creating a scope, just so we can compose it,

    doesnt make sense.

    example:

    class Monkey < ActiveRecord::Base
      # we assume only living monkeys have use
      named_scope :living, :conditions => ["dead = ?", false]
      named_scope :useful, :conditions => ["nimble = ? AND randy = ?", true, true], :through => :living
    end
    

    we have no use for finding any dead monkeys who are nimble and randy,

    so we cut the unneccessary scope.

    but yeah,

    i need to add some examples to the documentation,

    and get the testing up to scratch.

  • MatthewRudy
  • MatthewRudy

    MatthewRudy May 4th, 2008 @ 11:32 AM

    proper testing, bit of documentation.

    will do one more bit of testing later.

  • MatthewRudy

    MatthewRudy May 9th, 2008 @ 12:41 PM

    hmm...

    still need to do this,

    Tests that are missing are;

    "doesnt work with Proc"

    "doesnt work through named_scopes with Proc"

    I wonder if these shortcomings should be fixed, before I reopen it

    • that's why I have written the tests yet.

    Making it work with Procs would involve a much more complex implementation, I think.

    Should really put in the time over the weekend to try and make it work.

  • Mathijs Kwik

    Mathijs Kwik May 21st, 2008 @ 10:26 AM

    I would like to add some thoughts on this topic.

    Combining/composing/through'ing named_scopes would be nice to have indeed, but I would like to mention that this feature would be very good on associations.

    class Product < ActiveRecord::Base
      belongs_to :category
      named_scope :cheap, :conditions => { :price => 0..5 }
      named_scope :recent, lambda { |*args| {:conditions => ["released_at > ?", (args.first || 2.weeks.ago)]} }
      named_scope :visible, :include => :category, :conditions => { 'categories.hidden' => false }
    end
    

    In this example 'visible' is just a simple attribute, but it's from an associated model (category). What if category had more attributes, like hidden, private, public, read_only and stuff like that. Say Category already has some named_scopes, for example 'free_for_all' which means a category is public but not read-only.

    Now it would make sense (maybe not in this example but in general) to be able to use these named_scopes in the Product model, not on instances only, but in named_scopes themselves as well. So Product.in_free_for_all can just use the 'free_for_all' scope in Category. Otherwise you would have to define this 'logic' twice, which isn't very DRY. I hope my explanation is clear enough...

    Of course I know that I could also use Category.free_for_all.map(&:products), but then all products get fetched right away which means sorting them (and other nice stuff finders have) will have to be done in ruby instead of in the DB.

    A solution I use at the moment is this, but I'm not fully satisfied with it yet, so any comments are very welcome...

    class Product << ActiveRecord::Base
      belongs_to :category
      named_scope :in_free_for_all, lambda {{ :conditions => {:category_id => Category.free_for_all.find(:all, :select => 'id').map(&:id)} }}
    end
    

    Which will do 2 queries, which is default for :include in rails 2.1 anyway I believe.

    While this solution works, it would be a lot nicer if rails could somehow write a subquery for it. Along the lines of:

    (example, doesn't work)

    named_scope :in_free_for_all, :conditions => {:category => Category.free_for_all}
    

    should translate to:

    select * from products where category_id in (select id from categories where public = 't' and read_only = 'f')

    I thought I'd mention this 'wishlist-item' in this ticket, since it's very related.

    Thanks,

    Mathijs

  • Ryan Bigg

    Ryan Bigg April 10th, 2010 @ 09:40 PM

    • Tag set to activerecord, edge, named_scope, through

    I was under the impression that chaining scopes did this.

  • Ryan Bigg

    Ryan Bigg April 10th, 2010 @ 11:59 PM

    • State changed from “incomplete” to “needs-more-info”

    Yes, I think that if you were to chain the named_scopes or even use anonymous scopes it would work. I'm open to further discussion on this ticket.

  • Ryan Bates

    Ryan Bates April 11th, 2010 @ 05:17 AM

    This is possible with the new Active Record query syntax added in Rails 3.

    scope :visible, where(:hidden => false)
    scope :available, visible.where(:discontinued => false)
    

    This is also much cleaner than the :through solution provided in the ticket.

  • MatthewRudy

    MatthewRudy April 11th, 2010 @ 12:22 PM

    that's all fair enough
    this was 2 years ago

    the problem with the original ticket
    (back when it was necessary) was that there was no clear way to deal with lambda-based scopes
    so... i ended up never having a complete solution

    feel free to close this off

    or perhaps add documentation to the "scope" method
    pointing out that you can use an existing scope as part of the constructor.

  • Rohit Arondekar

    Rohit Arondekar June 15th, 2010 @ 12:05 PM

    • State changed from “needs-more-info” to “invalid”
  • Oinak

    Oinak July 20th, 2010 @ 02:06 PM

    • Importance changed from “” to “”

    You can use proxy_options to use one named_scope from another:

        class Thing
          #...
          named_scope :billable_by, lambda{|user| {:conditions => {:billable_id => user.id } } }
          named_scope :billable_by_tom, lambda{ self.billable_by(User.find_by_name('Tom').id).proxy_options }
          #...
        end
    

    This way it can be chained with other named_scopes (as an advatange to use custom class methods).

    I use this in my code and it works perfectly.

    It was discussed here and here

  • Neeraj Singh

    Neeraj Singh July 20th, 2010 @ 02:36 PM

    class User < ActiveRecord::Base
      named_scope :billable_by, lambda{|user| {:conditions => {:billable_id => user.id } } }
      named_scope :billable_by_tom, lambda{ self.billable_by(User.find_by_name('Tom').id).proxy_options }
    end
    

    Above code does not work in rails edge. There is no method called 'proxy_optioins' in edge.

  • Ryan Bates

    Ryan Bates July 20th, 2010 @ 05:05 PM

    Neeraj, In Rails 3 you can just call the scope directly.

    class User < ActiveRecord::Base
      named_scope :billable_by, lambda{|user| {:conditions => {:billable_id => user.id } } }
      named_scope :billable_by_tom, lambda{ billable_by(User.find_by_name('Tom')) }
    end
    
  • Oinak
  • Neeraj Singh

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>

Referenced by

Pages