This project is archived and is in readonly mode.

#1429 ✓invalid
Chris O'Sullivan

Add singular version of named_scope that returns first object

Reported by Chris O'Sullivan | November 21st, 2008 @ 05:24 PM | in 2.x

So, I find myself creating a bunch of named_scopes designed to return only one object, like this:


class Book
  named_scope :most_recently_read, :order => "last_read desc", :limit => 1
end

Which means I have to chain .first to get what I want like this:


Book.most_recently_read.first

Which just feels plain wrong!

My proposition is adding some way of creating a named_scope that is designed to return only one object.

I've attached two patches (with docs and tests) for this. The first one (add_first_to_named_scope.patch) adds a first option to named_scope, so it looks like this:


named_scope :most_recently_read, :first => true, :order => "last_read desc", :limit => 1

But I think the :first => true format looks pretty ugly.

My second patch (add_singular_named_scope.patch) proposition is to add a new method singular_named_scope. You would define your singular scopes thusly:


singular_named_scope :most_recently_read, :order => "last_read desc", :limit => 1

But..I'm open to suggestions!

Comments and changes to this ticket

  • Chris O'Sullivan

    Chris O'Sullivan November 21st, 2008 @ 05:25 PM

    (The second patch file for the singular_named_scope method)

  • Ryan Bates

    Ryan Bates November 21st, 2008 @ 07:25 PM

    Sorry, perhaps I'm missing something. Is there a reason you don't like doing this?

    
    class Book < ActiveRecord::Base
      named_scope :most_recently_read, :order => "last_read desc"
    end
    Book.recently_read.first
    

    To me this is much more useful and flexible. If you need to list multiple recently read books no need to add a second named scope. And this is chainable with other named scopes. If you have a singular named scope you couldn't chain others after it:

    
    Book.recently_read.favorites.first
    Book.most_recently_read.favorites # wouldn't work
    

    Of course you can always chain the singular one at the end, but in general that seems to go against the grain of named scopes to me.

  • Ryan Bates

    Ryan Bates November 21st, 2008 @ 07:26 PM

    Oops, the first code snippet should be:

    
    class Book < ActiveRecord::Base
      named_scope :recently_read, :order => "last_read desc"
    end
    Book.recently_read.first
    
  • Chris O'Sullivan

    Chris O'Sullivan November 23rd, 2008 @ 09:22 PM

    I think it IS in the spirit to want to return one object from a named_scope. After all, .first and .last return only one object (although I guess they're not 'technically' named_scopes.) This patch would allow developers to create named_scopes like oldest or largest - (or if you're so inclined second through forty_second).

    With traditional ActiveRecord finders we don't have to write code like:

    
    Book.find(:all, :order => "last_read desc").first
    

    So why should named_scopes be any different?

    I also prefer the readability of something like:

    Book.last_read

    rather than

    Book.recently_read.first

    And there's no reason I can't have both scopes, one to define the last book that was read, and one to define a list of books ordered by 'most recently read'.

    But, of course there's also nothing to stop me defining a class method like:

    
    def self.last_read
      self.recently_read.first
    end
    
  • Chris Kilmer

    Chris Kilmer February 19th, 2009 @ 05:28 PM

    To continue the conversation a little bit. We are currently looking at this exact problem. We want a particular model to return a default. The model can have one and only one model.

    We currently have:

    Device.default.first

    (where Device.default is a named_scope)

    First seems redundant. Default should imply 1.

  • MatthewRudy

    MatthewRudy February 25th, 2009 @ 03:45 PM

    I dont mind having some sort of declaration like this.

    But it isn't a "named_scope" by any means

    
    Book.most_recently_read.count => Exception,
    Book.most_recently_read.in_stock => Exception,
    

    It doesn't scope anything. It doesnt even use the named scope.

    If I were implementing it I'd called it "named_finder"

    
    named_finder :most_recently_read, :first, :order => "last_read desc"
    

    or

    
    named_scope :recently_read, :order => "last_read DESC" do
      named_finder :most_recently_read, :first
    end
    

    It feels like an abuse of the name and the code with the patches submitted so far.

  • MatthewRudy

    MatthewRudy February 25th, 2009 @ 03:52 PM

    
    def named_finder(name, *find_args)
      (class << self; self end).instance_eval do 
        define_method name do
          self.find(*find_args)
        end
      end
    end
    

    and it actually seems to work

    
    >> class User
    >>   named_finder :first_matthew, :first, :conditions => {:firstname => "Matthew"}
    >> end
    => #<Proc:0x03eed6f0@(irb):4>
    
    >> User.first_matthew
    => #<User id: 633, a_whole_load_of_stuff: "redacted", html_email: true>
    

    nice.

  • MatthewRudy

    MatthewRudy February 25th, 2009 @ 03:56 PM

    (I need to have just one lighthouse user)

  • Pratik

    Pratik March 9th, 2009 @ 01:33 PM

    • Assigned user set to “Pratik”
    • Tag changed from named_scope to named_scope, patch
    • State changed from “new” to “invalid”
    • Title changed from “[PATCH] Add singular version of named_scope that returns first object” to “Add singular version of named_scope that returns first object”

    I don't think named_scopes should be returning a single object. It's scoping, not a finder.

    But please do discuss in the mailing list if you guys think it's worth it.

    Thanks.

  • Timothy Jones

    Timothy Jones February 10th, 2010 @ 05:34 PM

    Anyone care to create a plugin/gem for this, since many of us think it a good idea but it didn't meet Pratik's personal taste?

  • Jeremy Kemper

    Jeremy Kemper February 10th, 2010 @ 06:31 PM

    Let's see a wider body of examples of where you'd use a singular finder+scope.

    Every one I've seen (or thought of) is something like latest: latest version, latest comment, etc. Order + find first.

    Others?

  • Timothy Jones

    Timothy Jones February 10th, 2010 @ 06:49 PM

    Sometimes the result set contains exactly one (or at most one) record, or I just one one random record and really don't care which it is, so order isn't necessary.

    For example, in a geographic database containing US cities and states I have a named_scope "Location.city_in_state(city_name, state_abbreviation)". Having to suffix that with ".first" everyplace it's used throughout the application isn't very DRY, is it?

    Basically, named_scope() should have a :first mode for the same reason that find() has a :first mode.

  • DHH

    DHH February 10th, 2010 @ 07:47 PM

    Tim, why not do Location.find_by_city_and_state(city_name, state_abbreviation) in that case?

  • Timothy Jones

    Timothy Jones February 10th, 2010 @ 07:54 PM

    In this case my query is more complicated than the attribute-based finders support AFAIK (specifically it requires a join).

  • Timothy Jones

    Timothy Jones February 10th, 2010 @ 08:05 PM

    Ok, I take that back, now realizing that attribute-based finders do accept general find() options. But the whole point of named_scopes is syntactic sugar, right?

    My Location model is generic, so locations have a name, type, and potentially multiple parents. In the US, cities' parents are counties, then states. In Canada, cities are parented directly by states. Here's my (admittedly gnarly) named_scope:

    named_scope(

    :city_in_state,
    lambda do |c,s|
      {
        :select => "locations.*",
        :joins => "JOIN locations_parents AS l_p ON locations.id = l_p.location_id LEFT JOIN locations AS counties ON l_p.parent_id = counties.id AND counties.location_type_id = #{LocationType.find_by_name('county').id} LEFT JOIN locations_parents AS c_p ON counties.id = c_p.location_id JOIN locations AS states ON (c_p.parent_id = states.id OR l_p.parent_id = states.id) AND states.location_type_id = #{LocationType.find_by_name('state').id}",
        :conditions => ["locations.location_type_id = ? AND locations.name = ? AND (states.name = ? OR states.abbreviation = ?)", LocationType.find_by_name("city").id, c, s, s]
      }
    end
    

    )

    If you can show me how to write that with current finders in a way that's not drastically uglier than:

    city = Location.city_in_state(city_name, state_name)

    then I'll shut up :-).

  • Lachlan Sylvester

    Lachlan Sylvester February 11th, 2010 @ 10:43 AM

    Tim, why not define a scope to find cities within a state, then you can use find_by_name to get the city.

    city = Location.cities_in_state(state_name).find_by_name(city)
    
  • Timothy Jones

    Timothy Jones February 11th, 2010 @ 06:07 PM

    Yes, that would work and is not drastically uglier than what I proposed.

  • Ryan Bigg

    Ryan Bigg October 9th, 2010 @ 10:03 PM

    • Tag cleared.
    • Importance changed from “” to “Low”

    Automatic cleanup of spam.

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

Pages