Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

:through option for "named_scope"

#57

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)

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

State: invalid
Milestone: none
Assigned to: Pratik Pratik
Importance: none

Activity

  1. MatthewRudy
    MatthewRudy

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

    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

    April 28th, 2008 @ 12:00 AM

  2. MatthewRudy
    MatthewRudy
    • Assigned user set to Pratik

    April 28th, 2008 @ 10:24 PM

  3. DHH
    DHH
    • State changed from new to incomplete

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

    April 30th, 2008 @ 08:45 PM

  4. MatthewRudy
    MatthewRudy

    thanks David,

    will get to that tomorrow.

    May 2nd, 2008 @ 08:43 PM

  5. duncanbeevers
    duncanbeevers

    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...

    May 2nd, 2008 @ 11:02 PM

  6. MatthewRudy
    MatthewRudy

    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.

    May 3rd, 2008 @ 12:46 AM

  7. MatthewRudy
  8. MatthewRudy
    MatthewRudy

    proper testing, bit of documentation.

    will do one more bit of testing later.

    May 4th, 2008 @ 11:32 AM

  9. MatthewRudy
    MatthewRudy

    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.

    May 9th, 2008 @ 12:41 PM

  10. Mathijs Kwik
    Mathijs Kwik

    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

    May 21st, 2008 @ 10:26 AM

  11. Ryan Bigg
    Ryan Bigg
    • Tag set to activerecord, edge, named_scope, through

    I was under the impression that chaining scopes did this.

    April 10th, 2010 @ 09:40 PM

  12. Ryan Bigg
    Ryan Bigg
    • 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.

    April 10th, 2010 @ 11:59 PM

  13. Ryan Bates
    Ryan Bates

    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.

    April 11th, 2010 @ 05:17 AM

  14. MatthewRudy
    MatthewRudy

    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.

    April 11th, 2010 @ 12:22 PM

  15. Rohit Arondekar
    Rohit Arondekar
    • State changed from needs-more-info to invalid

    June 15th, 2010 @ 12:05 PM

  16. Oinak
    Oinak
    • 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

    July 20th, 2010 @ 02:06 PM

  17. Neeraj Singh
    Neeraj Singh
    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.

    July 20th, 2010 @ 02:36 PM

  18. Ryan Bates
    Ryan Bates

    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
    

    July 20th, 2010 @ 05:05 PM

  19. Oinak
  20. Neeraj Singh