Rails paginate array items one-by-one instead of page-by-page - ruby-on-rails

I have a group of assets, let's call them "practitioners".
I'm displaying these practitioners in the header of a calendar interface.
There are 7 columns to the calendar. 7 columns = 7 practitioners per view/page.
Right now:
if the first page shows you practitioners 1-7, when you go the next page you will see practitioners 8-15, next page 16-23, etc. etc.
i am wondering how to page the practitioners so that if the first page shows you practitioners 1-7, the next page will show you practitioners 2-8, then 3-9, etc. etc.
i would greatly appreciate any help you can offer.
here is the rails code i am working with.
best regards,
harris novick
# get the default sort order
sort_order = RESOURCE_SORT_ORDER
# if we've been given asset ids, start our list with them
unless params[:asset_ids].blank?
params[:asset_ids] = params[:asset_ids].values unless params[:asset_ids].is_a?(Array)
sort_order = "#{params[:asset_ids].collect{|id| "service_provider_resources.id = #{id} DESC"}.join(",")}, #{sort_order}"
end
#asset_set = #provider.active_resources(:include => {:active_services => :latest_approved_version}).paginate(
:per_page => RESOURCES_IN_DAY_VIEW,
:page => params[:page],
:order => sort_order
)

Good question! I guess this is one thing WillPaginate doesn't really account for. I'm going by looking at WillPaginate's code here, but I didn't actually test this solution. If you intend to try it, let me know if it worked for you.
The logic is well separated, in WillPaginate::Collection. You need to change the behavior of the offset and total_entries= methods. You can do this with subclassing, but that means you can no longer use the special paginate finder, unfortunately. (It has WillPaginate::Collection hardcoded.)
You could have something like the following, perhaps in your lib/:
class SlidingWindowCollection < WillPaginate::Collection
def offset
current_page - 1
end
def total_entries=(number)
#total_entries = number.to_i
#total_pages = [#total_entries - per_page, 1].max
end
end
And then, your example code would look like:
#asset_set_scope = #provider.active_resources(:include => {:active_services => :latest_approved_version})
#asset_set = SlidingWindowCollection.create(params[:page], RESOURCES_IN_DAY_VIEW, #asset_set_scope.count) do |pager|
pager.replace(#asset_set_scope.all(:offset => pager.offset, :limit => pager.per_page, :order => sort_order))
end
Usage is a bit more complicated, I suppose. All the extra stuff is normally taken care of by the special finder paginate, such as figuring out the total number of entries and selecting the right entries. I suppose you could create a helper if it's something you intend to do often.

I think LIMIT will work for you. I don't know using pagination but you can try following
LIMIT params[:page], 7
Where params[:page] is number of the page,
So for page 1 it will show 7 rows from 1 i.e. 1-7
Smilarly,
for page 2 it will show 7 rows from 2 i.e. 2-8

Related

Rails :order by date in Postgres returning incorrect order

I have a model called Story that I'm trying to order by the created_at date. Since I've hosted my app on Heroku, which uses Postgresql, I have the following in my controller:
#stories = Story.find( :all, :order => "DATE(created_at) DESC" , :limit => 11)
I would expect this to give the first 11 of my stories, ordered by the creation date, with the newest story first.
Unfortunately, this doesn't work. Most of the stories return ordered correctly, but the first two are flipped. That is, the latest story appears second in the list.
Why would this be? I have a sneaky suspicion that my results aren't ordered at all or are being ordered on the wrong column (maybe id?) and that until now it just happened to be ordered like I expected when displayed on my index page. How can I get it to order as I expect it to?
In case anyone cares, the index view is simply displaying the stories, in order. That is (HAML):
- #stories.each do |story|
= render :partial => "event", :locals => { :event => story }
EDIT
I am suspicious that the created_at is a datetime column and the DATE(...) function disregards the time portion. So it returns the elements created on the same date in a random order. Since the first two stories were created on the same day, but several hours apart, which would explain why they seem to be 'reversed'. If this is the case, what would be the correct syntax to order by both date and time?
I believe you want:
#stories = Story.find(:all, :order => "created_at DESC" , :limit => 11)
Update for Rails 3+:
#stories = Story.order(created_at: :desc).limit(11)
If you are using Rails 3, I would also encourage you to use the cool new query syntax which would be:
#stories = Story.order('created_at DESC').limit(11)
See Active Record Query Interface for more information.

how to paginate records from multiple models? (do I need a polymorphic join?)

After quite a bit of searching, I'm still a bit lost. There are a few other similar questions out there that deal with paginating multiple models, but they are either unanswered or they pagainate each model separately.
I need to paginate all records of an Account at once.
class Account
:has_many :emails
:has_many :tasks
:has_many :notes
end
So, I'd like to find the 30 most recent "things" no matter what they are. Is this even possible with the current pagination solutions out there?
Like using some combination of eager loading and Kaminari or will_paginate?
Or, should I first set up a polymorphic join of all these things, called Items. Then paginate the most recent 30 items, then do a lookup of the associated records of those items.
And if so, I'm not really sure what that code should look like. Any suggestions?
Which way is better? (or even possible)
Rails 3.1, Ruby 1.9.2, app not in production.
with will_paginate :
#records = #do your work and fetch array of records you want to paginate ( various types )
then do the following :
current_page = params[:page] || 1
per_page = 10
#records = WillPaginate::Collection.create(current_page, per_page, records.size) do |pager|
pager.replace(#records)
end
then in your view :
<%=will_paginate #records%>
Good question... I'm not sure of a "good" solution, but you could do a hacky one in ruby:
You'd need to first fetch out the 30 latest of each type of "thing", and put them into an array, indexed by created_at, then sort that array by created_at and take the top 30.
A totally non-refactored start might be something like:
emails = Account.emails.all(:limit => 30, :order => :created_at)
tasks = Account.tasks.all(:limit => 30, :order => :created_at)
notes = Account.notes.all(:limit => 30, :order => :created_at)
thing_array = (emails + tasks + notes).map {|thing| [thing.created_at, thing] }
# sort by the first item of each array (== the date)
thing_array_sorted = thing_array.sort_by {|a,b| a[0] <=> b[0] }
# then just grab the top thirty
things_to_show = thing_array_sorted.slice(0,30)
Note: not tested, could be full of bugs... ;)
emails = account.emails
tasks = account.tasks
notes = account.notes
#records = [emails + tasks + notes].flatten.sort_by(&:updated_at).reverse
#records = WillPaginate::Collection.create(params[:page] || 1, 30, #records.size) do |pager|
pager.replace(#records)
end
Thats it... :)

Help converting Rails 2 Database logic to Rails 3.1/ PostgreSQL

How do I select a single random record for each user, but order the Array by the latest record pr. user.
If Foo uploads a new painting, I would like to select a single random record from foo. This way a user that uploads 10 paintings won't monopolize all the space on the front page, but still get a slot on the top of the page.
This is how I did it with Rails 2.x running on MySQL.
#paintings = Painting.all.reverse
first_paintings = []
#paintings.group_by(&:user_id).each do |user_id, paintings|
first_paintings << paintings[rand(paintings.size-1)]
end
#paintings = (first_paintings + (Painting.all - first_paintings).reverse).paginate(:per_page => 9, :page => params[:page])
The example above generates a lot of SQL query's and is properly badly optimized. How would you pull this off with Rails 3.1 running on PostgreSQL? I have 7000 records..
#paintings = Painting.all.reverse = #paintings = Painting.order("id desc")
If you really want to reverse the order of the the paintings result set I would set up a scope then just use that
Something like
class Painting < ActiveRecord::Base
scope :reversed, order("id desc")
end
Then you can use Painting.reversed anywhere you need it
You have definitely set up a belongs_to association in your Painting model, so I would do:
# painting.rb
default_scope order('id DESC')
# paintings_controller.rb
first_paintings = User.includes(:paintings).collect do |user|
user.paintings.sample
end
#paintings = (first_paintings + Painting.where('id NOT IN (?)', first_paintings)).paginate(:per_page => 9, :page => params[:page])
I think this solution results in the fewest SQL queries, and is very readable. Not tested, but I hope you got the idea.
You could use the dynamic finders:
Painting.order("id desc").find_by_user_id!(user.id)
This is assuming your Paintings table contains a user_id column or some other way to associate users to paintings which it appears you have covered since you're calling user_id in your initial code. This isn't random but using find_all_by_user_id would allow you to call .reverse on the array if you still wanted and find a random painting.

How do I create a 'Most Recently Popular' bar for content in Ruby on Rails?

I'm a noob so please forgive if this is an easy question but I'm trying to create a 'Most Recently Popular' output for specific content on a rails project.
Right now the object I am pulling from has an attribute revision.background_title. I want to calculate popularity by finding the number of specific background_title's added over the past seven days then put them in order. For example if there are 4 background_title's named 'awesomecontent' then that would be above one that has 1 background_title named 'less awesome content'
This pulls all of them:
#revisions = Revision.find(:all, :order => "created_at desc")
Thanks.
You can use the basic ActiveRecord find method to do this. The code would end up looking something like this:
#revisions = Revision.all(
:select => "background_title, count(*) count", # Return title and count
:group => 'background_title', # Group by the title
:order => '2 desc' # Order by the count descending
)
To see the output, you could then do something like this:
#revisions.each do |revision|
puts "Revision #{revision.background_title} appears #{revision.count} times"
end
giving
Revision z appears 10 times
Revision a appears 3 times
Revision b appears 2 times
Another option would be to take a look at ActiveRecord::Calculations:
http://api.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html
Calculations supports a count method that also supports the group option. However, going this route will give you back a hash containing the background_title as the key and the count as the value. Personally, find the first method more useful.

Using will_paginate with multiple models (Rails)

Pretty sure that I'm missing something really simple here:
I'm trying to display a series of pages that contain instances of two different models - Profiles and Groups. I need them ordering by their name attribute. I could select all of the instances for each model, then sort and paginate them, but this feels sloppy and inefficient.
I'm using mislav-will_paginate, and was wondering if there is any better way of achieving this? Something like:
[Profile, Group].paginate(...)
would be ideal!
Good question, I ran into the same problem a couple of times. Each time, I ended it up by writing my own sql query based on sql unions (it works fine with sqlite and mysql). Then, you may use will paginate by passing the results (http://www.pathf.com/blogs/2008/06/how-to-use-will_paginate-with-non-activerecord-collectionarray/). Do not forget to perform the query to count all the rows.
Some lines of code (not tested)
my_query = "(select posts.title from posts) UNIONS (select profiles.name from profiles)"
total_entries = ActiveRecord::Base.connection.execute("select count(*) as count from (#{my_query})").first['count'].to_i
results = ActiveRecord::Base.connection.select_rows("select * from (#{my_query}) limit #{limit} offset #{offset}")
Is it overkilled ? Maybe but you've got the minimal number of queries and results are consistent.
Hope it helps.
Note: If you get the offset value from a http param, you should use sanitize_sql_for_conditions (ie: sql injection ....)
You can get close doing something like:
#profiles, #groups = [Profile, Group].map do |clazz|
clazz.paginate(:page => params[clazz.to_s.downcase + "_page"], :order => 'name')
end
That will then paginate using page parameters profile_page and group_page. You can get the will_paginate call in the view to use the correct page using:
<%= will_paginate #profiles, :page_param => 'profile_page' %>
....
<%= will_paginate #groups, :page_param => 'group_page' %>
Still, I'm not sure there's a huge benefit over setting up #groups and #profiles individually.
in my last project i stuck into a problem, i had to paginate multiple models with single pagination in my search functionality.
it should work in a way that the first model should appear first when the results of the first model a second model should continue the results and the third and so on as one single search feed, just like facebook feeds.
this is the function i created to do this functionality
def multi_paginate(models, page, per_page)
WillPaginate::Collection.create(page, per_page) do |pager|
# set total entries
pager.total_entries = 0
counts = [0]
offsets = []
for model in models
pager.total_entries += model.count
counts << model.count
offset = pager.offset-(offsets[-1] || 0)
offset = offset>model.count ? model.count : offset
offsets << (offset<0 ? 0 : offset)
end
result = []
for i in 0...models.count
result += models[i].limit(pager.per_page-result.length).offset(offsets[i]).to_a
end
pager.replace(result)
end
end
try it and let me know if you have any problem with it, i also posted it as an issue to will_paginate repository, if everyone confirmed that it works correctly i'll fork and commit it to the library.
https://github.com/mislav/will_paginate/issues/351
Have you tried displaying two different sets of results with their own paginators and update them via AJAX? It is not exactly what you want, but the result is similar.

Resources