Rails Union Table and sort by common key - ruby-on-rails

I have two models in rails application Match and Round as follow
Match(id, team1, team2, series_id, event_time, event_lock_time, match_type, created_at, updated_at, match_no, match_stage)
Round(id, series_id, name, description, event_time, event_lock_time, team_ids, matches_count, created_at, updated_at)
I would like to add two table and sort records based on created_at something like
events = (Match + Round).order('created_at desc').paginate(:page => params[:page], :per_page => params[:per_page])
and also apply pagination. I am using will paginations gem.
Can anyone help me how to do this in active record.

As long as I know, models doesn't have + operator. So you can't do that:
(Match + Round)
But you can add Active Record Relations:
matches = Match.all
rounds = Round.all
(matches + rounds).sort_by(&:created_at)
You have to use sort_by or a similar way because (matches + rounds) is Array and not an Active Record Relation (so order is not defined).
UPDATE:
I guess you are using will_paginate to paginate the results. It still works with array but you have to tell that and you can tell it in two ways:
Add this to the top of the controller: require 'will_paginate/array'
Create an initializer config/initializers/will_paginate_with_array.rb and require it inside this file.
Then, you can do:
(matches + rounds).sort_by(&:created_at).paginate(:page => params[:page], , :per_page => params[:per_page])

Related

How to merge multiple and different ActiveRecord query in rails and paginate over the records?

I have two similar tables in rails named as messages and message_histories. Periodically we remove the old data from messages table and drop it in message_histories .
Now , I want to generate a report on the count of messages grouped by app_id which is present in message and message_history table .
Is there a way to Query [Message & MessageHistory ] Model and paginate on the records .
Currently I use the following Step , It looks weird but suggest a better way to do this :
#messages = Message.select("SUM(devices_count) as count ,CAST(run_at AS DATE) AS date").where(:app_id => #app.id).where("last_run_date is not ?", nil).group("CAST(run_at AS DATE)").order("date desc")
#messages << MessageHistory.select("SUM(devices_count) as count ,CAST(run_at AS DATE) AS date").where(:app_id => #app.id).where("last_run_date is not ?", nil).group("CAST(run_at AS DATE)").order("date desc")
#messages = #messages.flatten.paginate(:page => params[:page] || 1, :per_page => 100)
It sounds like what you want is to UNION the two tables, then paginate the results.
See ActiveRecord Query Union for some examples, and look at the active_record_union gem (which makes it easier to do UNIONs with ActiveRecord).
It will take some experimentation to figure out how to apply the where clause filters and sums/groups properly!

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

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 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