will_paginate plugin doesn't work with Association Extensions?

I have:

  class Post < ActiveRecord::Base     has_many :comments do       def published         find( :all,               :conditions => {:published => true} )       end     end    end

When in my controller I do

   Post.find(:first).comments.published.paginate :page => params[:page]

I get an error

   undefined method `paginate' for :Array

Is will_paginate supposed to work through Association Extensions?

-christos

Nope, it's returning an Array, not an AssociationProxy.

has_many :published_articles, :class_name => 'Article', :conditions => {:published=>true}

Desi just posted last night about using will_paginate with arrays. http://www.devchix.com/2007/07/23/will_paginate-array/

That paginates after the select. I wouldn't recommend this for a large association.

Is there any better way to do this? I've run into this situation a few times where I have a query which is a little bit too complicated to turn into an active record association, but I still need to paginate the results.. For example, I have an Event model which has_many :line_items, and each LineItem has_one :ticket_info (I need to store extra information about tickets such as a list of names attending the event).. I need to get a list of ticket information for an event by doing something like @event.tickets, but I can't paginate the results since it returns an array. Can this be turned into an AssociationProxy to be used with will_paginate?

Thanks,

You can call paginate instead of find in your complex finder.

yeah, that's what I guess I'll have to end up doing.. I wanted to avoid this since I'll end up duplicating my find method wherever I need to paginate the list of tickets for an Event.. Using @event.tickets.paginate if much preferred, but unfortunately it doesn't work. If anyone has any other ideas, please let me know, as I've come across this problem more than once and would like to figure out an ideal solution. Thanks,

yeah, that's what I guess I'll have to end up doing.. I wanted to avoid this since I'll end up duplicating my find method wherever I need to paginate the list of tickets for an Event.. Using @event.tickets.paginate if much preferred, but unfortunately it doesn't work. If anyone has any other ideas, please let me know, as I've come across this problem more than once and would like to figure out an ideal solution. Thanks,

OK, first the disclaimer:

I've only been using Ruby/Rails for a few weeks, so am *very* new to this, and so it is entirely feasible that there is something hideously wrong with my solution; however, this is what I've come up with to solve this problem for me:

First of all, I have defined a Class called PaginatableFinder (it's the third name it's had in as many hours):

class PaginatableFinder   attr_reader :parent

  def initialize(parent, method, *args, &extension)     @parent, @method, @args = parent, method, args

    # if a block is given, use it to exetend this object     if block_given?       extend(Module.new(&extension))     end   end

  # define a paginate method to gracefuly handle pagination   def paginate(paginate_options = nil)     # replace 'find' with 'paginate' in the method name     @method = @method.to_s.sub('find', 'paginate').to_sym

    # get the options from the arguments, or create an empty hash     options = @args.last.is_a?(Hash) ? @args.pop : {}

    # merge the paginate options in with the standard options, if necessary     if paginate_options       options.merge!(paginate_options)       (paginate_options[:conditions] << ' and ' << options[:conditions]) if paginate_options[:conditions] && options[:conditions]     end

    # add the options back to the end of the arguments     @args << options

    self   end

  # missing methods should be passed to the object we are proxying   def method_missing(name, *args, &block)     object.send(name, *args, &block)   end

  # define private method for getting the object we are proxying   private     def object       # get the object by seding the method and args to the parent object       @object ||= @parent.send(@method, *(@args))     end end

Then, in my model, I can do things like:

  has_many :favourites, :order => :id do     def with_recommendations       PaginatableFinder.new(         self, :find, :all,         :joins => ', recommendations',         :select => 'favourites.*',         :group => "recommender_id",         :conditions => "recommender_type = 'Favourite' and favourites.id = recommender_id ")     end   end

this allows me, to do the following in my controller:

@user.favourites.with_recommendations

or

@user.favourites.with_recommendations.paginate :page => params[:page]

It also allows you to extend extended associations, so I can now do the following in my model:

  has_many :reviews, :order => :position do     def positive       PaginatableFinder.new(self, :find, :all, :conditions => 'stars

= 3') do

        def with_recommendations           PaginatableFinder.new(             parent, :find, :all,             :joins => ', recommendations',             :select => 'reviews.*',             :group => "recommender_id",             :conditions => "stars >= 3 and recommender_type = 'Review' and reviews.id = recommender_id ")         end       end     end   end

so I can then do any of the following in my controller:

@user.reviews.positive @user.reviews.positive.paginate :page => params[:page] @user.reviews.positive.with_recommendations @user.reviews.positive.with_recommendations.paginate :page => params[:page]

I hope this helps others, and would be massively grateful for any advice on how I can improve it.

Cheers

Luke.

avoid this since I'll end up duplicating my find method wherever I Using @event.tickets.paginate if much preferred, but unfortunately it doesn't work. If anyone has any other ideas, please let me know, as

It does work! @event.tickets returns an AssociationProxy which by default proxy missing methods to the Ticket class. So it's like calling

Ticket.paginate :all, :conditions => {:event_id => @event.id}

Your problem above came from the assocation extension method you added. #find returns a plain array. I already suggested adding another association for this:

has_many :published_articles, :class_name => 'Article', :conditions => {:published=>true}

@foo.published_articles.paginate

Or, since published is kindof the default, I tend to do this:

class Blog < AR   # default usage   has_many :articles, :conditions => {:published=>true}

  # added for the admin area and to set the :dependent option   has_many :all_articles, :class_name => 'Article', :dependent => :destroy end

> avoid this since I'll end up duplicating my find method wherever I > Using > @event.tickets.paginate if much preferred, but unfortunately it > doesn't work. If anyone has any other ideas, please let me know, as

It does work! @event.tickets returns an AssociationProxy which by default proxy missing methods to the Ticket class. So it's like calling

Ticket.paginate :all, :conditions => {:event_id => @event.id}

I should've been more specific about my method.. tickets is defined as follows:

in event.rb   def tickets     TicketInfo.find(:all, :conditions => ['line_item_id = line_items.id and product_id = ?', self],                     :include => :line_item)   end

so this returns an Array, not an AssociationProxy, so it won't work with the paginate method.. The problem is that I can't turn my tickets method into an association because of the way my classes are related to each other..

Perhaps this demonstrates that my relationships could be designed in a better way? Right now I have an Event class which has_many :line_items. And each LineItem has_one :ticket_info. I figured it was better to use a has_one relation from LineItem to TicketInfo, since not every LineItem has ticket_info associated with it (Event is a subclass of Product - I've also got Albums which don't need to have TicketInfo associated with them). I can turn my tickets method into an association by using LineItem has_many :ticket_info, and then using has_many :ticket_info, :through => :line_items in my Event class, but this didn't seem like a good solution, since a LineItem will only ever have one TicketInfo associated with it.

Anyway, another solution I came across was to paginate the result set in my ticket method as follows (I think you may have alluded to this when you said "You can call paginate instead of find in your complex finder") :

event.rb   # list of tickets for the event, paginated if page param is positive and non nil   def tickets(page = nil)     args = { :conditions => ['line_item_id = line_items.id and product_id = ?', self], :include => :line_item, :order => 'last_name'}

    if page and page > 0       TicketInfo.paginate(:all, args.merge(:page => page))     else       TicketInfo.find(:all, args)     end   end

this allows me to either call @event.tickets or @event.tickets(params[:page]) from within my controller and will call either 'paginate' or 'find' depending upon whether a page param is passed or not. Does anyone see anything wrong with this approach? Other than mixing display logic (paginate) in the model?

Your problem above came from the assocation extension method you added. #find returns a plain array. I already suggested adding another association for this:

has_many :published_articles, :class_name => 'Article', :conditions => {:published=>true}

@foo.published_articles.paginate

Or, since published is kindof the default, I tend to do this:

class Blog < AR   # default usage   has_many :articles, :conditions => {:published=>true}

  # added for the admin area and to set the :dependent option   has_many :all_articles, :class_name => 'Article', :dependent => :destroy end

Like I mentioned earlier, the way my models are related, I can't turn my tickets method into an AR association, but I think I've got a workable solution for now (unless anyone can point out otherwise, or give me a better method). Thanks