Rails 5.1, sum by currency with nested resources - ruby-on-rails

I have a problem with a SUM calculation. I have multiple currency in a table, I have users, I have groups and I have a bets table. The user can join a group via member (user_id and group_id). And the user can share his bet via groupbet (bet_id and member_id). I don't enter in detail here because all this works properly.
I would like to do some SUM calculation in the group view (show). This is where I'd like to display the total amount of money the members of the group spend for there bet. I also would like to use chartkick in the mix to show some nice graph.
This is what I have so far
My controller :
m = Member.where(group_id: #group.id)
gs = Groupbet.where(member_id: m)
#tasks = gs.joins(:bet).select("bets.*, sum(amount) as total")
#amount = gs.joins(:bet).
select(:symb, 'bets.*, sum(amount) AS amount').
where(bet_id: { id: #current_user.bets.all.map(&:id) }).
group(:symb)
#tasks works but SUM all the spendings from the group, that's cool, but I have several currencies so the amount is not correct. This is why I came up with #amount which in the view :
<% #amount.each do |amount| %>
<%= '%.02f' % "#{amount.amount}" %>
<%= "#{amount.symb}" %>
<% end %>
display nothing at all.
the :symb is one of my Currency column btw. In currency I have name and symb, where symbole is this : € or $ for example. In my spending table I have a column amount and the currency_id.
My relations :
class Currency < ApplicationRecord
has_many :bets, dependent: :destroy
has_many :users, through: :bets
end
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
has_many :grouplinks, through: :members
has_many :bets, through: :groupbets
has_many :groupbets, through: :members
has_many :notifications, dependent: :destroy
def to_param
auth_token
end
end
class Groupbet < ApplicationRecord
belongs_to :bet
belongs_to :member
end
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
has_many :grouplinks, dependent: :destroy
has_many :groupbets, dependent: :destroy
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
class Bet < ApplicationRecord
belongs_to :user
belongs_to :currency
has_many :groupbets, dependent: :destroy
end
class User < ApplicationRecord
has_secure_password
has_many :bets, dependent: :destroy
has_many :currencies, through: :bets
has_many :links, dependent: :destroy
has_many :notifications, dependent: :destroy
has_many :members, :dependent => :destroy
has_many :groups, :through => :members
has_one :owned_group, foreign_key: "owner_id", class_name: "Group"
end
Links has nothing to do here but yeah...
I'm also using groupdate and for chartkick I don't really know because when a user creates a bet he can actually go on his profile and check all the bets he did, as well in a chart. To display this chart, which is working, I'm doing like so :
My helper
def expenses_bet_amount_per_month
#current_user.bets.all.joins(:currency).group('currencies.symb').group_by_month(:date, format: "%B %Y").sum(:amount)
end
date is the date when the user create the bet. Don't ask why I didn't use created_at, doesn't matter :)
And my view :
<%= column_chart expenses_bet_amount_per_month %>
That works. For one single user. But for the group I don't know how I could make that happen.
If anyone can save the situation ? Thank you very much
EDIT :
The code for the single user :
def sums_by_currency_total
Currency.joins(:bets).
select(:symb, 'SUM(bets.amount) AS amount').
where(bets: { id: user_bets_total.map(&:id) }).
group(:symb)
end
def user_spendings_total
user.bets.all
end
That works for one user. Display the SUM from all bets per currency. But for a group ? I don't know

Ok people I managed !!!!
I share the code in case anybody is interested :
def sums_by_currency_group
m = Member.where(group_id: #group.id)
gs = Groupbet.where(member_id: m)
Currency.joins(:bets)
.select(:symb, 'SUM(bets.amount) AS amount')
.where(bets: { id: gs.all.map(&:bet_id) })
.group(:symb)
end
In the view :
<% sums_by_currency_group.each do |sum| %>
<%= '%.02f' % "#{sum.amount}" %> <%= "#{sum.symb}" %>
<% end %>
And there we go. All the SUM from the bets that are shared in each group.
I also solved the problem for chart kick :
def bets_amount_per_month_group
m = Member.where(group_id: #group.id)
gs = Groupbet.where(member_id: m)
Currency.joins(:bets)
.where(bets: { id: gs.all.map(&:bet_id) })
.group('currencies.symb')
.group_by_month(:date, format: "%B %Y")
.sum(:amount)
end
In the view :
<%= column_chart bets_amount_per_month_group %>
Et voila ! Nice chart of all the bets shared in the group. I used the same method as the calculation and it worked !! Olé !!

Related

Rails 5.1, search scope within a group

I have a little issue with a search scope.
Users can join groups by providing a token. User can also create spendings and they can share them in a group. We are in the show view of a group. This is where I loop through spendings that each user has in this group. It looks something like that :
<% group_spendings_paginate_search.each do |groupspending| %>
<%= groupspending.member.user.firstname %>
<%= groupspending.spending.title %>
<%= groupspending.spending.description %>
<%= '%.02f' % groupspending.spending.amount %>
<%= groupspending.spending.currency.symb %>
<%= groupspending.created_at.strftime("%d/%m/%Y") %>
<% end %>
The group_spendings_paginate_search comes from a helper which represent this :
def group_spendings_paginate_search
#search.scope.order('created_at DESC').paginate(:page => params[:page], :per_page => 10)
end
The search form looks like this and is just above the loop :
<%= form_tag group_path(#group), method: :get do %>
<%= date_field_tag "search[date_from]", #search.date_from %>
<%= date_field_tag "search[date_to]", #search.date_to %>
<%= select_tag "search[user_id]", options_from_collection_for_select(#group.users.all, :id, :firstname, params[:user_id]), include_blank: "All Users" %>
<%= select_tag "search[currency_id]", options_from_collection_for_select(Currency.all, :id, :name, params[:currency_id]), include_blank: "All Currencies" %>
<%= submit_tag "Search", name: nil %>
<% end %>
Note that here I had to go for Currency.all which is not the currencies the user are using. They may only use euro for example and here the all list is going to pop, which is also not very cool. Would prefer to show only the currencies the users are using according to the group and spending. But hey it's already complicated enough so for now I'll keep it simple and come back on that later. But if anybody has a idea, feel free.
My controller :
def show
#search = GroupspendingSearch.new(params[:search])
end
And finally my GroupspendingSearch.rb
class GroupspendingSearch
attr_reader :date_from, :date_to, :user_id, :currency_id
def initialize(params)
params ||= {}
#date_from = parsed_date(params[:date_from], 1.month.ago.to_date)
#date_to = parsed_date(params[:date_to], Date.tomorrow)
#user_id = params[:user_id]
#currency_id = params[:currency_id]
end
def scope
launch = Groupspending.where('groupspendings.created_at BETWEEN ? AND ?', #date_from, #date_to)
launch = launch.where(member_id: find_member) if find_member.exists?
launch = launch.where(spending_id: find_currency) if find_currency.exists?
launch
end
private
def parsed_date(date_string, default)
Date.parse(date_string)
rescue ArgumentError, TypeError
default
end
def find_member
Member.where(user_id: #user_id)
end
def find_currency
Spending.where(currency_id: #currency_id)
end
end
The scope as is is actually working. Only problem is that it goes for all the Groupspending and not #group.groupspendings. It means that if I go in any group I will see all the spendings and I want to avoid that of course. Also I had to be smart about how I find the user_id and the currency_id. Using the table Groupspending gives me only member_id and spending_id. With the group_id or token in this model everything would be way easier...
Basically I don't know how to specify in the scope to look for the #group if this makes any sense.
I thought about a couple of things. The first is in the controller and specify a other param like so #search = GroupspendingSearch.new(params[:search],#group) and add it in the model def initialize(params, group) but to be honnest I don't really know what I'm doing so yea...
I'm dry here, anybody to help ? Maybe I'm totally wrong here and there is a other approach.
Models relations (don't pay attention to link and notification, it's a other story ^^) :
class Currency < ApplicationRecord
has_many :spendings, dependent: :destroy
has_many :users, through: :spendings
end
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
has_many :grouplinks, through: :members
has_many :spendings, through: :groupspendings
has_many :groupspendings, through: :members
has_many :notifications, dependent: :destroy
def to_param
auth_token
end
end
class Groupspending < ApplicationRecord
belongs_to :spending
belongs_to :member
end
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
has_many :grouplinks, dependent: :destroy
has_many :groupspendings, dependent: :destroy
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
class Spending < ApplicationRecord
belongs_to :user
belongs_to :currency
has_many :groupspendings, dependent: :destroy
end
class User < ApplicationRecord
has_secure_password
has_many :spendings, dependent: :destroy
has_many :currencies, through: :spendings
has_many :links, dependent: :destroy
has_many :notifications, dependent: :destroy
has_many :members, :dependent => :destroy
has_many :groups, :through => :members
has_one :owned_group, foreign_key: "owner_id", class_name: "Group"
end
EDIT :
Well I just realized that actually the scope isn't correct... I look for member_id: find_member and this goes for all the member with a member_id: X. Which means that if a user is a member of many groups well... it shows multiple entries. I'm getting crazy ^^
Ok I think I got it but I want to be sure that there is no mistake or security issues...
Here is what I changed :
Helper
def group_spendings_paginate_search
m = Member.where(group_id: #group.id)
gs = Groupspending.where(member_id: m)
#search.scope.where(member_id: gs.all.map(&:member_id) ).order('created_at DESC').paginate(:page => params[:page], :per_page => 10)
end
I didn't change the view and didn't change the GroupspendingSearch either. With this new helper I retrieve only the members within the group which "blocks" the search query to that. I guess...
I don't know if it's the best, and I don't know if it's safe. In the URL if I try to change the user_id to someone else, if he/she is not part of the group it doesn't show up. So it looks ok. Of course I have to secure the show view by restricting only to the members of the group. But appart from that, would that be ok ?

How to show number of total records for an associated model - Rails 4

I need to be able to display the count for an associated model with specific conditions. I have a Mall model that has_many Shops, a Shop model which belongs_to Mall and has_many Sales, and a Sale model that belongs_to Shop.
Basically what I want to do is to first Iterate through each mall to display the :name of each mall, and then I would like to show how many total Sale records exist for shops belonging to each mall. However, I only want to show the number of Sale records with the following criteria: Sale.where('offer_end >= ?', Date.today).
Im not really sure how to achieve this due to Sales not having a direct connection to Malls. The following code is my best attempt to achieve this but its not working.
<% #malls.each do |mall| %>
<%= mall.name %> - <%= mall.shops.sales.where('offer_end >= ?', Date.today).count %>
<% end %>
Mall Model
class Mall < ActiveRecord::Base
has_many :mall_shops
has_many :shops, :through => :mall_shops
validates :name, presence: true, uniqueness: true
end
Shop Model
class Shop < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
has_many :mall_shops
has_many :malls, :through => :mall_shops
has_many :sales, dependent: :destroy
end
Sale Model
class Sale < ActiveRecord::Base
belongs_to :shop
end
First, you can nest has_many :through associations:
In Mall.rb:
has_many :sales, through: :shops
You can also create a scope for active sales:
In Sale.rb:
scope :active, -> do
where('offer_end >= ?', Date.today) # Pay attention to time zone here
end
Now, you can get active sales for a mall:
#mall = Mall.find(...)
#mall.sales.active.count

Rails Checking if One Array is in Another

I have five models: Course, Lesson, Question, Answer and User.
What I'm trying to do is determine if the User has Answers for all of the Questions in a Lesson (so I can put "Done" next to the lesson in the view if this is the case).
My models:
class Course < ActiveRecord::Base
has_many :lessons, dependent: :destroy
has_many :questions, :through => :lessons
has_many :users, through: :purchases
end
class Lesson < ActiveRecord::Base
belongs_to :course
has_many :questions, dependent: :destroy
has_many :answers, through: :questions
end
class Question < ActiveRecord::Base
belongs_to :lesson
belongs_to :course
has_many :answers, dependent: :destroy
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :user
end
class User < ActiveRecord::Base
has_many :answers
has_one :author
has_many :courses, through: :purchases
end
What I tried to do was to check if a Lesson's Questions were in the Questions the User Answered, but the includes? line doesn't seem to be working the way I want.
in my controller, I have:
#lessons = #course.lessons
#answers = current_user.answers
#questions = Question.where(:id => #answers.map(&:question_id))
in my view, I have:
<% #lessons.each do |lesson| %>
<% lesson_questions = lesson.questions %>
<%= user_questions = #questions.where("lesson_id = ?", lesson.id)%>
<% if user_questions.include?(lesson_questions)%>
Done!
<% end %>
<% end %>
I'm not sure if this is the cause, but I noticed the lesson_questions are #<Question::ActiveRecord_Associations_CollectionProxy:0x9c49698>
While the user_questions are: #<Question::ActiveRecord_Relation:0x9c48330>
I'm wondering, (a) how I accomplish my objective of finding the Lessons with all of the Questions answered, and (b) if there's a more efficient way to do this. Thanks!
Problem
You can't check if an array includes another array just like this:
user_questions.include?(lesson_questions)
You need to check if each element from lesson_questions is included in the user_questions.
Try these instead:
Solution: 1
lesson_questions.all? { |lq| user_questions.include?(lq) }
This should return true if all the lesson_questions are included in the user_questions.
Solution: 2
(lesson_questions - user_questions).empty?

How do I eager load two levels of associations in 1 call?

I have FamilyTree, Node, Comment, & User models.
The relationship is like this:
FamilyTree
class FamilyTree < ActiveRecord::Base
belongs_to :user
has_many :memberships, dependent: :destroy
has_many :members, through: :memberships, source: :user, dependent: :destroy
has_many :nodes, dependent: :destroy
end
Node
class Node < ActiveRecord::Base
belongs_to :family_tree
belongs_to :user
has_many :comments, dependent: :destroy
end
Comment
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :node
end
User
class User < ActiveRecord::Base
has_one :family_tree, dependent: :destroy
has_many :memberships, dependent: :destroy
has_many :nodes, dependent: :destroy
has_many :comments
end
Membership ## This is just to store the user memberships on various family_trees
class Membership < ActiveRecord::Base
belongs_to :family_tree
belongs_to :user
end
In my Dashboard#IndexController where I am using this, I have:
def index
#family_tree = current_user.family_tree
#nodes = #family_tree.nodes.includes(:comments)
#node = current_user.nodes.new
#memberships = current_user.memberships.limit(3)
end
When I am trying to optimize my app with the Bullet gem, I get this message:
N+1 Query detected
Comment => [:user]
Add to your finder: :include => [:user]
N+1 Query method call stack
My _comments partial that is generating this N+1 issue is called like this - in my views/dashboard/index.html.erb:
<% #nodes.each do |node| %>
<%= render partial: "shared/comments", locals: {node: node} %>
<% end %> <!-- node -->
This is where the n+1 offending queries occur, around these lines in my _comments partial.
<% node.comments.each do |comment| %>
<li class="clearfix">
<a class="avatar" href="#">
<%= image_tag(comment.user.avatar.url)%>
So it seems the solution is to optimize my controller call, but I am not quite sure how to do 2-levels of association. I tried this:
#nodes = #family_tree.nodes.includes(:comments).includes(:user)
But that doesn't seem to get rid of the N+1 query problem.
Any ideas?
You have to pass a hash to the includes
#nodes = #family_tree.nodes.includes(:comments => :user)

Listing all instances of a document model that belongs to user through group relationship

I basically followed the ROR guide, http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association, to create the relationship models as shown below.
Because of the through association, I figured that #user.trips would give you both the trips that the user created and the trips that belong to the user. However, when I do #user.trips.count in console, the result was only the number of trips that the users created; the trips that belonged to the user through the 'group' association was not counted.
Question: How do I get my view to display both the trips that the user created and the trips that the user belongs to through 'group'?
user/show.html.erb
<% unless #user.all_trips.empty? %>
<% #user.all_trips.each do |trip| %>
<!-- Content -->
<% end %>
<% end %>
user.rb
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:source => :trip
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
self.trips | self.group_trips
end
end
trip.rb
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :traveldeal
has_many :groups
has_many :users, :through => :groups
end
group.rb
class Group < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
Thanks!
Edit: Modified code per TSherif's partial solution.
Edit 2: Fixed up the all_trips method. Everything appears to work for me at this point.
Oh! I think I get what you're trying to do and why it's a problem. I was wondering why has_many :trips was called twice. But from what I understand, you have two different User-Trip relationships. These two can't have the same name, otherwise one will hide the other. Try something like this:
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:class_name => "Trip"
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
Trip.joins(:groups).where({:user_id => self.id} | {:groups => {:user_id => self.id}})
end
end
Or if you're using an older version of Rails that doesn't have MetaWhere:
def all_trips
Trip.joins(:groups).where("(trips.user_id = ?) OR (groups.user_id = ?)", self.id, self.id)
end

Resources