Kaminari pagination breaking with where clause on has_many association - ruby-on-rails

We have two models
Book has_many :reviews
Review belongs_to :book
A Review has a rating (from 1 to 5).
book1
review1 - rating 5
review2 - rating 5
review3 - rating 4
book2
review4 - rating 5
book3
review5 - rating 5
Let say I need to filter all books that have a rating of 5 I would do something like:
#books = Book.includes(:reviews)
.where(reviews: { rating: 5 })
.page(params[:page]).per(3)
The issue is that I get the following
book1
review1 - rating 5
review2 - rating 5
book2
review4 - rating 5
This is not working and is breaking pagination as the #books.total_count is 2 not 3 (which is the triggering pagination as we require per(3)). How would you fix it ?
I would expect the query to render 3 Books with all the reviews matching the where clause
book1
review1 - rating 5
review2 - rating 5
book2
review4 - rating 5
book3
review5 - rating 5

Pagination can be fixed by using
#books = Book.includes(:reviews)
.where(reviews: { rating: 5 })
#books = Kaminari.paginate_array(#books).page(params[:page]).per(4)
This works as expected but is not performant as there is no limit applied to #books on the first query. I think this should be optimized by using manual pagination.

Related

RAILS: How to select fields from associated table with grouping and create a hash from result?

I would like to create an active record query and store the result in a hash which includes summary information from associated tables as follows.
Here is the tables and associations:
Post belongs_to: category
Category has_many: posts
Here I would like to count the # of posts in each category and create a summary table as follows (with the SQL query for the desired table):
select c.name, count(p.id) from posts a left join categories c on p.category_id = c.id where p.status = 'Approved' group by (c.name) order by (c.name);
Category | count
---------------+-------
Basketball | 2
Football | 3
Hockey | 4
(3 rows)
Lastly I would like to store the result in a hash as follows:
summary_hash = { 'Basketball' => 2, 'Football' => 3, 'Hockey' => 4 }
I will appreciate if you can guide me how to write the active record query and store the result in the hash.
Try
Post.where(status: 'Approved').joins(:category).
select("categories.name").group("categories.name").count

SQLite, Ruby on rails. How to get the top voted objects with acts_as_votable gem?

I"m trying to display the best 3 Recipes from the last 7 days.
The best is dependant on the amount of votes it has using the acts_as_votable gem but i have not created a cache.
current user.rb, limits to the last 7 days
def featuredfeed
Recipe.where('created_at >= ?', Time.zone.now - 1.week)
end
recipe.rb
class Recipe < ActiveRecord::Base
belongs_to :user
acts_as_votable
....
default_scope -> { order(created_at: :desc) }
end
the votes table is ordered
id, votable_id, votable_type, voter_id, voter_type, vote_flag, vote_scope, vote_weight, created_at, updated_at
The votable_id is the id of the recipe which needs to be counted for the number of times it has been upvoted
I would like to say, that caching votes is much cleaner solution, than the following one. It is just a matter of adding a new migration. But up to you..
Get the ids of recent recipes (this part you already have done):
recipes_ids = Recipe.where('created_at >= ?', Time.zone.now - 1.week)
Filter the votes for this recipes:
Vote
.where(votable_id: recipes_ids)
Group them by votable_id:
.group(:votable_id)
And find those, which have more, at least 1 vote:
.having('count(votable_id) > 1')
Now take the ids of 3 most upvoted recipes:
most_voted_recipes_ids = Vote
.where(votable_id: recipes_ids)
.group(:votable_id)
.having('count(votable_id) > 1')
.limit(3).votable_ids # or limit(3).pluck(:votable_id)
And, lastly, find those most popular recipes:
most_popular_recipes = Recipe.where(id: most_voted_recipes_ids)

ActiveRecord return uniq item by title condition nearest distance

I have following relation, because same product can exist in multiple stores, so DB stores the same product record in database with different store_id for each store, I also stores the coords for each product, now my query returns duplicate products by title for each store.
For quick fix how can I modify the query to it returns the closest products only and group by the product title maybe. BTW I am using geocode gem and near function to select nearby product near([#lat, #long], 20, order: #sort.blank? ? 'distance' : false)
class Product < ActiveRecord::Base
belongs_to :store
end
class Store < ActiveRecord::Base
has_many :products
end
ID TITLE STORE_ID
1 product_1 1
2 product_1 2
3 product_1 3
4 product_2 1
5 product_2 2
6 product_2 3
I think you have a misconception in your model. If the same product can be in several stores, then A product does NOT belong to a store. Cause it could be in several.
You should change your associations to a has_and_belongs_to_many or a has_many through one. But if you really want this query done, you can use the group method in your query.
Like Product.near(...).group(:title)
You can also filter after in Rails. Assuming it's already an ordered set, something like
#products.to_a.uniq(&:title)
would work.

Rails app with postgresql; query with .take does not keep the order of records

I have a rails 4 application and I recently switched from SQlite to Postgresql. I have the following 3 models;
class User < ActiveRecord::Base
has_many :user_games
has_many :games, :through => :user_games
end
class Game < ActiveRecord::Base
has_many :user_games
has_many :users, :through => :user_games
end
class UserGame < ActiveRecord::Base
belongs_to :user
belongs_to :game
end
with the records:
game
id
1
userGame
id user_id game_id
1 5 1
2 3 1
user
id
1
2
I could do this with sqlite:
game = Game.Find(1)
game.users.take(2).first
which would return User with id 5.
The same code with postgres gives user with id 3.
Why?
EDIT:
I forgot to add that I need to get the users in the order they appear in the table.
So if...
game
id
1
2
userGame
id user_id game_id
1 5 1
2 3 1
3 1 2
4 4 2
user
id
1
2
3
4
... I need a query that preserves the order of the users, so that game1.users.first.id = 5 and game2.users.first.id = 1
game1.users.take(2).first.id would preserve this order when I used sqlite, but with postgresql it does not.
take has the following explanation in manual:
Take
Gives a record (or N records if a parameter is supplied) without any
implied order. The order will depend on the database implementation. If
an order is supplied it will be respected.
If you want to get the same result with Postgresql and with Sqlite specify a default order.
game.users.order('users.id DESC').first
Or since last and first method` sorts your data in Rails 4 use:
game.users.last
This will translate into the following SQL:
SELECT * FROM users WHERE "games"."id" = 1 ORDER BY "users"."id" DESC LIMIT 1
My conclusion is that you don't need to use take at all
Edit
You have:
game = Game.find(1)
users = game.users.order("user_games.id ASC").take(2)
Then, if you iterate over your users collection:
users.each do |user|
puts user.inspect
end
you should get those users ordered as you want.

Creating items in groups in Rails

My product table is
id type price location
1 chips $3 aisle3
I have a question with adding the products in groups.
There is a quantity field(nonmodel) where the user can enter the quantity
While adding a new product if the user enters:
type: soda
quantity: 3
Then there 3 records should be created in product model with type= soda like the following.
id type
2 soda
3 soda
4 soda
If user enters
location: aisle4
quantity: 2
Then
id location
5 ailse4
6 ailse4
Can you tell me how to pass the nonmodel field 'quantity' to the rails(model or controller) and how use it to add the products in groups as mentioned above? or should I create a column called quantity in my product table? Will the history be updated too for all these new records with after_create filter which I already have ?
Is there any good tutorial or book which shows how to pass such nonmodel html/javascript fields from view to rails and then back to the view?
Any help will be greatly appreciated.
Thanks
Try this:
class Product < ActiveRecord:Base
attr_accessor :quantity
def self.create_in_group(params)
size, i = params["quantity"].to_i, 0
size.times { Product.create(params);i+=1 }
i == size
end
end
class ProductsController < ApplicationController
def create
if Product.create_in_group(params[:product])
# success
else
# error
end
end
end
PS: In your view you can access the quantity field as though it is a product model field.

Resources