Rails: loading association at once vs multiple tiny queries - sql

I've got a situation. I am rendering a paginated collection of resources(let's say Posts). I need to show whether the post is liked by the current user. So basically we have three tables: posts, users and likes(a join table o indicate if users like some posts).
I came up with three solutions:
class Post
# 1
def liked_by?(user)
likes.exists?(user: user) # usage: Post.limit(50).map { |post| [p.id, post.liked_by?(current_user)] }
end
# 2
def liked_by?(user)
likes.any? { |like| like.user_id == user.id } # usage: Post.limit(50).includes(:likes).map { |post| [p.id, post.liked_by?(current_user)] }
end
# 3
def liked_by?(user)
user.likes.exists?(post: self) # usage: Post.limit(50).includes(:likes).map { |post| [p.id, post.liked_by?(current_user)] }
end
# 4
def liked_by?(user)
user.likes.any? { |like| like.user_id == user.id } # usage: Post.limit(50).map { |post| [p.id, post.liked_by?(current_user)] }
end
end
I can imagine the advantages and disadvantages of each of them but would be happy to hear any thoughts from you guys.
Usually, we render 50 posts at a time and the current user has about a hundred likes, but some users have thousands of likes so it's hard to say that there is a general solution for this. Anyway, I'm open to hear your proposals. Probably there's way # 5...(like denormalizing tables, materialized views and etc).

None of the above as they will all create a N+1 query issue. Also interrogation methods (methods that end with ?) should by convention return a boolean in Ruby.
You can use EXISTS with a subquery:
SELECT "posts".* FROM "posts"
WHERE EXISTS (SELECT "likes".* FROM "likes" WHERE "likes"."user_id" = $1 AND "likes"."post_id" = "posts"."id")
class User < ApplicationRecord
has_many :likes
end
class Post < ApplicationRecord
has_many :likes
# returns posts liked by user
def self.liked_by_user(user)
where(
user.likes
.where(Like.arel_table[:post_id].eq(arel_table[:id]))
.arel.exists
)
end
end
If you want all the posts regardless if the have been liked or not and a boolean indicating if they have been liked by the user can put that same EXISTS into the SELECT clause:
class Post < ApplicationRecord
# ...
# selects posts and a liked_by_user boolean
def self.with_likes_by_user(user)
columns = self.respond_to?(:arel) ? arel.projections : arel_table[Arel.star]
select(
columns,
user.likes
.where(Like.arel_table[:post_id].eq(arel_table[:id]))
.arel.exists.as('liked_by_user')
)
end
end
SELECT
"posts".*,
EXISTS (
SELECT "likes".*
FROM "likes" WHERE "likes"."user_id" = $1
AND "likes"."post_id" = "posts"."id"
) AS liked_by_user
FROM "posts"

Related

Get count of one-to-many records efficiently

I have two tables: App (with a release_id field) and User.
Models
class App < ActiveRecord::Base
has_one :user
end
class User < ActiveRecord::Base
belongs_to :app
end
Class
class Stats::AppOverview
def initialize(from:, apps:)
#from = from || Date.new(2010)
#apps = apps
end
def total_count
{ apps: { total: total_app, with_user: app_with_user } }
end
private
def app_since
#apps.where('created_at >= ?', #from)
end
def total_app
devices_since.count
end
def app_with_user
User.where(app: app_since).count
end
end
I would like to return
the number of app records for a given array of release_id
the number of app records that belong to each user, satisfying other criteria
This is how I use the class
Stats::AppOverview.new(from: 1.month.ago.iso8601,
apps: App.where(release_id: [1,2,3,4,5,19,235]).total_count
#=> { apps: { total: 65, with_user: 42 } }
For the moment I do it in two queries, but is it possible to put them in the same query? Is this possible using active record?

Rails - Serialize related data

I've got two models:
class Continent < ActiveRecord::Base
has_many :countries
end
class Country < ActiveRecord::Base
belongs_to :continent
end
I created controller like:
class ContinentsController < ApplicationController
def index
#continents = Continent.all
render json: #continents
end
end
and serializer:
class ContitnentSerializer < ActiveModel::Serializer
attributes :name, :countries
end
Here my issue begins. I'd like to serialize only countries with given condition where value comes from HTTP GET params. E.g country inside serializer should be displayed only if population is more than params[:population]. The problem is inside serializer we don't have access to params to examine that.
[
{
name: 'Europe'
countries: [
{
name: 'Italy',
population: 1000000
}
]
},
{
name: 'Africa'
countries: [
]
}
]
I've tried to join table with condition but it seems be not working.
#continents = Continent.all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > #{params[:population]}")
Create a scope and call the scope with param value from controller:
scope :population_more_than, ->(population) {all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > ?", population)}
Now call it from controller instead of Continent.all
Continent.population_more_than(params[:population])
You can try
#continents = Continent.all
#continents.num_population = params[:population]
render json: #continents.to_json(methods: :countries_with_population_gt)
in your Continent model
attr_accessor :num_population
def countries_with_population_gt(num_population=0)
countries.where('population > ?', #num_population)
end
Basically, you need to select only Continents that fall under specific rule. If this is a frequently used filter, then I would go with the Babar's suggestion and create a scope.
If this is a one time selection, then I prefer simply do filtering right there without cluttering up my models with non-frequently used scopes.
Continent.joins(:countries).where("countries.population > :population", population: params[:population])
# Or event shorter
Continent.joins(:countries).where("countries.population > :population", params)

Get associated ActiveRecord::Relation from complex query

I have the following AR models:
class Checkin < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :checkins
end
Let's say I have a complex query on checkins and users, for example, Checkin.nearby.today and User.friends_of(john). Is there a straightforward way I can derive an ActiveRecord::Relation of Users? The end result would be friends of John who have checked in nearby today.
I would like the end result to be an instance of ActiveRecord::Relation.
Thanks!
This should do it:
users = User.friends_of(john)
users = users.joins(:checkins).where(checkins: { checkin_date: Date.today })
users = users.where( # your logic to determine the nearby )
As you can see, there is the logic about the nearby scope missing.
In a custom method:
def self.friends_checked_nearby_at_date(friend, nearby = true, date = Date.today)
users = User.friends_of(friend)
users = users.joins(:checkins).where(checkins: { checkin_date: date })
users = users.where( # your logic for the nearby scope ) if nearby.present?
return users
end
# usage:
User.friends_checked_nearby_at_date( User.first )
# or
User.friends_checked_nearby_at_date( User.first, true, Date.today-1.week )
# or
User.friends_checked_nearby_at_date( User.first, false )
# etc.

Rails: How can I tell if a record has a child record in any one of its child tables?

I'm working in Rails 3 and have a table with multiple child tables, e.g.
class Foo < ActiveRecord::Base
has_many :things
has_many :items
has_many :widgets
end
class Thing < ActiveRecord::Base
belongs_to :foo
end
class Item < ActiveRecord::Base
belongs_to :foo
end
class Widget < ActiveRecord::Base
belongs_to :foo
end
Is there a simple way for me to check to if a given Foo has a child record in one or more of the tables? Basically, is there a better way to do this:
if !foo.things.empty? or !foo.items.empty? or !foo.widgets.empty?
puts "This foo is in use!"
emd
Well, I think you're on the right track, but maybe just put that as a method in your Foo model
class Foo < ActiveRecord::Base
def children?
things.any? || items.any? || widgets.any?
end
end
Then just say, Foo.first.children? and get true if the Foo instance has any children.
This is what any? is for.
class Foo < ActiveRecord::Base
def children?
things.any? || items.any? || widgets.any?
end
end
Since this has become a topic of contention, I present to you:
> foo = Foo.last
Foo Load (0.6ms) SELECT "foos"......
> foo.items
Item Load (0.9ms) SELECT "items".*.......
> foo.items.any?
=> true #OH, WHAT's that? NO SQL CALLS? GEE WILLICKERS
> foo.items.exists?
Item Exists (0.5ms) #Hmmmmmm....
=> true
The point here is that under any circumstances, exists makes a DB call, where as any? will not, if spaces is always loaded into memory. Now as I said, many times, the importance is not the efficiency of the DB call (AND YES, the SQL call exists? makes is more efficient), but the fact that any? won't necessarily make a call to the DB, which is a HUGE advantage. Look for yourself:
[20] pry(main)> Benchmark.measure { foo.item.exists? }
Item Exists (0.5ms) SELECT 1 AS one FROM "items" ...
=> #<Benchmark::Tms:0x007fc1f28a8638
#cstime=0.0,
#cutime=0.0,
#label="",
#real=0.002927,
#stime=0.0,
#total=0.00999999999999801,
#utime=0.00999999999999801>
[21] pry(main)> Benchmark.measure { foo.items.any? }
=> #<Benchmark::Tms:0x007fc1f29d1aa0
#cstime=0.0,
#cutime=0.0,
#label="",
#real=7.6e-05,
#stime=0.0,
#total=0.0,
#utime=0.0>
For a more concise timing, look at this:
> Benchmark.measure { 1000.times {foo.items.exists?} }.total
=> 2.5299999999999994
> Benchmark.measure { 1000.times {foo.items.any?} }.total
=> 0.0
Now as I said, many times, it depends on circumstance -- you could have many circumstances where these items aren't loaded into memory, but many times, they are. Choose which one works best for you depending on how you're calling it.
This should work for any given model.
class Foo < ActiveRecord::Base
def children?
has_associated_records = self.class.reflect_on_all_associations.map { |a| self.send(a.name).any? }
has_associated_records.include?(true)
end
end
You could subclass Thing Item and Widget. Or add a polymorphic join table to keep track of it. Not ideal, I know.
You could at least do this, so it would read a little better.
if foo.things.exists? || foo.items.exists? || foo.widgets.exists?
puts "This foo is in use!"
end
'empty?' uses 'exists?' behind the scenes, I believe.
Suppose all the associations are loaded into memory:
class Foo < ActiveRecord::Base
has_many :things
has_many :items
has_many :widgets
def in_use?
[things, items, widgets].flatten.any?
end
end
Edit
I just realized that this is wrong: each association (even if still loaded into memory) will be loaded which isn't good.
things.any? || items.any? || widgets.any?
is more correct and has been proposed before me.
The answer by #Marcelo De Polli is the most generalized one posted so far.
This answer is an updated version of it for Rails 5.
The parent class for a model is ApplicationRecord in Rails 5 and later, which used to be ActiveRecord::Base up to Rails 4 (n.b., the original question is tagged as Rails 3).
For simplicity of the code, use:
class Foo < ApplicationRecord
def children?
self.class.reflect_on_all_associations.map{ |a| self.send(a.name).any? }.any?
end
end
To pursue more run-time efficiency when a model may have many classes of children, use:
class Foo < ApplicationRecord
def children?
self.class.reflect_on_all_associations.each{ |a| return true if self.send(a.name).any? }
false
end
end

Ruby on Rails - Simplifying similar methods that access different variables

I'm working on a fairly simple site that allows users to choose recipe ingredients, their quantities and then shows them nutritional info based on their recipe and a large database.
Right now, I feel like I'm repeating myself a bit. I want to be able to make this "DRY" by having one method each in the Recipe and Recipe_Ingredient model that will do the same thing only accept the right parameter, which will be the type of nutrient.
Here is the relevant code in my view that currently calls two different methods (and will call more when extended to the other nutrients):
<ul>Calories <%= #recipe.total_calories %></ul>
<ul>Fat (grams) <%= #recipe.total_fat %></ul>
In my recipe model, I have methods that iterate over each of the ingredients in the recipe:
def total_calories
recipe_ingredients.to_a.sum { |i| i.total_calories }
end
def total_fat
recipe_ingredients.to_a.sum { |i| i.total_fat }
end
In the block, we call two separate methods that actually calculate the nutrients for each individual recipe ingredient:
def total_calories
ingredient.calories*ingredient.weight1*quantity/100
end
def total_fat
ingredient.fat*ingredient.weight1*quantity/100
end
This last piece is where we reference the database of ingredients. For context, here are the relationships:
class RecipeIngredient < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
class Recipe < ActiveRecord::Base
has_many :recipe_ingredients
Thanks in advance for any help.
Lev
The send method with a symbol parameter works well for that kind of DRY.
<ul>Calories <%= #recipe.total :calories %></ul>
<ul>Fat (grams) <%= #recipe.total :fat %></ul>
Recipe
def total(type)
recipe_ingredients.to_a.sum { |i| i.total type }
end
RecipeIngredient
def total(type)
ingredient.send(type) * ingredient.weight1 * quantity / 100
end
You could use meta programming to dynamically add the methods. Here is a start, you can get even more DRY than this.
class DynamicTotalMatch
attr_accessor :attribute
def initialize(method_sym)
if method_sym.to_s =~ /^total_of_(.*)$/
#attribute = $1.to_sym
end
end
def match?
#attribute != nil
end
end
Recipe
class Recipe
def self.method_missing(method_sym, *arguments, &block)
match = DynamicTotalMatch.new(method_sym)
if match.match?
define_dynamic_total(method_sym, match.attribute)
send(method_sym, arguments.first)
else
super
end
end
def self.respond_to?(method_sym, include_private = false)
if DynamicTotalMatch.new(method_sym).match?
true
else
super
end
end
protected
def self.define_dynamic_total(method, attribute)
class_eval <<-RUBY
def self.#{method}(#{attribute})
recipe_ingredients.to_a.sum { |i| i.send(attribute)
end
RUBY
end
end
RecipeIngredient
class RecipeIngredient
def self.method_missing(method_sym, *arguments, &block)
match = DynamicTotalMatch.new(method_sym)
if match.match?
define_dynamic_total(method_sym, match.attribute)
send(method_sym, arguments.first)
else
super
end
end
def self.respond_to?(method_sym, include_private = false)
if DynamicTotalMatch.new(method_sym).match?
true
else
super
end
end
protected
def self.define_dynamic_total(method, attribute)
class_eval <<-RUBY
def self.#{method}(#{attribute})
ingredient.send(attribute) * ingredient.weight1 * quantity / 100
end
RUBY
end
end
Example was copied from ActiveRecord and this page: http://technicalpickles.com/posts/using-method_missing-and-respond_to-to-create-dynamic-methods/