using model methods for repetitive finds

i am still fairly new to rails and am trying to write methods in the model for finds that i will need to use over and over.

in my Task model i have:

def self.most_recent_tasks   find(:all, :order => 'updated_at DESC', :limit => 10) end

this works great when i try something like Task.most_recent_tasks, but is there a way to rewrite it so that i can use this method on a collection of tasks too? i don't know if i need to write a separate method to be able to do something like:

josh.tasks.most_recent_tasks

or if there is a way to write one that works for both.

After playing around with it a little more I am using this:

def self.most_recent(tasks = self)   tasks.find(:all, :order => 'updated_at DESC', :limit => 10) end

now I can use something like:

Task.most_recent()

AND

Task.most_recent(josh.tasks)

although this works, i would really like to hear from other developers and see if this would be the recommended way of doing something like this.

See the association extensions section of the API near the top of this page. Should let you do exactly what you're wanting.

Something like..

Class Josh < ActiveRecord::Base     has_many :tasks do        def most_recent(n)           Find.tasks(:all, :order => 'updated_at DESC', :limit => n)        end     end end

josh.tasks.most_recent(42) => array of 42 most recently updated tasks.

Josh wrote:

Hello Josh,

i am still fairly new to rails and am trying to write methods in the model for finds that i will need to use over and over.

in my Task model i have:

def self.most_recent_tasks   find(:all, :order => 'updated_at DESC', :limit => 10) end

this works great when i try something like Task.most_recent_tasks, but is there a way to rewrite it so that i can use this method on a collection of tasks too? i don't know if i need to write a separate method to be able to do something like:

josh.tasks.most_recent_tasks

or if there is a way to write one that works for both.

First solution : you can add another association to your model like this.

class Person < AR::B   has_many :tasks   has_many :most_recent_tasks, :class_name => 'Task',                    order => 'updated_at DESC', :limit => 10 end

So you could have josh.tasks and josh.most_recent_tasks

Second solution, you can use association extension, that is, you define additional methods in a block for your has_many association :

class Person < AR::B   has_many :tasks do     def most_recent_tasks(n = 10)       find :all, :order => 'updated_at DESC', :limit => n     end   end end

josh.tasks.most_recent_tasks or josh.tasks.most_recent_tasks(20)

Note that you can put your definition in a module :

class Person < AR::B   has_many :tasks, :extend => Stuff end

and most_recent_tasks is in module Stuff.

   -- Jean-François.

Thanks for the replies.

Is there any specific reason why it would be better to keep this in the User model instead of just having it in my Task model?

One of the reasons I was trying to do it this way was so I could just put:

"Task.most_recent" and find ALL of the most recent tasks and then still use the same method for individual users and put "Task.most_recent(josh.tasks)"

I see how the methods you have posted would help a lot if I was only finding a collection of tasks relating to the user, but with mine, i could pass any collection i wanted or just not give it any paramters to search the entire database.

Hi again Josh,

Is there any specific reason why it would be better to keep this in the User model instead of just having it in my Task model?

One of the reasons I was trying to do it this way was so I could just put:

"Task.most_recent" and find ALL of the most recent tasks and then still use the same method for individual users and put "Task.most_recent(josh.tasks)"

but your definition :

def self.most_recent(tasks = self) tasks.find(:all, :order => 'updated_at DESC', :limit => 10) end

works but is quite ugly, in the sense it's not the Ruby way.

If you want to share your AR query, put it in a module, then mix it in the classes you want :

module Stuff   def most_recent(n = 10)     find :all, :order => 'updated_at DESC', :limit => n   end end

class User < AR::B    class << self      include Stuff    end

  has_many :tasks, :extend => Stuff end

class Task   class << self     include Stuff   end end

class Comment < AR::B   class << self     include Stuff   end end

Then you can do Comment.most_recent, josh.tasks.most_recent Task.most_recent, Task.most_recent(20)

Modules are the way to make it DRY.

I see how the methods you have posted would help a lot if I was only finding a collection of tasks relating to the user, but with mine, i could pass any collection i wanted or just not give it any paramters to search the entire database.

and with your method, sth like Task.most_recent(Comment) would return the last recent comments, but since Comment model is not necessarily related to Task model, this is quite ugly for me !

Regards,

    -- Jean-François.

beautiful! that's exactly what i'm looking for! thanks

ok i'm sorry for this one, but where is the best place to put modules so that i can do this?

This module is model-related so app/models is OK for me.

   -- Jean-François.