I'm using will_paginate to display data returned from a query that includes both a joins and a select statement. When I paginate the data the number of entries is equal to the number of entries before executing the select statement, even though paginate is being called after the query, and the query contains fewer elements than paginate reports.
#sales = Sale.joins(:line_items).where(company_id: company_id, status: ['Complete', 'Voided'], time: (midnight_1..midnight_2)).order('id DESC')
puts #sales.length
14
#sales = #sales.select('distinct sales.*')
puts #sales.length
4
#sales.paginate(:per_page => 4, :page => params[page])
puts #sales.total_entries
14
This leads to displaying links to empty pages.
It's always going to be slightly harder to paginate and join in has_many or has_and_belongs_to_many associations with will_paginate, or indeed any pagination solution.
If you don't need to query on the joined in association you can remove it. You lose the benefit of getting the associated line items in one query but you don't lose that much.
If you need to query on it, and presumably you want sales that only have line items, you'll need to pass in a :count option to the paginate call which specifies additional options that are used for the call to count how many items there are. In your case:
#sales.paginate(:per_page => 4,
:page => params[page],
:count => {:group => 'sales.id' })
Assuming that your Sale model has_many :line_items, by joining you're going to get a 'sales' entry for every related 'line_item'.
Related
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.
The find methods are very convenient for retrieving records, and I'm frequently using :include to prefetch referenced records to avoid expensive db accesses.
I have a case where I retrieve all sales by a salesperson.
#sales = Sales.find(:all,
:include => [:salesperson, :customer, :batch, :product],
:conditions => {:salesperson_id => someone},
:order => :customer_id)
I then want to slice and dice the returned records based on what was returned. For instance, I want to produce a report for all the sales made by this salesperson at a particular store, which we know is a subset of the previously returned data.
What I'd like to do is,
#storeSales = #sales.find_by_store(store_id)
...and retrieve this subset from the array held in memory as a new array, rather than achieve the same thing by performing a find on the database again. After all, #sales is just a array of Sales objects, so it doesn't seem unreasonable that this should be supported.
However, it doesn't seem that there's a convenient way to do this, is there? Thanks.
If you are using Rails 3, #sales will be an AREL criteria object. What you can do is as follows:
#sales = Sales.find(:all,
:include => [:salesperson, :customer, :batch, :product],
:conditions => {:salesperson_id => someone},
:order => :customer_id)**.all**
Now #sales is an instance of an Array of Sales model objects. Getting a subset of the array objects is now easy using the select method:
#my_product_sales = #sales.select { |s| s.product == my_product_criteria }
Upon using select method you will now have #sales being the full result set and #my_product_sales being the subset based on the collect criteria.
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)
In my rails project, I have a query which finds the 10 most recent contests and orders by their associated poll dates:
#contests = Contest.find(
:all,
:limit => "10",
:include => :polls,
:order => "polls.start_date DESC" )
Currently this shows each contest and then iterates through associated polls sorting the master list by poll start date.
Some of these contests have the same :geo, :office and :cd attributes. I would like to combine those in the view, so rather than listing each contest and iterating through each associated poll (as I'm doing right now), I'd like to iterate through each unique combination of :geo, :office and :cd and then for each "group," iterate through all associated polls regardless of associated contest and sort by polls.start_date. I'd like to do this without having to create more cruft in the db.
Unless I've misunderstood, I think you might be looking for this:
#contests.group_by { |c| [c.geo, c.office, c.cd] }
It gives you a Hash, keyed on [c.geo, c.office, c.cd], each entry of which contains an Array of the contests that share the combination.
I have a list of 'request' objects, each of which has fairly normal activerecord qualities. The requests table is related to the games table with a join table, 'games_requests,' so that a request has a request.games array.
The question is, is there a way to do a find for the last n unique requests, where uniqueness is defined by the games column and a couple others, but specifically ignores other colums (like the name of the requesting user?)
I saw a syntax like 'find (:all, :limit=>5, :include=>[:games,:stage])' but that was returning duplicates.
Thanks...
EDIT: Thanks to chaos for a great response. You got me really close, but I still need the returns to be valid request objects: the first 5 records that are distinct in the requested rows. I could just use the find as you constructed it and then do a second find for the first row in the table that matches each of the sets returned by the first find.
EDIT:
Games.find(
:all, :limit => 5,
:include => [:games, :requests],
:group => 'games, whatever, whatever_else'
)
...gives an SQL error:
Mysql::Error: Unknown column 'games' in 'group statement': SELECT * FROM `games` GROUP BY games
I made a few changes for what I assumed to be correct for my project; getting a list of requests instead of games, etc:
Request.find(
:all, :order=>"id DESC", :limit=>5,
:include=>[:games], #including requests here generates an sql error
:group=>'games, etc' #mysql error: games isn't an attribute of requests
:conditions=>'etc'
)
I'm thinking I'm going to have to use the :join=> option here.
Games.find(
:all, :limit => 5,
:include => [:games, :requests],
:group => 'games, whatever, whatever_else'
)
Try Rails uniq_by.It also works with association and returns array.
#document = Model.uniq_by(&:field)
More Detail
I think you'll be able to do this using find_by_sql and GROUP BY:
Games.find_by_sql("SELECT * FROM games GROUP BY user_id")