Reputation: 431
I would like to know the best practice when I need a computed attribute that require a call to the database.
If I have a Parent
that has many Child
, how would I render a children_count
attribute in ParentController#index
as I don't want to render the children, just the count? what's the best way to do it?
Thank you!
Model:
class Parent < ApplicationRecord
has_many :children
def children_count
children.count # Wouldn't it ask the database when I call this method?
end
end
Controller:
class ParentsController < ApplicationController
def index
parents = Parent.all
render json: parents, only: %i[attr1, attr2] # How do I pass children_count?
end
end
Upvotes: 3
Views: 177
Reputation: 106922
The Rails way to avoid additional database queries in a case like this would be to implement a counter cache.
To do so change
belongs_to :parent
in child.rb
to
belongs_to :parent, counter_cache: true
And add an integer column named children_count
to your parents
database table. When there are already records in your database then you should run something like
Parent.ids.each { |id| Parent.reset_counters(id) }
to fill the children_count
with the correct number of existing records (for example in the migration in which you add the new column).
Once these preparations are done, Rails will take care of incrementing and decrementing the count automatically when you add or remove children.
Because the children_count
database column is handled like all other attributes you must remove your custom children_count
method from your Parent
class and can still simple call
<%= parent.children_count %>
in your views. Or you can add it to the list of attributes you want to return as JSON:
render json: parents, only: %i[attr1 attr2 children_count]
Upvotes: 5
Reputation: 11109
children.count
will call the database, yes; however, it will do it as a SQL count:
SELECT COUNT(*) FROM "children" WHERE "children"."parent_id" = $1
It doesn't actually load all of the child records. A more efficient method is to use a Rails counter_cache for this specific case: https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache
Upvotes: 4