I've got an App in which User can buy shares in a Portfolio through a wallet.cash_transaction. Now I would like to display all shareholders of the portfolio specifying the user's name and the number of shares in a given portfolio.
#models association
class User < ApplicationRecord
has_one :wallet, as: :walletable
has_many :cash_transactions, through: :wallet
end
class Portfolio < ApplicationRecord
has_one :wallet, as: :walletable
end
class Wallet < ApplicationRecord
belongs_to :walletable, polymorphic: true
has_many :cash_transactions
end
class CashTransaction < ApplicationRecord
belongs_to :wallet
belongs_to :to_wallet, class_name: 'Wallet', optional: true
end
To display shareholders I'll need below join:
> portfolio = Portfolio.last
#shareholders = User.joins(:cash_transactions).where(cash_transactions: { to_wallet: portfolio.wallet }).uniq
Which I can use in the view by below iteration:
<tbody>
<% #shareholders.each do |user| %>
<tr>
<td><%= "#{user.first_name} #{user.last_name}" %></td>
<td><%= user.cash_transactions.where(to_wallet: portfolio.wallet).sum(:shares_number) %></td>
</tr>
<% end %>
</tbody>
Line user.cash_transactions.where(to_wallet: portfolio.wallet).sum(:shares_number) is responsible for the summation of all user shares in the portfolio. This method doesn't seem very efficient to me because it seems like I'll be sum-up unnecessarily at each iteration. I imagine that when there will be hundreds of thousands of users it can be aggravating at every refresh of the page. Is there a better way to do so?
You can write SQL queries to get sum of all matched records
#shareholders = User.joins(:cash_transactions)
.where(cash_transactions: { to_wallet: portfolio.wallet})
.select("users.*, SUM(cash_transactions.shares_number) as total_shares_number")
.group("users.id")
you will get all columns of users table and sum of total shares_number, Add more fields form another table as per your requirement
Related
I have a landlord model and in the table there is a field for listing_agent_id. There is also an agent model where all of their info is stored. In the index view I am trying to us <%= landlord.listing_agent.name but keep getting an error. I have defined agents in my landlords_controller, but it still doesn't seem to be working. Any help would be appreciated.
Landlord Index:
<tbody>
<% #landlords.each do |landlord| %>
<tr>
<td><%= landlord.listing_agent.name %></td>
</tr>
<% end %>
</tbody>
Landlords Controller:
def index
#landlords = Landlord.all
end
def new
#landlord = Landlord.new
#agents = Agent.employees.order(first_name: :asc)
end
Landlord Model:
class Landlord < ActiveRecord::Base
has_many :landlord_addresses
end
Error:
ActiveRecord does not "automagically" create an association just because you have a *_id column. There are just two many possibilities for that to be remotely useful.
To setup an association between Landlord and Agent you would do:
class Landlord < ActiveRecord::Base
belongs_to :listing_agent, class_name: 'Agent'
inverse_of: :landlord
# use inverse_of: :landlords if the relation is one to many.
end
class Agent < ActiveRecord::Base
has_one :landlord, inverse_of: :listing_agent
# or
has_many :landlords, inverse_of: :listing_agent
end
The class_name: 'Agent' option is needed because ActiveRecord cannot deduce the class from the name of the association. inverse_of helps avoid inconsistencies by keeping a single object in memory.
I am looking for a way to show a count of how many images there are for a category but obtained through a has_many association. I have been reading a little on counter_cache but as yet no joy on an implementation
class Category < ActiveRecord::Base
has_many :image_categories
has_many :images, through: :image_categories
end
class ImageCategory < ActiveRecord::Base
# Holds image_id and category_id to allow multiple categories to be saved per image, as opposed to storing an array of objects in one DB column
belongs_to :image
belongs_to :category
end
class Image < ActiveRecord::Base
# Categories
has_many :image_categories, dependent: :destroy
has_many :categories, through: :image_categories
end
Controller
#categories = Category.all
View
<% #categories.each do |c| %>
<li>
<%= link_to '#', data: { :filter => '.' + c.name.delete(' ') } do %>
<%= c.name %> (<%= #count here %>)
<% end %>
</li>
<% end %>
A couple important things to consider with counter_cache:
Certain Rails methods can update the database while bypassing callbacks (for instance update_column, update_all, increment, decrement, delete_all, etc.) and can cause inconsistent values for a counter cache. Same applies to any database changes outside of Rails.
Creating/deleting a child model always requires updating the parent. To ensure consistency of the counter cache Rails uses an additional DB transaction during this update. This usually isn't a problem but can cause database deadlocks if your child model is created/deleted frequently, or if the parent model is updated frequently. (http://building.wanelo.com/2014/06/20/counter-cache-a-story-of-counting.html)
These problems will be exacerbated since you're using a counter cache across a join table.
If you want to do an efficient dynamic count, that's always up to date, then you can use a custom select with a grouped join:
#categories = Category.select("categories.*, COUNT(DISTINCT images.id) AS images_count").joins(:images).group("categories.id")
<% #categories.find_each do |c| %>
<li>
<%= link_to '#', data: { :filter => '.' + c.name.delete(' ') } do %>
<%= c.name %> (<%= c.images_count # <- dynamic count column %>)
<% end %>
</li>
<% end %>
The cost of this grouped join should be very small provided your foreign keys are indexed, and I'd strongly consider taking this approach if you need images_count to always be consistent with the true value, or if images are frequently being created or destroyed. This approach may also be easier to maintain in the long run.
Since you are looking for an efficient way, i would suggest using counter_cache
Here is how your models should look like:
class Category < ActiveRecord::Base
has_many :image_categories
has_many :images, through: :image_categories
end
class ImageCategory < ActiveRecord::Base
# Holds image_id and category_id to allow multiple categories to be saved per image, as opposed to storing an array of objects in one DB column
belongs_to :image, counter_cache: :category_count
belongs_to :category, counter_cache: :image_count
end
class Image < ActiveRecord::Base
# Categories
has_many :image_categories, dependent: :destroy
has_many :categories, through: :image_categories
end
You'll need to add image_count field to your categories table and category_count in images table.
Once you are done adding the counters and fields, you'd need to reset the counters so that the fields are updated with the correct count values for the records already present in your db.
Category.find_each { |category| Category.reset_counters(category.id, :images) }
Image.find_each { |image| Image.reset_counters(image.id, :categories) }
I have a these models:
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
end
class Movie < ActiveRecord::Base
has_many :tickets
has_many :childrens, through: :tickets
belongs_to :cinema
end
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
What I need now is in the page of "Cinemas" I wanna print the sum (count, size?) of the childrens just for the movies of that cinemas, so I wrote this:
in the cinemas_controller.rb:
#childrens = #cinema.childrens.uniq
in the cinemas/show.html.erb:
<% #childrens.each do |children| %><%= children.movies.size %><% end %>
but obviously I have bullet gem that alert me for Counter_cache and I don't know where to put this counter_cache because of different id for the movie.
And also without the counter_cache what I have is not what I want because I want a count for how many childrens in that cinema taking them from the tickets from many days in that cinema.
How to?
UPDATE
If in my view I use this code:
<% #childrens.each do |children| %>
<%= children.movies.where(cinema_id: #cinema.id).size %>
<% end %>
gem bullet don't say me anything and every works correctly.
But I have a question: this way of querying the database is more heavy because of the code in the views?
This might help you.
#childrens_count = #cinema.childrens.joins(:movies).group("movies.children_id").count.to_a
You can use includes to load all associations ahead of time. For example:
#childrens = #cinema.childrens.includes(:movies).uniq
This will load all of the children's movies in the controller, preventing the view from needing access to the database in your loop.
You might agree, that the number of movies belongs to a child equals the number of tickets they bought.
That's why you could just cache the number of tickets and show it on the cinemas#show.
You can even create a method to make it more clear.
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
def movies_count
tickets.size
end
end
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children, counter_cache: true
end
class Movie < ActiveRecord::Base
belongs_to :cinema
has_many :tickets
has_many :childrens, through: :tickets
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
And then:
<% #childrens.each do |children| %><%= children.tickets.size %><% end %>
Or
<% #childrens.each do |children| %><%= children.movies_count %><% end %>
But if you want to show the number of tickets for every movie, you definitely need to consider the following:
#movies = #cinema.movies
Then:
<% #movies.each do |movie| %><%= movie.tickets.size %><% end %>
Since you have belongs_to :movie, counter_cache: true, tickets.size won't make a count query.
And don't forget to add tickets_count column. More about counter_cache...
P.S. Just a note, according to conventions we name a model as Child and an association as Children.
Actually is much more simpler than the remaining solutions
You can use lazy loading:
In your controller:
def index
# or you just add your where conditions here
#childrens = Children.includes(:movies).all
end
In your view index.hml.erb:
<% #childrens.each do |children| %>
<%= children.movies.size %>
<% end %>
The code above won't make any extra query if you use size but if you use count you will face the select count(*) n + 1 queries
I wrote a little ActiveRecord plugin some time ago but haven't had the chance to publish a gem, so I just created a gist:
https://gist.github.com/apauly/38f3e88d8f35b6bcf323
Example:
# The following code will run only two queries - no matter how many childrens there are:
# 1. Fetch the childrens
# 2. Single query to fetch all movie counts
#cinema.childrens.preload_counts(:movies).each do |cinema|
puts cinema.movies.count
end
To explain a bit more:
There already are similar solutions out there (e.g. https://github.com/smathieu/preload_counts) but I didn't like their interface/DSL. I was looking for something (syntactically) similar to active records preload (http://apidock.com/rails/ActiveRecord/QueryMethods/preload) method, that's why I created my own solution.
To avoid 'normal' N+1 query issues, I always use preload instead of joins because it runs a single, seperate query and doesn't modify my original query which would possibly break if the query itself is already quite complex.
In You case You could use something like this:
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children
end
class Movie < ActiveRecord::Base
has_many :tickets
has_many :childrens, through: :tickets
belongs_to :cinema
end
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
#cinema = Cinema.find(params[:id])
#childrens = Children.eager_load(:tickets, :movies).where(movies: {cinema_id: #cinema.id}, tickets: {cinema_id: #cinema.id})
<% #childrens.each do |children| %>
<%= children.movies.count %>
<% end %>
Your approach using counter_cache is in right direction.
But to take full advantage of it, let's use children.movies as example, you need to add tickets_count column to children table firstly.
execute rails g migration addTicketsCountToChildren tickets_count:integer,
then rake db:migrate
now every ticket creating will increase tickets_count in its owner(children) by 1 automatically.
then you can use
<% #childrens.each do |children| %>
<%= children.movies.size %>
<% end %>
without getting any warning.
if you want to get children count by movie, you need to add childrens_count to movie table:
rails g migration addChildrensCountToMovies childrens_count:integer
then rake db:migrate
ref:
http://yerb.net/blog/2014/03/13/three-easy-steps-to-using-counter-caches-in-rails/
please feel free to ask if there is any concern.
Based on sarav answer if you have a lot of things(requests) to count you can do:
in controller:
#childrens_count = #cinema.childrens.joins(:movies).group("childrens.id").count.to_h
in view:
<% #childrens.each do |children| %>
<%= #childrens_count[children.id] %>
<% end %>
This will prevent a lot of sql requests if you train to count associated records
i have 2 models one is listing and user
user has_many listings
listing belongs_to user
i have a view setup , i want to display for each user their own listings count ,i try this code :
<% User.all.each do |user| %>
<%= user.listings.count %>
<% end %>
i want to grab the listing count for each user . i found a bunch of solution here , all return the loop .other solutions i tried is to create a class method .
def count_listings
Listing.where(:user_id => user.id).count
end
try to call this way <%= User.count_listings%> it doesn't work .
for some reason there something i'm missing ,can't quite figure it out .
The :counter_cache option can be used to make finding the number of belonging objects more efficient. Consider these models:
class Order < ActiveRecord::Base
belongs_to :customer
end
class Customer < ActiveRecord::Base
has_many :orders
end
With these declarations, asking for the value of #customer.orders.size requires making a call to the database to perform a COUNT(*) query. To avoid this call, you can add a counter cache to the belonging model:
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: true
end
class Customer < ActiveRecord::Base
has_many :orders
end
With this declaration, Rails will keep the cache value up to date, and then return that value in response to the size method.
Although the :counter_cache option is specified on the model that includes the belongs_to declaration, the actual column must be added to the associated model. In the case above, you would need to add a column named orders_count to the Customer model. You can override the default column name if you need to:
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: :count_of_orders
end
class Customer < ActiveRecord::Base
has_many :orders
end
Counter cache columns are added to the containing model's list of read-only attributes through attr_readonly.
source: Rails guide on associations
..scroll down to options of belongs_to
If all you need is what you show in the example you can do it better as follows
<% Listing.group(:user_id).count.each do |user, count| %>
<%= "user: #{user} has #{count} listings" %>
<% end %>
This does a single query to the database and fetches only what you need.
SELECT COUNT(*) AS count_all, user_id AS user_id FROM `listings` GROUP BY user_id
and returns a hash like:
{
1: 123,
2: 231
}
#{ user_id: count }
I need to display multidimensional array data in a table, like so:
for an sla.expected_billing, there is a grid with 12 periods down the left side (rows)
7 ledger numbers across the top (columns)
amounts in applicable fields, but there may be null values
For instance,
Period 1: Ledger 1 = 400.00, Ledger 2 = null, Ledger 3 = 250.55, Ledger 4 = 500, etc..
Period 2: Ledger 1 = null, Ledger 2 = null, etc...
class ExpectedBilling < ActiveRecord::Base
belongs_to :sla
belongs_to :period
belongs_to :ledger
end
class Period < ActiveRecord::Base
has_many :expected_billings
end
class Ledger < ActiveRecord::Base
has_many :expected_billings
end
class Sla < ActiveRecord::Base
has_many :expected_billings
end
I was thinking the Matrix library and/or Class.transpose might be what I need, but I'm not sure. When there may be null values in any given array, I'm afraid it won't populate the grid correctly, either.
Can anyone point me in the right direction?
I would model it slightly differently:
class Sla < ActiveRecord::Base
has_one :expected_billing
end
class ExpectedBilling < ActiveRecord::Base
has_many :periods
belongs_to :sla
end
class Period < ActiveRecord::Base
has_many :ledgers
belongs_to :expected_billing
end
class Ledger < ActiveRecord::Base
belongs_to :period
end
To print your table in a view:
<table>
<!-- insert header row here -->
<% sla.expected_billing.periods.each do |period| %>
<tr>
<!-- insert header column here -->
<% period.ledgers.each do |ledger| %>
<td><%= ledger.value %></td>
<% end %>
</tr>
<% end %>
</table>