How to paginate over multiple models? - ruby-on-rails

Suppose I have three or more models with the same table schema and the same number of records.
ford
----
id
model
toyota
----
id
model
chevrolet
----
id
model
I want to create a webpage with pagination where I display all three tables interleaved by id. For example:
id model
---- -----
1 Fusion
1 Corolla
1 Camaro
2 Explorer
2 Camry
2 Volt
I attempted to do this with the following controller:
class CarsController < ApplicationController
def index
#cars = []
#ford = Ford.paginate(:page => params[:page], :per_page => 10)
#toyota = Toyota.paginate(:page => params[:page], :per_page => 10)
#chevrolet = Chevrolet.paginate(:page => params[:page], :per_page => 10)
for i in 0..#ford.size-1 do
#cars << #ford[i]
#cars << #toyota[i]
#cars << #chevrolet[i]
end
end
end
Pagination works up to the last page. I get an error in the last page because less than 10 records are returned. The iteration over #cars in the view somehow expects 10 records. Thus, I get a null reference error.
I do not want to get all of the records from the ford, toyota, and chevrolet tables and then paginate. I want to request the records in batches, and then simply display them. My method is very likely the wrong way to do this. Any suggestions? Perhaps, I can create a view and query that?

Related

Rails scope that orders Users by a Reputation Score

I have a users table and I keep a reputation score on each user. The reputation score is the combined score from 2 factors:
total followers on the Users projects (projects he/she created)
total votes on the Users tasks (tasks that he/she created)
My goal is to allow users to filter users by most recent (the standard index action) or by users with the most reputation. What Rails scope would give back the list of Users ordered by this 'total reputation score' in order of highest score to lowest? thx,
My code is as follows:
User.RB Model
def total_reputation_score
project_follower_count + task_vote_count
end
scope :by_reputation, -> { #HOW DO I CREATE THIS SCOPE? }
Users Controller
def index
#users = User.text_search(params[:query]).paginate(page: params[:page], :per_page => 12)
end
def most_reputation
#users = policy_scope(User)
#users = #users.
text_search(params[:query]).
by_reputation.
paginate(page: params[:page], :per_page => 12)
render :index
end
While you can do this in SQL on the fly, you really want to:
Add a counter cache for each of the two count items shown here; this adds these as calculated columns that Rails will maintain for you, then do this for your scope:
User.order(User.arel_table[:project_follower_count] + User.arel_table[:task_vote_count]).reverse_order
(or something similar)
Alternately you can double down on your RDBMS and build out indices to handle on the fly.

Paginate from record

I use the gem will-paginate.
Lets suppose i have a model records that is sort by created_at and the client has the records until a specific record with the id 77. Now would it be possible to define for example:
Records.paginate(:page => params[:record_id], :per_page => 30)
So that the pagination doesnt`t start at a specific page but at a record
Thanks!
The unique alternative I can imagine is to add a where condition and sorting the results by id
Records.where("id >= ?", params[:record_id]).order(id: :asc).paginate(:page => params[:page], :per_page => 30)
This way you ensure that the record_id received is the first element in the pagination and all the results are after that one

Rails: How to display users having specific criteria in search?

When User ABC clicks on the "Search" link, I want ABC to see only those members who live in the same city as ABC. Right now I have the following code in the index action of the Users controller, which displays all the members registered on the website. I am also using will_paginate gem for pagination.
def index
#users = User.paginate :page => params[:page], :per_page => 10
end
In the view, I am iterating through the #users array to display all users.
However, I want ABC to only see members from his/her city. Once ABC can only see members from his/her city, I am going to implement filters to further narrow the results. But that is a later step. Also, ABC should not be able to see profiles of users from other city by simply typing their username in the address bar. How do I do this?
You could use ActiveRecord scopes (Ruby on Rails Guides / ActiveRecord Query Interface)
Then in your User model you will have something like this:
scope :living_in_the_same_city_with, lambda { |user| /* your where condition where(:city_id => user.city_id) */ }
And inside controller:
#users = User.living_in_the_same_city_with(current_user).paginate :page => params[:page], :per_page => 10

rails tire elasticsearch weird error

I have indexed a Car model with one car record mercedes benz in the database. If I search for the word benz I get an error:
ActiveRecord::RecordNotFound in CarsController#index
Couldn't find all Cars with IDs (1, 3) (found 1 results, but was looking for 2)
If I search for hello I get:
Couldn't find Car with id=2
Other random search terms work returning accurate results.
So it's basically random errors generated by random search terms. What could be the cause of this?
Controller:
def index
if params[:query].present?
#cars = Car.search(params)
else
#cars = Car.paginate(:page => params[:page], :per_page => 10)
end
end
Model:
def self.search(params)
tire.search(load: true, page: params[:page], per_page: 10) do |s|
s.query { string params[:query]} if params[:query].present?
end
end
This happens because, you are using the load => true option to load the search results from database. The activerecord seems to be missing in DB, but the elasticsearch index contains the same document.
Reindex is not always the solution IMHO.
The best solution is to delete the document when it is deleted in db. You can use the after_destroy callback for this.
Tire remove api is used to remove a single document from index.
Tire.index('my-index-name').remove('my-document-type', 'my-document-id')
Reference: https://github.com/karmi/tire/issues/43

How do I Order on common attribute of two models in the DB?

If i have two tables Books, CDs with corresponding models.
I want to display to the user a list of books and CDs. I also want to be able to sort this list on common attributes (release date, genre, price, etc.). I also have basic filtering on the common attributes.
The list will be large so I will be using pagination in manage the load.
items = []
items << CD.all(:limit => 20, :page => params[:page], :order => "genre ASC")
items << Book.all(:limit => 20, :page => params[:page], :order => "genre ASC")
re_sort(items,"genre ASC")
Right now I am doing two queries concatenating them and then sorting them. This is very inefficient. Also this breaks down when I use paging and filtering. If I am on page 2 of how do I know what page of each table individual table I am really on? There is no way to determine this information without getting all items from each table.
I have though that if I create a new Class called items that has a one to one relationship with either a Book or CD and do something like
Item.all(:limit => 20, :page => params[:page], :include => [:books, :cds], :order => "genre ASC")
However this gives back an ambiguous error. So can only be refined as
Item.all(:limit => 20, :page => params[:page], :include => [:books, :cds], :order => "books.genre ASC")
And does not interleave the books and CDs in a way that I want.
Any suggestions.
The Item model idea will work, but you are going to have to pull out all the common attributes and store them in Item. Then update all you forms to store those specific values in the new table. This way, adding a different media type later would be easier.
Update after comment:
What about a union? Do find_by_sql and hand-craft the SQL. It won't be simple, but your DB scheme isn't simple. So do something like this:
class Item < ActiveModel::Base
attr_reader :title, :genre, :media_type, ...
def self.search(options = {})
# parse options for search criteria, sorting, page, etc.
# will leave that for you :)
sql = <<-SQL
(SELECT id, title, genre, 'cd' AS media_type
FROM cds
WHERE ???
ORDER BY ???
LIMIT ???
) UNION
(SELECT id, title, genre, 'book' AS media_type
FROM books
WHERE ???
ORDER BY ???
LIMIT ???
)
SQL
items = find_by_sql(sql)
end
end
untested
Or something to that effect. Basically build the item rows on the fly (you will need a blank items table). The media_type column is so you know how to create the links when displaying the results.
Or...
Depending on how often the data changes you could, gasp, duplicate the needed data into the items table with a rake task on a schedule.
You say you can't change how books and CDs are stored in the database, but could you add an items view? Do you have any control over the database at all?
CREATE VIEW items
(id, type, title, genre, created_at, updated_at)
AS
SELECT b.id, 'Book', b.title, b.genre, b.created_at, b.updated_at
FROM books b
UNION
SELECT c.id, 'CD', c.title, c.genre, c.created_at, c.updated_at
FROM cds c;
You can paginate on a results array, so leave pagination out of the invidual model queries, and add it to your results array:
re_sort(items,"genre ASC").paginate(:page => params[:page], :per_page => items_per_page)

Resources