Pagination prob in Grails - grails

I want to display all games under a certain category in pagination (10/page)
This is my controller
def listGame(){
def category = GameCategory.list()
def platform = Platform.list()
def currentCategory = params.categoryName
def myCategory=GameCategory.findByCategoryName(currentCategory)
def games = myCategory.games
[currentCategory:currentCategory, category:category, games:games, platforms:platform, gameCount:Game.count(), chosenPlatform:params.platform]
}
and this is my pagination code in my view
<g:each in="${games}" status="i" var="game">
${game.gameTitle}
</g:each>
<div class="pagination">
<g:paginate action="listGame" total="${gameCount}" />
</div>
When I have 9 games in the current category the page 12 showed up wherein it shouldn't
When I have 11 games in the current category all 11 games are displayed in page 1 and clicking page 2 will result in this error

There are a couple of things to address.
Make sure categoryName is not coming into the controller as null.
Make use of the max and offset parameters that the paginator is providing to select the appropriate Games.
The category name
Because the paginator is creating the URL that calls into the controller, and your controller needs the category name, the paginator must add the category name to the params it calls the controller action with. You can achieve this with the tag's params attribute:
<g:paginate action="listGame" total="${gameCount}" params="${[categoryName: currentCategory]}"/>
Max & offset
The controller needs to use max and offset parameters to know what subset of the Games to retrieve from the database and render in the view. However, in some cases, such as when visiting the URL http://blah/someController/listGame, there would be no max and offset params. This is something your controller action must be able to handle. It goes something like this:
def max = params.max ?: 10
def offset = params.offset ?: 0
So the max and offset params will be used if they are available. Otherwise they'll use default values.
Finally, you need to use these parameters to select the Games. For that you can use a where query:
def games = Game.where {
categories.categoryName == currentCategory
}.list(max: max, offset: offset)
Something interesting about the returned games object: It acts like a list, but it's actually a grails.gorm.PagedResultList. That's a good thing because it contains the total number of records in the result (ignoring max and offset). In other words, it's your new gameCount.
You can read more about where queries in the Grails documentation. I also have a series of articles which cover where, criteria and HQL queries in more detail.
Putting it all together, listGame() would look like this:
def listGame(){
def category = GameCategory.list()
def platform = Platform.list()
def currentCategory = params.categoryName
def max = params.max ?: 10
def offset = params.offset ?: 0
def games = Game.where {
categories.categoryName == currentCategory
}.list(max: max, offset: offset)
[currentCategory:currentCategory, category:category, games:games, platforms:platform, gameCount:games.totalCount, chosenPlatform:params.platform]
}

Looks like you haven't done your homework. Please go through grails doc and other tutorials, to understand how pagination works.
Just to give you a hint. You are doing two things wrong here.
You are not getting a paginated list at the first place from controller
You are not sending back all the required data back to the controller, from the pagination link. And that's the reason you are getting the error!

Related

Rails - More about "N+1"

When I try to show linked data in index action - I can't avoid N+1 problem using standart includes. Example:
Model 1 - Pet (let it will be animal: title:string. Pet can has many PetAttributeElems, they show attributes this Pet have and what values they have)
Model 2 - PetAttribute (this model contains only titles of attributes, like weight, age and 1000+ more attributes. PetAttribute has many PetAttributeElems - one attribute, such as weight, can be described for many Pets)
Model 3 - PetAttributeElem (this model belongs to pet and to petAttribute, also it has value field, that show value of attribute for pet.
When I make show action - I use in HAML:
-#pet.pet_attribute_elems.includes(:pet_attribute).each do |elem|
="#{elem.pet_attribtute.title}: #{elem.value}"
But when I make index action I want to use:
-#pets.each do |pet|
-pet.pet_attribute_elems.includes(:pet_attribute).each do |elem|
="#{elem.pet_attribtute.title}: #{elem.value}"
That includes method will call many SQL queries, for every pet
Now I solve it by manually creating additional object like this:
#pet_elems = {}
PetAtributeElems.includes(:pet_attribute)
.where(pet_id:#pets.map(&:id)).each do |elem|
pet_id = elem.pet_id
if #pet_elems.include?(pet_id)
#pet_elems[pet_id] << elem
else
#pet_elems[pet_id] = [elem]
end
end
Than I can use:
-#pets.each do |pet|
-if #pet_elems.include?(pet.id)
-#pet_elems[pet.id].each do |elem|
="#{elem.pet_attribtute.title}: #{elem.value}"
How could I solve that task more easy?
You're going down a non rails-convention path.
Move code out of the views so it's simply
= render #pet_attribute_elems
Make a partial to handle display
# _pet_attribute_elems.html.haml
="#{pet_attribute_elem.pet_attribtute.title}: #{pet_attribute_elem.value}"
In the controller, do the queries
def show
#pet = Pet.find(...)
#pet_attribute_elems = #pet.pet_attribute_elems.includes(:pet_attribute)
end
def index
#pet_attribute_elems = PetAttributeElem.includes(:pet_attribute)
end

How does one get the "next" record from database sorted by a specific attribute without loading all the records?

Here's the situation:
I have an Event model and I want to add prev / next buttons to a view to get the next event, but sorted by the event start datetime, not the ID/created_at.
So the events are created in the order that start, so I can compare IDs or get the next highest ID or anything like that. E.g. Event ID 2 starts before Event ID 3. So Event.next(3) should return Event ID 2.
At first I was passing the start datetime as a param and getting the next one, but this failed when there were 2 events with the same start. The param start datetime doesn't include microseconds, so what would happen is something like this:
order("start > ?",current_start).first
would keep returning the same event over and over because current_start wouldn't include microseconds, so the current event would technically be > than current_start by 0.000000124 seconds or something like that.
The way I got to work for everything was with a concern like this:
module PrevNext
extend ActiveSupport::Concern
module ClassMethods
def next(id)
find_by(id: chron_ids[current_index(id)+1])
end
def prev(id)
find_by(id: chron_ids[current_index(id)-1])
end
def chron_ids
#chron_ids ||= order("#{order_by_attr} ASC").ids
end
def current_index(id)
chron_ids.find_index(id)
end
def order_by_attr
#order_by_attr ||= 'created_at'
end
end
end
Model:
class Event < ActiveRecord::Base
...
include PrevNext
def self.order_by_attr
#order_by_attr ||= "start_datetime"
end
...
end
I know pulling all the IDs into an array is bad and dumb* but i don't know how to
Get a list of the records in the order I want
Jump to a specific record in that list (current event)
and then get the next record
...all in one ActiveRecord query. (Using Rails 4 w/ PostgreSQL)
*This table will likely never have more than 10k records, so it's not catastrophically bad and dumb.
The best I could manage was to pull out only the IDs in order and then memoize them.
Ideally, i'd like to do this by just passing the Event ID, rather than a start date params, since it's passed via GET param, so the less URL encoding and decoding the better.
There has to be a better way to do this. I posted it on Reddit as well, but the only suggested response didn't actually work.
Reddit Link
Any help or insight is appreciated. Thanks!
You can get the next n records by using the SQL OFFSET keyword:
china = Country.order(:population).first
india = City.order(:population).offset(1).take
# SELECT * FROM countries ORDER BY population LIMIT 1 OFFSET 1
Which is how pagination for example often is done:
#countries = Country.order(:population).limit(50)
#countries = scope.offset( params[:page].to_i * 50 ) if params[:page]
Another way to do this is by using would be query cursors. However ActiveRecord does not support this and it building a generally reusable solution would be quite a task and may not be very useful in the end.

Rails db query to find and sort posts

My app has designs that users can like (vote, using acts_as_voteable). To find a design's like count in the view, you use
#design.votes.count
I'm making a popular page to showcase the most popular designs based on the number of votes they have. I only want designs that has at least 5 votes to them. Right now, I had that in the view but I want to push that into the controller. My controller, thus far, looks like this which shows all the designs and sorts them in order of most votes.
def popular
#designs = Design.all
#designs.sort! {|t1, t2| t2.votes.count <=> t1.votes.count}
end
Now i just want to make sure the designs have a minimum vote count of 5.
Previously, I was doing this the wrong way and putting it in my view by putting this inside my Design loop
<% if design.vote.count > 5 %>
...
<% end %>
Thanks!
First of all, the behavior you want is better defined in the Design model and not in the controller since it deals with data. So in your Design model, add the following code:
scope :most_popular, -> do
results = select {|design| design.votes.count > 4 }
results.sort! {|t1, t2| t2.votes.count <=> t1.votes.count}
end
Adding the two scope methods above in your Design model, you could do this in your controller code:
def popular
#designs = Design.most_popular
end
Your controller code ends up being a lot cleaner and you have scope methods that you can reuse anywhere else you need them. :)
Hope that helps!
You can use a having() clause. See: http://guides.rubyonrails.org/active_record_querying.html#having
For example: Design.joins(:votes).group('votes.design_id').having('votes.count > 5').order('votes.count')
Edit
You can also just use a where clause. For example, for the first design:
Design.first.votes.where('count > 5')
Example for multiple designs:
Design.all.map{ |a| a.votes.where('count > 5').count }.sort! # returns a sorted array with all vote counts

Rails: trying to store particular column value in an array

I am building an application in rails, and I have an items_controller which contains the methods application for create, show, edit, destroy etc.
However, I am trying to create my own method to access all the values in a specific column of my database and I am having greatly difficulty in capturing this data in an array.
I have tried the following ways of capturing the data (where 'quantity' is the column in the database for which I looking for):
#items = Item.find(params[:id])
#items2 = #item.find(params[:quantity])
I have also tried:
#items = Item.find(params[:quantity])
& even:
#items = Item.all
#items2 = #item.find(params[:quantity])
However, none of these methods appear to be working. For what I am doing it is not even essential to
know which quantity column values relate to which row...just getting a list of the column values would suffice.
If any one knows what is wrong here, the help you be very greatly appreciated!
Thanks.
UPDATE:
For clarity I am trying to retrieve all the data for a particular column in my database associated with the items_controller and filter the data for a particular piece of data (in this case the string "7" - as the data is returned from the db as a string when using the Items.all method.
I then want a counter to increase each time the "7" is encountered in the quantity column.
def chartItems
#items = Item.find(params[:id])
#items2 = #items.find(params[:quantity])
#filter = custom_filter_for(#items2)
def custom_filter_for(value)
j=0 # counter initialised at 0
value.each do |x|
if x == "7" # checking for data equal to "7" - the num is retrieved as a string
j = j+1 # increase j counter by 1 whenever "7" is encountered as a quantity
end
return j
end
end
Your find parameter is handled as an id in this case:
#items = Item.find(params[:quantity])
All items are returned which has the id of your quantity parameter. This is clearly not what you want.
You can select Items based on quantity:
#items = Item.find_by_quantity(params[:quantity])
But if you need only the quantities in an array, this is what you are looking for:
#quantities = Items.select(:quantity).map(&:quantity)
Your updated question:
result = Items.find_by_quantity(params[:quantity]).count
In new versions of ActiveRecord, they've added the pluck which does essentially what #Matzi's select and map method does.
To get all item quantities, you could do
#quantities = Item.pluck(:quantity)
Also, I would double check your use of the find_by helpers. I think that find_by_quantity will only give you a single match back (#item, not #items). To get all, I think you really want to use where
#quantities = Item.where(:quantity => params[:quantity])
If you were to use the pluck I mentioned above, I think your filtering step could also be written pretty concisely. That filter is simply counting the number of 7's in the list, right?
#quantities = Item.pluck(:quantity)
#filtered_values = #quantities.select{|q| q == 7}.length
I hope this helps out.

How can I limit an existing Rails AR query by a certain time frame?

I have a dashboard(esque) view in a Rails app which is showing some data in similar ways but broken out into many time periods.
I have some code in my controller like so:
#issues_this_month = Issue.where('issues.created_at BETWEEN ? AND ?', DateTime.now.in_time_zone.beginning_of_month, DateTime.now.in_time_zone.end_of_month)
and I also want to create a variables which shows issues this year and issues all time so I have this code:
#issues_this_year = Issue.where('issues.created_at BETWEEN ? AND ?', DateTime.now.in_time_zone.beginning_of_year, DateTime.now.in_time_zone.end_of_year)
I am curious if someone can think of a good way of doing one query, and from that inferring the date ranges all while avoiding the extra queries. Should I pass the results to a helper method and do the logic there?
in the model... you can define
def date
self.created_at.to_date
end
then in the controller
start = Date.today.beginning_of_year
end = Date.today.end_of_year
#issues_this_year = Issue.where(create_at: start..end).group_by(&:date)
now you have a hash of [month_1, {issues that exist in month_1}, month_2, {issues that exist in month_2}, etc]. play with it in the console to find the proper keys... #issues_this_year.keys
How about defining a method like
def self.in_timeframe(start_time=DateTime.now.in_time_zone.beginning_of_month,
end_time=DateTime.now.in_time_zone.end_of_month)
Issue.where('issues.created_at BETWEEN ? AND ?', start_time, end_time)
end
You can now invoke this as follows:
Issue.in_timeframe # For issues in the month
Issue.in_timeframe(x,y) # For issues within x-y timeframe
If you want the data in a single query, you could do stuff like:
def self.in_timeframes(time_frames)
data = {}
times_frames.each do |time_frame|
data[time_frame[:name]] = Issue.in_timeframe(time_frame[:srtart]. time_frame[:end])
end
data
end
You can invoke the above method using:
time_frames = [{:name=>"month"},
{:name=>"x-y", :start=>x, :end=>y}]
Issue.in_timeframes(time_frames)

Resources