Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Tools
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker

HasFinder — It's Now Easier than ever to create complex, re-usable SQL queries

Pivotal Labs
Sunday, September 2, 2007

HasFinder is an extension to ActiveRecord that makes it easier than ever to create custom find and count queries.

Let’s start with an example. Suppose you have an Article model; some articles are published, some are popular. Let’s declare finders for each of these notions. You may be tempted to write something like the following:

class Article < ActiveRecord::Base
  def self.published
    find(:all, :conditions => {:published => true})
  end

  def self.popular
    ...
  end
  ...
end

But there are serious limitations to this approach. How do you find articles that are BOTH popular and published? How can you easily paginate published articles?

HasFinder Features

Let’s define the equivalent finders using my new plugin, has_finder:

class Article < ActiveRecord::Base
  has_finder :published, :conditions => {:published => true}
  has_finder :popular, :conditions => ...
end

Query Composition

Now, you can elegantly compose queries:

Article.published.popular

This will return all articles that are both popular and published.

Calculations and Nested Finds

You can also easily paginate or call nested finders or do calculations:

Article.published.paginate(:page => 1)
Article.published.popular.count
Article.popular.find(:first)
Article.popular.find(:all, :conditions => {...})

Works with ActiveRecord has_many and has_many :through Associations

Furthermore, without any additional work, these finders will work with ActiveRecord associations.

class User
  has_many :articles
end

user.articles.popular.find(:first)
user.articles.published.popular.average(:view_count)

Finders are extendable just like ActiveRecord Associations

class Article
  has_finder :unpublished :conditions => {:published => false} do
    def published_all
      find(:all).map(&:publish)
    end
  end
end

Alternatively, you can use the :extend options:

class Article
  has_finder :unpublished, :conditions => ..., :extend => MyExtensionModule
end

Finders behave just like ActiveRecord Associations

For example, you can call #reload:

Article.published.popular.reload

Finders can take parameters

class Car
  has_finder :colored, lambda {|color| { :conditions => {:color => color} } }
end

Car.colored('red').paginate(:page => 1)

What makes HasFinder better than alternatives like scope_out and scoped_proxy?

There are already two plugins similar to HasFinder: scope_out and scoped_proxy. Both of them are excellent. In fact, Scoped Proxy was the model for HasFinder. Unfortunately, neither plugin provided all of the features I desired. Scope-Out lacks on-the-fly composition, a nice way to call a nested find, or the ability to do arbitrary calculations. Scoped Proxy is great, but it doesn’t work with regular ActiveRecord Associations, it is not extendable like ActiveRecord associations, and it doesn’t behave exactly like a regular ActiveRecord Association. Neither of them work out of the box with will_paginate. For all of these reasons and more, I rolled my own. It’s now available as a gem.

Installation

% gem install has_finder

Usage

In environment.rb:

gem 'has_finder'
require 'has_finder'

See the examples above for usage.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

81 Comments

  1. jeffb says:

    I’m trying to dynamically add has_finder methods to model classes. As my first exploration I tried the following in a model class:

    content_columns.select {|c| c.type == :string}.each do |column|
    has_finder column.name.to_sym,
    lambda {|param| { :conditions => {column.name.to_sym => param} } }
    end

    Unfortunately this isn’t working. I’m seeing the following error many times when I true to use one of these finders:

    /path/to/my/mode_model.rb:42: warning: multiple values for a block parameter (0 for 1)
    from /path/to/my/rails_root/vendor/plugins/has_finder/lib/has_finder/has_finder.rb:55

    Any idea how I might be able to pull this off?

    Thanks.

    February 8, 2008 at 8:30 pm

  2. Jörg Battermann says:

    Hello there,

    has anyone used has_finder successfully with has_many_polymorphs associations?

    Basically I have an user model which has many items (which can be of type :a, :b and :c) and for example I want to get all UserItems which have been created yesterday (finding within the hmp user.items associations). I am kinda confused atm because it isn’t working as expected :-/

    _j

    February 24, 2008 at 1:16 pm

  3. Jörg Battermann says:

    Markdown obviously messes with the _’es ;)

    Anyway – what I forgot to add was the setup: I have one User model, one UserItem model and several other models A, B and C. UserItem is the join model for the hmp associations. I placed e.g. this finder in there:

    has_finder :created_today, :conditions => ['created_at > ?', Date.yesterday]

    Now when I fetch an User u and do a u.items.created_today, I get a big, bad exception:

    ActiveRecord::StatementInvalid: Mysql::Error: Column ‘created_at’ in where clause is ambiguous: …. :-/

    A, B and C have all the typical 2 rails timestamp columns and so do the useritems.

    Any Idea what’s wrong here?

    February 24, 2008 at 1:29 pm

  4. nick says:

    Jorg,

    HMP scares me – I’ve used it before and with no good effect. It looks like a simple workaround to your problem would be:

    has_finder :created_today, :conditions => ['users.created_at > ?', Date.yesterday]

    Note the prefix on the created_at column. Also, bear in mind that doing Date.yesterday is really dangerous as the statement gets evalled at class-eval time, meaning if your code is long-running, yesterday may be 5 days ago, etc.

    Two workarounds for that are: 1) use the lambda form, 2) put the date in the sql clause, not using bind (the question-mark).

    February 24, 2008 at 6:04 pm

  5. Jason Arora says:

    Hi Nick,

    I found a bug with finders that only do an “order by”. For example:

    hasfinder :popular, :order => ‘hits desc’
    hasfinder :alphabetical, :order => ‘name asc’

    Topic.popular.alphabetical would only do “order by hits desc” and not “order by hits desc, name asc”.

    You’ve created a wonderful gem. Any update on if and when it will be available as a plugin or can we hope to see this in the official Rails release soon?

    Regards,
    Jason

    March 3, 2008 at 2:35 am

  6. Jason Arora says:

    Hi again! First, I have to say I am really digging this gem, man! This is exactly what Rails needed.

    I found another small bug – when I do something like this:
    forum.topics.popular

    The generated SQL checks forum_id twice:
    SELECT * FROM `topics` WHERE (topics.forum_id = 13) AND (topics.forum_id = 13) ORDER BY posts_count desc

    My finder is the following:
    has_finder :popular, :order => ‘posts_count desc’

    March 3, 2008 at 6:31 am

  7. Alexander says:

    I think that rails core team should consider has_finder as part of AR. It’s useful thing like associations. I hope that some day it becomes part of AR.

    March 16, 2008 at 2:53 pm

  8. Xin says:

    Thank you for releasing has_finder. It’s a pleasure to use.

    I want to use it in conjunction Geokit. Geokit adds :origin and :within options to finders. In a has_finder, it does not recognise these options.

    Can anyone help at all?

    March 16, 2008 at 9:07 pm

  9. jochen says:

    I encountered a “superclass mismatch for class Annotation” when trying to use with rails 2.0.2.

    How to fix that?

    March 17, 2008 at 6:37 pm

  10. dip00dip says:

    My solution is ugly -but it works.
    You have to remove the spec directory from plugin’s source.

    March 18, 2008 at 9:09 am

  11. dip00dip says:

    And there is second way.

    Just install it as a gem. (instructions here:
    http://pivotalrb.rubyforge.org/svn/has_finder/trunk/has_finder/README.txt)

    In such way it has no problems with rake.

    March 18, 2008 at 9:25 am

  12. Xin says:

    I had the same problem jochen. I also removed the spec directory!

    I want to chain a number of has_finders together. Some of these finders might have nil as parameter, in which case I’d like it to be ignored. Is there any way of doing this?

    i.e.
    Shoes.of_size(10).of_colour(nil).under(40)

    I tried messing with the lamda but didn’t get very far.

    March 18, 2008 at 3:45 pm

  13. Jacob Atzen says:

    How does this plugin play along with paginating_find?

    I’m trying to do something like:

    has_finder :paged, :page => {:size => 100, :auto => true}, :order => :id

    Which should potentially allow:

    User.active.paged

    March 25, 2008 at 2:57 pm

  14. Jon Buda says:

    Am I correct in assuming has_finder will not work with HABTM associations? I’ve tried it and it doesn’t produce the correct results. Just wondering if I’m doing something wrong or its just not supported.

    Other than that, been loving the plugin, thanks!

    April 11, 2008 at 6:47 pm

  15. Martin Zimmermann says:

    Hi,

    nice gem!

    I wanted to point another use which was very helpful for me.

    I needed to compose several OPTIONAL queries… Say your Person#controller accepts params[:search] and params[:ver] (with a boolean field), and you can call it with either or both of params. In the controller I would like just to call:

    Person.search(params[:search]).verification(params[:ver])

    and I wanted it to work with no (eg. give me all Person), one or the above two params options.

    I managed to do this by defining the has_finder like:

    has_finder :search, lambda {|name|
    {:conditions => name.nil? ? nil :
    ["name LIKE ?", name+"%"] }
    }
    has_finder :ver, lambda {|v|
    {:conditions => v.nil? ? nil : :conditions => {:verification = v} }
    }

    Bye,
    Z

    April 14, 2008 at 7:13 pm

  16. soliiid says:

    Hi nick,

    great job!

    but i found that has_finder does not work with polymorphic :has_many.

    here is the test code.


    class Membership < ActiveRecord::Base
    belongs_to :joinable, :polymorphic => true

    has_finder :accepted, :conditions => { :accepted => true }
    end

    class Group < ActiveRecord::Base
    has_many :memberships, :as => :joinable
    end

    the snippet

    Group.find(:first).memberships

    produces the sql

    SELECT * FROM `memberships` WHERE (memberships.joinable_id = 1 AND memberships.joinable_type = 'Group')

    while

    Group.find(:first).memberships.accepted

    only produces the sql

    SELECT * FROM `memberships` WHERE (accepted_at is not NULL)

    am i missing something?

    April 28, 2008 at 6:17 am

  17. soliiid says:

    Hi nick,

    great job!

    but i found that has_finder does not work with polymorphic :has_many.

    here is the test code.

    
    class Membership < ActiveRecord::Base
    
      belongs_to :joinable,	:polymorphic => true
    
      has_finder :accepted, :conditions => { :accepted => true }
    
    end
    
    class Group < ActiveRecord::Base
    
      has_many :memberships, 	:as => :joinable
    
    end
    

    the snippet

    Group.find(:first).memberships


    produces the sql

    
    
    SELECT * FROM `memberships` WHERE (memberships.joinable_id = 1 AND memberships.joinable_type = 'Group')
    
    

    while

    Group.find(:first).memberships.accepted

    only produces the sql

    
    SELECT * FROM `memberships` WHERE (accepted_at is not NULL)
    

    am i missing something?

    April 28, 2008 at 6:21 am

  18. UVSoft says:

    Hi there,

    Does has_finder cache the result of searches like usual AssociationProxies do?


    class Task < AR
    has_finder :resolved, :condititions => { :resolved => true }
    end

    Task.resolved
    Task.resolved

    produces two SQL quiries!!!

    April 28, 2008 at 9:39 am

  19. Nick Kallen says:

    UVSoft:

    It does cache results. `x = Task.resolved` has not yet hit the database. `x.collect` will hit the db one time. `x.collect` again will not hit the database. You do *not* want to cache `Task.resolved` as it’s effectively a global variable that will persist across http requests.

    April 28, 2008 at 5:01 pm

  20. Tekin says:

    Quick question, is there a reason has_finder/named scope ignores the :limit option?

    class Product

    named_scope :most_ordered, :condition => {:status => ONLINE}, :order => 'ordered_count DESC'

    end

    # Should return the top ten most ordered products but doesn't..
    Product.most_ordered(:limit => 10)

    At the moment, I’m using a lamda to pass in the limit and get around this, but this doesn’t feel right:

    named_scope :most_ordered, lambda { |l| limit = l ? { :limit => l } : {}
    {:conditions => {:status => ONLINE}, :order => 'ordered_count DESC'}.merge(limit) }

    May 14, 2008 at 9:47 pm

  21. Nazar says:

    Hi Nick.

    Not too sure if this awesome plugin is still maintained (as it is in Rails 2.x). I had hoped it would be on GitHub so that I could try my first fork and pull request. Alas, no luck…

    Just a quick post here in the hope this would benefit anyone else still not on Rails 2.x.

    I have a parametrised has_finder as follows:

    has_finder :most_popular, lambda { |limit| limit = 5 if limit.blank?
    {:conditions => ['id in (select photo_id from categories_photos where id = photo_id) and views_count > 0 and approved = ?',true],
    :order => ‘created_on DESC’, :limit => limit}
    }

    Which works well. Except when doing the following:

    my_object.most_popular.length

    A correct result is returned (ie 5) but with the following warning:

    multiple values for a block parameter (0 for 1) from
    /home/nazar/Rails/photos.git/vendor/plugins/has_finder/lib/has_finder/has_finder.rb

    I’ve tweaked the complaining module (lib/has_finder/has_finder.rb in module ClassMethods

    def has_finder(name, options = {}, &block)
    finders[name] = lambda do |parent_finder, *args|
    FinderProxy.new(parent_finder, case options
    when Hash
    options
    when Proc
    if args.length > 0
    options.call(*args)
    else
    options.call(nil)
    end
    end, &block)
    end
    meta_def(name) do |*args|
    finders[name].call(self, *args)
    end
    end

    Hope this is:

    a) correct
    b) helps anybody else who stumbles on this

    Many thanks for one my most favourite plugins! :)

    June 23, 2008 at 8:23 pm

  22. Keith says:

    For those asking about using geokit with has_finder/named_scope, I got this to work:

    named_scope :within, lambda{|o, d|
    origin = o
    distance_sql = self.distance_sql(origin)
    within = d
    {:select => “*, #{distance_sql} as distance”,
    :conditions => “#{distance_sql} < = #{within}",
    :order => ‘distance asc’}}

    September 4, 2008 at 7:56 pm

  23. josh says:

    Very nice!

    Maybe make available as a plugin instead?

    Also – why “has_finder” – why not just an explicit “finder”?

    December 12, 2007 at 11:50 pm

  24. Ryan says:

    Awesome. I wrote something to enable the same call syntax the other day, but this is much better. I second the vote for a plugin!

    December 12, 2007 at 11:50 pm

  25. Nick Kallen says:

    Josh/Ryan — thanks for the feedback. I’ll package this as a plugin soon. As for calling the method “finder” vs. “has_finder” — I’m OK with both. Let’s have a vote!

    December 12, 2007 at 11:50 pm

  26. Joel says:

    Really sweet!

    But isn’t with_scope going to be a protected method? Implications on this?

    Trying something like Page.published.find(params[:id])) in edge and it fails, something like @page.published.children.find(:all) works fine though..

    December 12, 2007 at 11:50 pm

  27. Nick Kallen says:

    Joel — I just updated the gem to use send :with_scope. I can’t seem to get rake to run on a freshly generated edge rails app, so I’m unsure whether this will fix the defect or not. Let me know!

    December 12, 2007 at 11:50 pm

  28. Joel says:

    That seems to do it. Much obliged =)

    (Did you try gem update –system; gem update rake; for edge rails?)

    December 12, 2007 at 11:50 pm

  29. Cyx says:

    Hi,

    For some reason there is a bug when using with scope. For example

    User.scoped_by_site(1) { User.superusers }

    and

    User.scoped_by_site(1) { User.superusers.find(:all) }

    for the first example, it doesn’t respect the scope, for the second it does.

    Any thoughts?

    December 12, 2007 at 11:50 pm

  30. Nick Kallen says:

    Cyx: That kind of usage is impossible to support, unfortunately. It’s difficult to explain why that doesn’t work, but let me try:

    User.superusers returns a proxy object, not an array. What you’re doing:

    User.scoped_by_site(1) { User.superusers }
    

    is more or less equivalent to just

    User.superusers
    

    Since the more complex expression just returns the proxy object. The withscope only “lasts” as long as the block being passed to it; it’s destroyed as soon as the block returns. In your first example, you’ll only effect the find after scopedbysite returns (e.g., by calling #each or #find), at which point the withscope is long gone.

    This fact is, by far, the most complex part of how hasfinder works. If you want an object to encapsulate a scope (which is more or less what hasfinder does), composition is only possible if the objects no how to chain themselves together, such that method invocation leads to nested with_scopes. Look at the source code for details. In any case, I recommend you do the following:

    class User
      has_finder :scoped_by_site lambda{|site_id| {...} }
    end
    

    and then you should be OK.

    December 12, 2007 at 11:50 pm

  31. macovsky says:

    great idea! vote for plugin too :).

    for STI it doesn’t work. for now i put has_finder’s definition in a module and included it in tables-children. other way i’ve got that:

    The error occurred while evaluating nil.call
    from …/gems/has_finder-0.1.2/lib/has_finder/has_finder.rb:59:in ‘published’

    December 12, 2007 at 11:50 pm

  32. grant says:

    One problem I’m having with nested scopes:

    Article.published.popular executes two queries, one to get published and another to get published AND popular AND published.

    SELECT * FROM articles WHERE (published = 1)

    SELECT * FROM articles WHERE (( ( published = 1 ) AND ( popular = 1 ) ) AND ( published = 1 ))

    December 12, 2007 at 11:50 pm

  33. Seth says:

    I was hacking around earlier and ended up implementing a half-baked version of this that doesn’t involve the use of AssociationProxies:

    http://pastie.caboo.se/95207

    It’s definitely under-powered (I’m using has_finder instead), but folks might find the approach interesting.

    December 12, 2007 at 11:50 pm

  34. Seth says:

    I was hacking around earlier and ended up implementing a half-baked version of this that doesn’t involve the use of AssociationProxies:

    http://pastie.caboo.se/95207

    It’s definitely under-powered (I’m using has_finder instead), but folks might find the approach interesting.

    December 12, 2007 at 11:50 pm

  35. nick says:

    macovsky – i’ll fix this bug and release a new version in the next couple of days.
    grant – i’ll see if I can reproduce this behavior.

    December 12, 2007 at 11:50 pm

  36. Zargony says:

    Great! Since I was playing around with scopeout and scopedproxy, I always wanted to somehow combine the advantages of both plugins. I didn’t try hasfinder yet, but it looks like it does exactly what I am searching for, so +1 for a hasfinder plugin :-)

    December 12, 2007 at 11:50 pm

  37. Nick Kallen says:

    macovsky/grant – both issues should be fixed and are now available as has_finder 0.1.3.

    December 12, 2007 at 11:50 pm

  38. Joel says:

    Hi, thanks again for a excellent piece of software..

    Have you thought about calculations? eg. Order.completed.sum(:amount) ect. (That maybe out of scope for has_finder though)

    Just checking if you are implementing it before I try a stab at it myself..

    On another note, it’s busted in edge again =)
    I’m getting

    vendor/rails/activerecord/lib/activerecord/base.rb:1277:in method_missing_without_paginate': undefined methodhasfinder’ for # (NoMethodError)

    lots of stuff going on in edge now apparantly.

    I think it has to do with how you include the HasManyAssociations, changing from just using include to doing a class_eval seems to do it.

    like so:

    ActiveRecord::Base.send :include, HasFinder::ActiveRecord
    module ActiveRecord::Associations
    [HasManyAssociation, HasManyThroughAssociation].each do |klass|
    klass.class_eval do
    include HasFinder::HasManyAssociation
    aliasmethodchain :methodmissing, :hasfinder
    end
    end
    end

    December 12, 2007 at 11:50 pm

  39. Joel says:

    whoops, busted rendering but you get the idea =)

    December 12, 2007 at 11:50 pm

  40. nick says:

    Joel — I’ll make those fixes later tonight… As for #sum, in theory they should be working (I thought they were already) since I thought with_scope affected calculations as well… I’ll double check that stuff.

    December 12, 2007 at 11:50 pm

  41. Nick Kallen says:

    Joel — I fixed the issue with sum (was being delegated to the array rather than ActiveRecord). I could not reproduce your issue in edge (just tried with latest version right now), sorry!

    Try 0.1.5 and let me know if it’s any better.

    December 12, 2007 at 11:50 pm

  42. oliver says:

    two things. one, i’m unable to get hasfinder working. I get an error from ActiveRecord::Base:
    “in’method_missing’:ArgumentError: no id given”
    on the line that calls “super” at the end. not sure what’s going on there.

    also…Is there any way to include has_finder finders directly on an association? so for instance:
    User
    – has_finder :active, :conditions = {:deactivated = false}

    Group
    has_many :memberships
    has_many :members, :through = :memberships, :with_finder = :active

    that would seem easier than having to always access @group.members.active

    December 12, 2007 at 11:50 pm

  43. Nick Kallen says:

    Hi oliver:

    1. There’s not yet planned support for things like:
      hasmany … :withfinder => :active
      Good idea though, so I’ll consider adding it.

    2. I cannot reproduce the error you have with has_finder. I need more detail. Send me a full stack trace (nick @ pivotallabs.com), and any source code that you can… Something to do might be generate a new rails app, require the gem in your environment and write a few lines of code that fail… you can zip it up and send it all to me.

    December 12, 2007 at 11:50 pm

  44. Nick Kallen says:

    Oliver, PS:

    class Group
      has_many :memberships ...
      def active_memberships
        memberships.active
      end
    

    end

    Not super-concise, but it gives you almost all that you want.

    December 12, 2007 at 11:50 pm

  45. Brandon Keepers says:

    Nick,

    HasFinder is quite a gem (in many ways), but do you have it available in an svn repo so it can be installed as a plugin?

    December 12, 2007 at 11:50 pm

  46. Nick Kallen says:

    brandon: source code is in rubyforge in the pivotalrb project. thanks for your interest!

    December 12, 2007 at 11:50 pm

  47. Michael says:

    I want to install as script/plugin is it posible?

    December 12, 2007 at 11:50 pm

  48. Michael says:

    I want to install as script/plugin is it posible?

    December 12, 2007 at 11:50 pm

  49. Mark says:

    can you use associations in the conditional:

    class Song < ActiveRecord::Base
    has_many :reviews
    has_finder :u2, {:conditions => {"songs.artist" => "U2", "reviews.rating" => 8 }, :include => :reviews }
    

    December 12, 2007 at 11:50 pm

  50. Mark says:

    can you use associations in the conditional:

    class Song < ActiveRecord::Base
    has_many :reviews
    has_finder :u2, {:conditions => {"songs.artist" => "U2", "reviews.rating" => 8 }, :include => :reviews }
    

    December 12, 2007 at 11:50 pm

  51. Mark says:

    can you use associations in the conditional:

    class Song < ActiveRecord::Base
    has_many :reviews
    has_finder :u2, {:conditions => {"songs.artist" => "U2", "reviews.rating" => 8 }, :include => :reviews }
    

    December 12, 2007 at 11:50 pm

  52. Mike Pepper says:

    @ those that were asking about using this as a plugin:

    Install it as a gem, then copy the ‘has_finder-0.1.5′ from the ‘ruby/lib/ruby/gems/1.8/gems’ directory into ‘vendor/plugins’, and put:

    require 'has_finder'

    into a new file named init.rb, inside that folder, then you dont need the two lines in your environment.rb file either.

    I think you can also safely delete the config and test folders, and the ‘setup.rb’ file, as they are then redundant. Oh and don’t forget to restart the server (as I always do!)

    A lot of gems can be ‘converted’ this way as they share the same basic structure as rails plugins.

    And thanks nick, this is just what i’ve been looking for :)

    December 12, 2007 at 11:50 pm

  53. andy says:

    This plugin (gem) seems to have the same problem with :group option of AR.find method that scope_out suffers. Namely it does not seem to support it.

    ex…

    hasfinder :nonactive, :group => ‘login’

    fails with…

    ActionView::TemplateError (Unknown key(s): group) on line #10 of app/views/users/_index.rhtml:

    /var/lib/gems/1.8/gems/activesupport-1.4.4/lib/activesupport/coreext/hash/keys.rb:48:in assert_valid_keys'
    /var/lib/gems/1.8/gems/activerecord-1.15.5/lib/active_record/base.rb:913:in
    with_scope’

    December 12, 2007 at 11:50 pm

  54. andy says:

    FYI, a work around for the lack of :group support

    ex…

    hasfinder :nonactive, :select => ‘distinct …’

    seems to work for me, but the :group option would be nice – as it’s a standard part of find method

    December 12, 2007 at 11:50 pm

  55. nick says:

    Mark — your technique should work.

    Andy — unfortunately, I rely on with_scope (as does scope_out) and it does not support group by. I can give you source code to make with_scope support group_by, (it’s fairly straightforward), but I wouldn’t want to include it in has_finder because of its potentially undesirable effect on the rest of the Rails app.

    December 12, 2007 at 11:50 pm

  56. DEkart says:

    It would be good to get an access to SVN repo. I want to install this plugin using Piston (http://piston.rubyforge.org/). It is much better than copying Gem

    December 12, 2007 at 11:50 pm

  57. nick says:

    DEkart — http://pivotalrb.rubyforge.org/svn/hasfinder/trunk/hasfinder/

    December 12, 2007 at 11:50 pm

  58. Chris says:

    I am currently using custom finders like this

    class Barcode < ActiveRecord::Base
    def self.findallbyclient(clientid)
    self.findallbyclientid(clientid, :include => {:item => :itemtype}, :order => “item_types.name, items.name”)
    end
    end

    I would like to replace it with this but how can I pass the session variable session[:client] to the has_finder method?

    hasfinder :all, :conditions => {:clientid => session[:client]}, :include => {:item => :itemtype}, :order => “itemtypes.name, items.name”

    December 12, 2007 at 11:50 pm

  59. Chris says:

    My underscores got stripped out of that post but you can still get the point.

    December 12, 2007 at 11:50 pm

  60. nick says:

    Chris — only the controller (and view) have access to the session (for better or worse). The easiest thing to do is pass in the client_id as a parameter to the finder, as you did in your self.find_all_by...

    class Barcode
      has_finder :all, lambda {|client_id| { :conditions => {:client_id => client_id} } }
    end
    
    class BarcodesController
      ...
        Barcode.all(session[:client_id])
    

    In the example you gave above, it doesn’t seem like you need your model to know about the session… But,

    If you want the session available in your model layer, this is an obstacle totally unrelated to has_finder. Use an around_filter to set (and unset) a class variable on AR::Base or a thread-local variable that AR::Base has access to. Better yet, alias_method_chain perform_actionto do the same thing (this will ensure your session is available in your models in the after_filters that happen after your around_filter).

    Don’t let anyone give you a hard time about MVC. Dogma does not lead to good software design.

    December 12, 2007 at 11:50 pm

  61. Chris says:

    Thanks, that works great for finding a collection of objects but when I try to find a single item for my show action, I get an method missing error. I think this is because has_finder is returning an array and my show action is treating it like an object.

    hasfinder :one, lambda {|id, clientid| { :conditions => {:id => id, :clientid => clientid} } }

    How can I write this to return a single object instead of an array of 1, or should I just use a custom method for finding one object instead of using has_finder?

    December 12, 2007 at 11:50 pm

  62. nick says:

    chris — has finder is not really designed for the “one” case. A simple, but clumsy, implementation might be something like the following:

    has_finder :ones, lambda {|id, client_id| {...} }
    
    def self.one
      ones.first
    end
    

    December 12, 2007 at 11:50 pm

  63. Chris says:

    Thanks Nick, you’ve been so helpful. One more question. I have the following defined.

    
    has_finder :active, lambda {|client_id| { :conditions => {:client_id => client_id, :active => true}, :include => [:tiers, :sale_contracts], :order => "plans.monthly_fee" } }
    has_finder :business, lambda {|client_id| { :conditions => {:client_id => client_id, :is_business_plan => true}, :include => [:tiers, :sale_contracts], :order => "plans.monthly_fee" } } 

    When I call Plan.active.business(session[:client]) I get nothing in the result list because the conditions are merged and the has finder for active is supplying the condition client_id = null because I didn’t pass it in the controller. To make it work I have to call Plan.active(session[:client]).business(session[:client]) and that works fine, I was just wondering if there was a prettier way to write it.

    By the time I got to the end of writing this post, I have decided to myself that its better this way but still let me know what you think. I’m still considering using the around filter that you mentioned before because the way my app is designed, every query in every model requires the client_id.

    December 12, 2007 at 11:50 pm

  64. Daniel Fischer says:

    This is perfect, just in the nick of time.

    Thanks!

    December 12, 2007 at 11:50 pm

  65. Daniel Fischer says:

    gem ‘has_finder’
    = true
    require ‘has_finder’
    NoMethodError: undefined method ‘delegate’ for HasFinder::FinderProxy:Class

    Eek, what’s going on?

    December 12, 2007 at 11:50 pm

  66. nick says:

    delegate is a method from activesupport. You must be requring ‘hasfinder’ too early in your environment file–assuming you’re using Rails. If you’re not using Rails, make sure to require has_finder after active record

    December 12, 2007 at 11:50 pm

  67. Rob M says:

    YOU ROCK. This works great!

    December 12, 2007 at 11:50 pm

  68. nick says:

    Daniel:

    class Writing
      has_many :pages
    end
    
    class Page
      has_finder :beginnings, :conditions => { :position => 1 }
    end
    

    And voila:

    my_writing.pages.openings
    

    December 12, 2007 at 11:50 pm

  69. Daniel Fischer says:

    Haha, thanks! I ended up doing this before the reply:

    first_scoped_page = self.writing.pages.find(:all, :conditions => ["position = 1"] )
    

    Now I can refactor!

    Thanks!

    December 12, 2007 at 11:50 pm

  70. Vladimir Meremyanin says:

    Absolutely Great thing, thanks a lot, man!

    I’ve noticed a bug when was using models in modules.

    class Foo::Bar < ActiveRecord::Base
      belongs_to :x
      belongs_to :y
    #...
    
      has_finder from_x, lambda {|x| {:conditions => {:from_id => x}}}
      has_finder in_y,  lambda {|y| {:conditions => {:y_id => y}}}
    end
    
    class Foo::Z < ActiveRecord::Base
      belongs_to :x
      belongs_to :y
    #...
    
      def outgoing_bars
         Bar.from_x(x_id).in_y(y_id)
      end
    end
    

    results in a ‘X is not missing constant Bar’ error.

    The workaround for this is to use absolute name – ::Foo::Bar, instead of just Bar.

    But it is possible that I did something wrong…

    December 12, 2007 at 11:50 pm

  71. Shifra says:

    Okay, we really need to package this as a plugin…

    December 12, 2007 at 11:50 pm

  72. nick says:

    Vlad — if you give me a full stack trace I will look into it in more detail. I know that using AR’s in modules in general can cause problems with associations like belongsto (at least it did with Rails 1.2.3), so it’s possible hasfinder is unrelated, not sure. But I’ll fix it if I can.

    December 12, 2007 at 11:50 pm

  73. Vladimir Meremyanin says:

    Sure, here it is:

    /var/lib/gems/1.8/gems/activesupport-1.4.4/lib/active_support/dependencies.rb:240:in `load_missing_constant'
    /var/lib/gems/1.8/gems/activesupport-1.4.4/lib/active_support/dependencies.rb:468:in `const_missing'
    /var/lib/gems/1.8/gems/activesupport-1.4.4/lib/active_support/dependencies.rb:470:in `send'
    /var/lib/gems/1.8/gems/activesupport-1.4.4/lib/active_support/dependencies.rb:470:in `const_missing'
    /app/models/foo/z.rb:15:in `outgoing_bars'
    /var/lib/gems/1.8/gems/activerecord-1.15.5/lib/active_record/associations/association_proxy.rb:123:in `send'
    /var/lib/gems/1.8/gems/activerecord-1.15.5/lib/active_record/associations/association_proxy.rb:123:in `method_missing'
    /app/views/shared/_document_bars.rhtml:5:in
    ...
    

    The call is like

    document.z.outgoing_bars
    

    To get the point what I’m trying to do: navigating kind of a graph, with multiple paths on the same vertices :)

    December 12, 2007 at 11:50 pm

  74. Steve says:

    I thought I posted this already but I didn’t see the comment appear. I have realized that in rails, when you do a find(:all) statement inside of an association (has_many :somethings, :thought => :other_things do; def completed; find(:all, :conditions => {:completed => 1}); end;) it breaks the :uniq => true statement in the association. I’m able to fix this in my models when i define finders to narrow a search down by just adding a .uniq to the end of the statement (find(:all).uniq) .. is there any way to add this functionality to has_finders? I’d love to use this gem and make my finders much easier to use and read, but i NEED the uniq fix and can’t figure out how to do it myself with this gem. Any help?

    December 14, 2007 at 9:31 am

  75. Steve says:

    After playing around in the source, I added .uniq to the find(:all) statement in the load_found private method and it now only returns unique results. This’ll do until an official fix is released.

    December 14, 2007 at 10:26 am

  76. Andy Watts says:

    Those looking for a plugin version can try this checkout into their plugins directory..

    svn checkout http://pivotalrb.rubyforge.org/svn/has_finder/trunk/has_finder

    January 26, 2008 at 1:29 am

  77. Richard Ibarra says:

    Hi!,
    I tried to use has_finder and has_many_polymorphs (tagging_extensions), but I get a strange behavior…
    I have two taggable models, in one of them I’ve added a finder (using has_finder)… and in that model I can’t access the dynamic method added by tagging_extensions (tags for example). In the model where I haven’t added any finder I can access these methods without any problem.

    For some reason, when I ask for the Tag model… this problem doesn’t happen anymore….
    —– environment.rb

    require 'tagging_extensions'
    require 'has_finder'
    Tag

    The last line makes all work fine.
    This model Tag, adds to my two models a lot of methods via :extend options in has_many_polymorphs… I think that is the reason why the problem doesn’t happen anymore.. but I think that is not the correct way for solving it..

    Any ideas?
    Thanks

    January 30, 2008 at 1:23 pm

  78. hosiawak says:

    Finally found a scoping plugin that works with acts_as_tree out of the box. Thank you :)

    January 31, 2008 at 12:56 pm

  79. Joshua says:

    It seems that HasFinder overrides the model translating aspect of Globalize (an il8n plugin for Rails), at least in the case where one is using the default translation storage.

    It’s a very nice system, otherwise!

    January 31, 2008 at 1:47 pm

  80. rubylicio.us says:

    This is so great. One “problem” I found though, was that I’ve started using rails new partial renderthingie:

    controller:
    @posts = Post.a_has_finder_finder

    view:
    render :partial => @posts

    This doesn’t work anymore.. probably since the objects in @posts aren’t pure Post-classes anymore, but some has_finder proxy thingie.

    The workaround isn’t that big of a deal though, just go back to using the “old way”:

    view:
    render :partial “posts/post”, :locals => bla bla …

    .. Maybe something could be done so it works with the first way though?

    February 7, 2008 at 1:37 am

  81. rubylicio.us says:

    Oups, sorry for the dups.. didn’t see my comments comming.

    And one correction, the way one has to do with has_finder in the view is:

    render :partial => “posts/post”, :collection => @posts … which isn’t such a big deal.

    February 7, 2008 at 1:46 am

Add New Comment Cancel reply

Your email address will not be published.

Pivotal Labs

Pivotal Labs

Recent Posts

  • Does the set of all sets contain itself?
  • Standup 3/8/2012
  • Standup 3/7/2012
Subscribe to Pivotal's Feed

Author Topics

riddles (1)
agile (167)
capistrano (2)
rails (26)
movember (1)
git (10)
railsdoc (1)
object-design (1)
bdd (3)
cucumber (3)
linkedin (1)
oauth (1)
ruby (17)
tdd (2)
lvh.me (1)
rails 3.1.1 (1)
selenium (6)
homebrew (1)
mysql (5)
rvm (1)
sproutcore (1)
paperclip (2)
pry (1)
amazon (1)
heroku (1)
rails3 (2)
jasmine (3)
design (3)
process (12)
productivity (8)
learning (1)
olin (1)
migrations (2)
mongodb (2)
devise (2)
javascript (13)
rubymine (4)
ipad (1)
whurl (1)
head.js (1)
pairing (2)
tools (4)
pair programming (1)
rspec (10)
rspec2 (1)
ruby19 (1)
incubation (3)
startup (5)
api (1)
presenter (1)
vanna (1)
pivotal tracker (5)
capybara (1)
fakeweb (1)
webmock (1)
intern (1)
ruby on rails (25)
meetup (1)
textmate (1)
testing (20)
solr (4)
nyc-standup (11)
community (1)
opensource (3)
activerecord (4)
chrome (1)
mp4 (1)
activeresource (1)
flash (3)
neo4j (1)
nginx (1)
rsoc (1)
meta programming (1)
agile standup (7)
government (3)
webos (4)
xss (1)
jquery (1)
bundler (2)
ci (3)
gems (5)
postgresql (1)
geminstaller (1)
gemcutter (1)
cloud (2)
rack (2)
refraction (1)
gem (5)
refactoring (1)
validations (1)
webrat (1)
engine-yard (1)
firefox (2)
jsunit (1)
mongrel (2)
thin (1)
unicorn (1)
facebook (1)
rubygems (5)
jruby (1)
actioncontroller (1)
rails 2.3 (1)
palmpre (1)
autotest (1)
mac (2)
hosting (1)
goruco (11)
database (3)
railsconf (11)
gogaruco (4)
deployment (4)
github (1)
ie (1)
ajax (1)
intellij (1)
json (1)
asset packaging (1)
polonium (1)
character encoding (1)
utf-8 (1)
test (3)
civics (1)
hpricot (1)
rake (3)
sms (1)
unicode (1)
iphone (1)
java (1)
safari (1)
memory leaks (1)
rr (3)
editor (1)
css (1)
nyc (3)
performance (5)
fun (5)
enterprise rails (1)
health (1)
new and cool (1)
general (2)
treetop (1)
errors (1)
stack (1)
trace (1)
cache (1)
cookies (1)
freesoftware (1)
conferences (1)
development (1)
driven (1)
proxy (1)
caching (1)
peertopatent (1)
languages (1)
rest (2)
rubyforge (1)
sake (1)
file (1)
upload (1)
constants (1)
osx (1)
terminal (1)
pairprogramming (2)
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Tools
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >