Rails - Reducing queries using includes - ruby-on-rails

This is my controller:
#trainings = Training.includes(:courses).order(:id, 'courses.order_id ASC')
In my view, for each training I need to loop for 3 times and check if has a course with column order_id with these values:
#trainings.each do |training|
for course_order in (1..3) do
this_course = training.courses.find_by(order_id: course_order)
# this_course.image(:resized) #i can print the paperclip image
end
end
The code above executes so many queries, so I tried using select method:
#trainings.each do |training|
for course_order in (1..3) do
this_course = training.courses.select { |course| course.order_id = course_order }
# this_course.image(:resized) #I cannot print paperclip image, because the result is an Array, so doesn't know the method "image"
end
end
So I have just one query, but I cannot call the image method from my Model, because the result is an Array object.
I know I can use:
training.courses.each do |course|
# course.image(:resized)
end
and I will get just one query, but I must loop 3 times, so when I dont have the row I can print a placeholder image, like:
Example image http://www.onrails.com.br/order_id.jpg

Your
this_course = training.courses.find_by(order_id: course_order)
returns first record that matches conditions, but
this_course = training.courses.select { |course| course.order_id == course_order }
selects all courses that match condition. If you want to find only the first one, use .detect instead of .select:
this_course = training.courses.detect { |course| course.order_id == course_order }
Also you have a typo. = is for assignment, == is for comparing.

Related

How to calculate specific rating count hash in ruby on rails?

So, I have an after_save hook on review model which calls calculate_specific_rating function of product model. The function goes like this:
def calculate_specific_rating
ratings = reviews.reload.all.pluck(:rating)
specific_rating = Hash.new(0)
ratings.each { |rating| specific_rating[rating] += 1 }
self.specific_rating = specific_rating
save
end
Right now, it returns
specific_rating => {
"2"=> 3, "4"=> 1
}
I want it to return like:
specific_rating => {
"1"=> 0, "2"=>3, "3"=>0, "4"=>1, "5"=>0
}
Also, is it okay to initialize a new hash everytime a review is saved? I want some alternative. Thanks
You can create a range from 1 until the maximum value in ratings plus 1 and start iterating through it, yielding an array where the first element is the current one, and the second element is the total of times the current element is present in ratings. After everything the result is converted to a hash:
self.specific_rating = (1..ratings.max + 1).to_h { |e| [e.to_s, ratings.count(e)] }
save
You could also do something like this -
def calculate_specific_rating
ratings = [1,2,3,4,5]
existing_ratings = reviews.group_by(&:rating).map{|k,v| [k, v.count]}.to_h
Hash[(ratings - existing_ratings.keys).map {|x| [x, 0]}].merge(existing_ratings)
end
which gives
{3=>0, 4=>0, 5=>0, 2=>3, 1=>1}

Select certain record within response from ActiveRecord

I have a call to ActiveRecord in my controller as so:
#configurations = EmailConfiguration.where(customer_id: '1', email_template: '1')
This will return all EmailConfigurations that have the correct parameters. Each record has a field_id and a the_value. I want to display the value in the view:
#configurations.where(field_id: 1).the_value
What do I need to add to the view to select a certain record within the collection that is returned by the database?
You can use select for a quick filter on arrays
#configurations.select {|c| c.field_id == 1}
that will return all collections with field_id = 1. If you know there is only one, you could chain it for a direct output:
#configurations.select {|c| c.field_id == 1}.first.the_value
#configurations.where(field_id: 1)
returns a collection of objects(array) even if there is only one result. If you would like to show only one you can do as suggested above:
#configurations.select {|c| c.field_id == 1}.first.the_value
If you want to show all of the "the_values" you can do
field_1_configs = #configurations.select do |c| c.field_id == 1
end
field_1_configs.map{|config| config.the_value }

Reiterate over collection

I have an unknown number of categories.
I want to pick one post from each category, and when there are no more categories I want to start from the beginning until I've reached a fixed number posts.
This is what I have, how could I rerun this iteration until I have my desired amount of posts?
desired_amount = 40
categories.each_with_index do |category, index|
post = category.posts.order(position: :asc)[index]
# do something with the post
return if desired_amount == (index + 1)
end
Personally, I would much prefer something like this:
posts = categories.cycle.take(desired_amount).each_with_index.map do |cat,ind|
cat.posts.order(position: :asc)[ind / categories.count]
end
That would give you the first post in each category, followed by the second post in each category, etc, until you had the number of posts you wanted. The one caveat is that if any category didn't have enough posts, your final array would have some empty spots in it (i.e. nils).
Maybe try something like this?
all_posts = []
#include posts to prevent constant querying the db
categories_with_posts = categories.includes(:posts)
until all_posts.size == 40
categories_with_posts.each do |category|
#pick a random post from current category posts
post = category.posts.order(position: :asc).sample
# add the post to collection if post is not nil
all_posts << post if post
# do something with the post
break if all_posts.size == 40
end
end
You could define an array of post before starting to loop:
desired_amount = 40
posts_array = []
unless posts_array.count == desired_amount
categories.each_with_index do |category, index|
post = category.posts.order(position: :asc)[index]
posts_array << post
return if desired_amount == (index + 1)
end
end

Handling a nil value from an instance method in Rails

I have a Product model which has many Items. The application lists unique items which belong to a product. So think of items as inventory. The following query grabs featured items for a product and removes the first item (irrelevant, but it becomes a featured item, displayed separately, if you're curious).
# product.rb
has_many :items_in_stock, -> { Item.in_stock }, class_name: 'Item'
def featured_items
items_in_stock.select("DISTINCT ON (condition) id, items.*")
.order(:condition, :price)
.sort_by { |item| item[:price] }[1..-1]
end
# item.rb
scope :in_stock, -> { where(status: 'in_stock') }
The trouble is when the feaured_items are empty, the method returns nil, and not a relation object. This means I get an error if I call #product.featured_items.any? on a product that has no items. If I remove the sort_by block, I get an empty relation object.
Is there a good way to handle this other than:
items = items_in_stock.select("DISTINCT ON (condition) id, items.*").order(:condition, :price)
if items.any?
items.sort_by { |item| item[:price] }[1..-1]
end
I can't reverse the ordering of the query because I get an error saying the order of the conditions in the order by statement must match the group conditions.
I'm confused...why call .any? on it then since nil is treated as false in ruby. If what you get back is nil then you know that you don't have any featured_items.
I ran this in irb and I think your issue is the [1..-1].
a = []
# => []
a.sort_by { |w| w.length }
# => []
a.sort_by { |w| w.length }[1..-1]
# => nil
The easiest way is to just do
items = items_in_stock.select("DISTINCT ON (condition) id, items.*")
.order(:condition, :price)
.sort_by { |item| item[:price] }
items.any? ? items[1..-1] : items
Then you don't actually have to do a check in other parts of your code unless it's necessary.
instead of if items.any? you can use unless items.blank? if it's nil or empty, it won't run the condition
items.blank? checks both items.empty? and items.nil?
And of course you can use it in your featured_items
items = items_in_stock.select("DISTINCT ON (condition) id, items.*")
.order(:condition, :price)
.sort_by { |item| item[:price] }[1..-1]
return Array.new if items.blank?
That way you know that result will be an array, no matter what
And for the proof, you can use .blank? on a nil object, and it works on nil itself, nil.blank? returns true

Converting an array with only one object in Ruby

Say I have an array, and I use the keep_if or select methods to delete everything but one object for the array -- is there a way to take that object out of the array, so it's not an array any more?
For example, the way my models are set up, previous and current will only ever have one object.
def more_or_less?(project, current_day)
words = project.words
current = words.select {|w| w.wrote_on == current_day}
previous = words.select {|w| w.wrote_on == (current_day - 1.day)}
end
So, if I want to do a comparison, like if current.quantity > previous.quantity -- I've resorted to actually writing if current.last.quantity > previous.last.quantity but I'm assuming there's a more idiomatic way?
If you're deleting all but one entries, why not use find to choose the one thing you care about? Something like this:
def more_or_less?(project, current_day)
words = project.words
current = words.find { |w| w.wrote_on == current_day }
previous = words.find { |w| w.wrote_on == (current_day - 1.day) }
if current.quantity > previous.quantity
#...
end
end
If you're in ActiveRecord land, then you can use detect as derp says or to_a to get a plain array that you can use find on:
def more_or_less?(project, current_day)
words = project.words
current = words.detect { |w| w.wrote_on == current_day }
previous = words.to_a.find { |w| w.wrote_on == (current_day - 1.day) }
if current.quantity > previous.quantity
#...
end
end
detect is an alias for find, maybe that would work

Resources