This project is archived and is in readonly mode.
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 November 21st, 2008 @ 05:25 PM
(The second patch file for the singular_named_scope method)
-
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 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 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 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 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 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.
-
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 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 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 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 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 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 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 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 February 11th, 2010 @ 06:07 PM
Yes, that would work and is not drastically uglier than what I proposed.
-
Ryan Bigg October 9th, 2010 @ 10:03 PM
- Tag cleared.
- Importance changed from to Low
Automatic cleanup of spam.
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>