Ruby on Rails will_paginate an array
Asked Answered
B

7

80

I was wondering if someone could explain how to use will_paginate on an array of objects?

For example, on my site I have an opinion section where users can rate the opinions. Here's a method I wrote to gather the users who have rated the opinion:

def agree_list
  list = OpinionRating.find_all_by_opinion_id(params[:id])
  @agree_list = []
  list.each do |r|
    user = Profile.find(r.profile_id)
    @agree_list << user
  end
end

Thank you

Bearwood answered 4/12, 2010 at 9:40 Comment(1)
Having come here from a Google search for "rails paginate array", I wanted to share with other visitors that kaminari is the rails pagination alternative that I settled on. Handles arrays elegantly.Cooney
M
231

will_paginate 3.0 is designed to take advantage of the new ActiveRecord::Relation in Rails 3, so it defines paginate only on relations by default. It can still work with an array, but you have to tell rails to require that part.

In a file in your config/initializers (I used will_paginate_array_fix.rb), add this

require 'will_paginate/array'

Then you can use on arrays

my_array.paginate(:page => x, :per_page => y)
Mariomariology answered 6/12, 2011 at 21:45 Comment(3)
This helped but it doesn't show the pagination in the view properly.Doublequick
can someone tell me - how we will render it in view? --- @array = my_array.paginate(:page => x, :per_page => y) <%= will_paginate @array%>Ahner
@Robbie Guilfoyle my_array.paginate(:page => params[:page], :per_page => 1) This fixed the pagination for me :)Belike
E
9

You could use Array#from to simulate pagination, but the real problem here is that you shouldn't be using Array at all.

This is what ActiveRecord Associations are made for. You should read that guide carefully, there is a lot of useful stuff you will need to know if you're developing Rails applications.

Let me show you a better way of doing the same thing:

class Profile < ActiveRecord::Base
  has_many :opinion_ratings
  has_many :opinions, :through => :opinion_ratings
end

class Opinion < ActiveRecord::Base
  has_many :opinion_ratings
end

class OpinionRating < ActiveRecord::Base
  belongs_to :opinion
  belongs_to :profile
end

It's important that your database schema is following the proper naming conventions or all this will break. Make sure you're creating your tables with Database Migrations instead of doing it by hand.

These associations will create helpers on your models to make searching much easier. Instead of iterating a list of OpinionRatings and collecting the users manually, you can make Rails do this for you with the use of named_scope or scope depending on whether you're using Rails 2.3 or 3.0. Since you didn't specify, I'll give both examples. Add this to your OpinionRating class:

2.3

named_scope :for, lambda {|id| 
  {
    :joins => :opinion,
    :conditions => {
      :opinion => { :id => id }
    }
  }
}

named_scope :agreed, :conditions => { :agree => true }
named_scope :with_profiles, :includes => :profile

3.0

scope :agreed, where(:agree => true)

def self.for(id)
  joins(:opinion).where(:opinion => { :id => id })
end

In either case you can call for(id) on the OpinionRatings model and pass it an id:

2.3

@ratings = OpinionRating.agreed.for(params[:id]).with_profiles
@profiles = @ratings.collect(&:profile)

3.0

@ratings = OpinionRating.agreed.for(params[:id]).includes(:profile)
@profiles = @ratings.collect(&:profile)

The upshot of all this is that you can now easily paginate:

@ratings = @ratings.paginate(:page => params[:page])

Update for Rails 4.x: more or less the same:

scope :agreed, ->{ where agreed: true }

def self.for(id)
  joins(:opinion).where(opinion: { id: id })
end 

Although for newer Rails my preference is kaminari for pagination:

@ratings = @ratings.page(params[:page])
Edington answered 4/12, 2010 at 10:7 Comment(13)
Well, the associations are table Opinion has_many :opinion_ratings, and belongs_to :opinion. An opinion is liked to the Profile table through a many-to-many relationship with a join table called Categories. With this set up do you know how it would change the code you mentioned above?Bearwood
@Bearwood I've updated my example, but I can't be certain unless you show me your model classes.Edington
Adam, how I get at the opinion rating model? If I'm understanding this right Profile.with_opinion returns a list of all the profiles with the specified id? What I need is to get a list of profiles from the OpinionRating model, which belongs_to :opinion and Opinion has_many :opinion_ratings. The Profile model is linked to the Opinion model through the Categories join table.Bearwood
This is why I was using an array, because I was unsure how to query the database.Bearwood
Well, I appreciate the help, but I think I found the answer to my question. Not sure if it is the most efficient way, but it works. I'm going to look into named_scope some more and try to implement that in the future.Bearwood
@Bearwood it's very difficult to help you if you're not upfront about your model relationships. You should be posting that code along with the question.Edington
sometimes array can not be avoided ... some filters or orderings are only possible using rubyTacy
@Tacy Unless there is something terribly wrong with his DB structure (and I'm not ruling that out) I am 99% certain this is not one of those cases.Edington
@AdamLassek it seems to me as well, but he may only be giving an example. I am trying to avoid arrays in these constructs whenever pissoble, but from my experience in 10% of cases I can not avoid arrays. This is just to say, that there are these cases.Tacy
@AdamLassek is it possible to update this answer for Rails 4? It is very in-depth, and would like to know if there are any differences with Rails 4 usageNeustria
@onebree sure, although the API for this has been pretty stable since 3.x. I added an example of an alternative pagination library that I like better.Edington
@Adam I find that will_paginate still works for Rails 4. Besides it not being as well maintained, why do you recommend Kaminari?Neustria
@onebree Kaminari does pretty much the same things with far less complexity than will_paginate. Some people might depend on some of that complexity, but I never have and I appreciate the relative simplicity.Edington
P
4

The gem will_paginate will paginate both ActiveRecord queries and arrays.

list = OpinionRating.where(:opinion_id => params[:id]).includes(:profile).paginate(:page => params[:page])
@agree_list = list.map(&:profile)
Pulsifer answered 4/12, 2010 at 10:10 Comment(0)
H
1

If you don't want to use the config file or are having trouble with it, you can also just ensure you return an ActiveRecord::Relation instead of an array. For instance, change the agree_list to be a list of user ids instead, then do an IN on those ids to return a Relation.

def agree_list
  list = OpinionRating.find_all_by_opinion_id(params[:id])
  @agree_id_list = []
  list.each do |r|
    user = Profile.find(r.profile_id)
    @agree_id_list << user.id
  end
  @agree_list = User.where(:id => @agree_id_list) 
end

This is inefficient from a database perspective, but it's an option for anybody having issues with the will_paginate config file.

Hectometer answered 28/6, 2013 at 22:13 Comment(0)
B
0

I took advantage of rails associations, and came up with a new method:

def agree_list
  o = Opinion.find(params[:id])
  @agree_list = o.opinion_ratings(:conditions => {:agree => true}, :order => 'created_at DESC').paginate :page => params[:page]
rescue ActiveRecord::RecordNotFound
  redirect_to(profile_opinion_path(session[:user]))
end

In my view I looked up the profile like so:

<% @agree_list.each do |rating| %>
  <% user = Profile.find(rating.profile_id) %>
<% end %>

Please post up if there's a better way to do this. I tried to use the named_scope helper in the OpinionRating model with no luck. Here's an example of what I tried, but doesn't work:

named_scope :with_profile, lambda {|id| { :joins => [:profile], :conditions => ['profile_id = ?', id] } }

That seemed like the same as using the find method though.

Thanks for all the help.

Bearwood answered 4/12, 2010 at 12:24 Comment(0)
B
0

I am using rails 3 ruby 1.9.2. Also, I am just starting app, so no css or styles included.

Install will_paginate:

gem install will_paginate

Add to Gemfile and run bundle.

Controller

class DashboardController < ApplicationController
    include StructHelper

    def show
        @myData =structHelperGet.paginate(:page => params[:page])
    end

end

module StructHelper queries a service, not a database. structHelperGet() returns an array of records.

Not sure if a more sophisticated solution would be to fake a model, or to grab the data every so often and recreate a sqllite table once in a while and have a real model to query. Just creating my first rails app ever.

View

<div id="Data">
                <%= will_paginate @myData%>
                    <table>
                    <thead>
                    <tr>
                    <th>col 1</th>
                    <th>Col 2</th>
                    <th>Col 3</th>
                    <th>Col 4</th>
                    </tr>
                    </thead>
                    </tbody>
                    <% @myData.each do |app| %>
                        <tr>
                           <td><%=app[:col1]%> </td>
                           <td><%=app[:col2]%> </td>
                           <td><%=app[:col3]%> </td>
                           <td><%=app[:col4]%> </td>
                        </tr>

                    <% end %>
                    </tbody>
                    </table>
                <%= will_paginate @myData%>
                </div>

This will give you pagnation of the default 30 rows per page.

If you have not read http://railstutorial.org yet, start reading it now.

Baldheaded answered 18/3, 2011 at 1:24 Comment(0)
A
0

You can implement pagination even without any gem.I saw this How do I paginate an Array?. Simple implementation in kaminari gems doc. Please see the below example which i got from kaminari gems doc

arr = (1..100).to_a
page, per_page = 1, 10
arr[((page - 1) * per_page)...(page * per_page)] #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
page, per_page = 2, 10
arr[((page - 1) * per_page)...(page * per_page)] #=> [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
Anhydrous answered 22/5, 2019 at 14:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.