Sum by model method and inheritance - ruby-on-rails

I have 3 models, User, Applicant and ApplicantCommission.
user has_many applicants
applicant has many applicant_commissions
I would like to return a sum of the applicant_commission instance method to the parenting models. So #user.getTotalCommission will return the sum of all applicant_commissions over every applicant. #applicant.getTotalCommission would return every commission belonging to that applicant, and #applicant_commission.getTotalCommission would return the total value for just one commission type.
In ApplicantCommission.rb I have a instance method:
# Returns the full amount of commission that the post has earned from this commission group.
def getTotalCommission
#Does some calculations
return number_with_precision(total.round(2), :precision => 2)
end
Applicant.rb
def getTotalCommission
self.applicant_commissions.to_a.sum(&:getTotalCommission)
end
User.rb
def getTotalCommission
self.applicants.to_a.sum(&:getTotalCommission)
end
Currently, if I have 2 applicant commissions, one 12.20 and another 10.00, I get 12.2010.00. The desired output would be 22.20.
It's supposed to be based around simple inheritance.. so perhaps I'm going down the wrong route completely?
Thanks

I think the problem is your use of number_with_precision which is a helper method to use in views which formats your number so that it can be displayed. It returns a string. Rails also provides the sum method which adds all the things in the array together.
Essentially you're getting an array ["12.20", "10.00"] and then it is getting "summed" by doing "12.20" + "10.00"
I would try and keep your total commission as a number as long as possible and only format it using number_with_precision when you are displaying it.
If your getTotalCommission method in ApplicantComission is just:
def getTotalCommission
total.round(2)
end
then your summing code will work as expecting.
P.S. I'd question whether you even need to round it at that point too - you may only need to round it when you output the value
P.P.S. You're not actually doing inheritance which is when different classes inherit from each other. You're methods both have the same interface because they both have a getTotalCommission method but it is not through inheritance.

There is an issue in the getTotalCommission method, just try this:
def getTotalCommission
# becase number_with_precision returns string.
number_with_precision(total.round(2), :precision => 2).to_f
end
I hope it will help.
You might need to modify User#getTotalCommission
def getTotalCommission
number_with_precision(applicants.to_a.sum(&:getTotalCommission), precision: 2).to_f
end

Related

How can I filter ActiveAdmin index listings by a join table?

Let's say I have a table posts, and another table reviews that has a post_id and a rating (integer).
How can I add a filter to app/admin/post.rb which returns posts with a certain total score? (e.g. SUM(reviews.rating) GROUP BY (posts.id)). I want the filter to show up on the right side of the index, along with the other filters, and ideally to function as a range input.
To be clear, when I say "filter", I mean the ActiveAdmin filter method which adds filters to the right sidebar on the index page.
I created a scope in Post which returns posts with scores, but I haven't been able to find a way to use that in an ActiveAdmin filter.
Note: I rewrote my example because my original one didn't capture the complexity of the question.
It's common to override scoped_collection to join associated records to increase performance:
ActiveAdmin.register Post do
controller do
def scoped_collection
super.includes :author, :publisher
end
end
end
Since the entire collection now has author and publisher included, you can have a scope that queries those:
scope :random_house do |scope|
scope.where publishers: {name: 'Random House'}
end
I haven't come up with a proper solution to this question, but I have found a workaround.
I can change the scoped_collection based on a query param, and simply pass the param in when I want to use it. For example, if I have a scope with_rating(x), which returns posts with a score of at least x, I can write:
controller do
def scoped_collection
if params[:with_rating]
super.with_rating(params[:with_rating])
else
super
end
end
end
Then I can go to /admin/posts?with_rating=100 and get back posts with a rating of at least 100.
Thanks to #seanlinsley for making me aware of the scoped_collection method that I used in this solution.
Use counter cache column to store the comments count
http://railscasts.com/episodes/23-counter-cache-column
Then the column will get updated each time a comment is created to that post.This would also help in increasing the performance of search.

How to tell what class is calling a method in Ruby on Rails?

In my Rails app I have this:
class Invoice < ActiveRecord::Base
has_many :payments
before_save :save_outstanding_amount
def save_outstanding_amount # atomic saving
self.outstanding_amount = new_outstanding_amount
end
def update_outstanding_amount # adds another SQL query
update_column(:outstanding_amount, new_outstanding_amount)
end
private
def new_outstanding_amount
total - payments.sum(&:amount)
end
end
How can make this dynamic, so that the first method gets called from all instances of the Invoice class and the second method gets called from all instances of other classes, e.g. the Payment class?
You are doing something very dangerous here. Make sure use sql to change the value instead of setting a new value.
Imagine the following:
Invoice amount: 1000
User1 deducts 100, but does it with a very slow request.
Right after user 1 starts, User2 deducts 500, and does it really quickly.
Since you're doing this stuff within the application, you end up with an outstanding amount of 900, since that's the last executed method. update_counters creates a 'safe' sql query.
self.class.update_counters self.id, payments.sum(&:amount)
This also solves your question on how to call them from two classes. Always update those columns like this.

Self-reference in a Rails Model

Let's say I have two models: User and Point.
Point belongs_to the user. Point has an 'amount'. I want to define a method in the Point model that will return the user's total points. I'd like to call it in such a manner: user.points.total
I just don't know how to define the method in the Point model in such a way that I don't have to pass it the User's ID. I'd imagine there is a simple way to do this, but my Googlefu is failing me.
Assuming that User model has
has_many :points
you can do like this in User model
def total_points
points.sum(:amount)
end
Given that you want the method defined in the Point model, you'd want to use:
class Point
def total
user.points.sum(:amount)
end
end
You can then get the total points for the user associated with a given point by invoking point.total. You can't get total points through User.points.total because User is a class and doesn't refer to a particular user.
Consider using #sum, which uses SQL sum to do an aggregation.
For example,
person.points.sum('amount')
or
# inside User.rb
def total
points.sum('amount')
end

ruby on rails: average numeric values from database

I'm allowing users to insert numberic values into database, and I can output them through an each loop. But is there a way to just output the average value? Like say, if user is reviewing this person (only 1-5 values), I would want to output only the average instead of
<% #ratings.each do |r| %>
<%= r.rating %>
<% end %>
This displays all the records. How do I only display the average (which should also need the total of reviews this user has right)?
Thanks!
There are several ways of going about this. For instance, you could potentially abstract this logic into a custom helper. The approach I would take is to bring this logic into the model and create a convenience method on the User model itself:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :ratings
def average_rating
ratings_array = ratings.map(&:rating)
ratings_array.inject{ |sum, el| sum + el }.to_f
end
end
Then, if your view, you'd access ratings like so:
<%= #user.ratings %>
UPDATE:
If you're inclined to use a view helper, something like this would work:
# app/helpers/ratings_helper.rb
module RatingsHelper
def average_rating(ratings)
ratings_array = ratings.map(&:rating)
ratings_array.inject{|sum, el| sum + el}.to_f
end
end
In your view, you'd access the average by passing in the ratings to calculate the average of:
<%= average_rating(#user.ratings) %>
UPDATE 2:
Wow. There's a native ActiveRecord function called average() that will calculate the average value of a given column with a single line of code:
<%= #user.ratings.average('rating') %>
I believe this function operates on the database level. If so, using this method would almost certainly generate the lowest overhead of any of the solutions listed.
its better to use some caching for the average rating. For example
user has_many ratings
and rating belongs_to :user
Then in rating model you may have a after_save call_back to save average ratings to its user. To achieve this you have to have average_ratings column in post model.
And in rating.rb:
after_save :update_average_ratings
private
def update_average_ratings
average_ratings = self.user.ratings.collect(&:rating).sum / self.user.ratings.count
user.update_attributes(average_ratings: average_ratings)
end
It will be very efficient and you can call #user.average_ratings when you need that.
Hope that helps

Rails - Good way to associate units of measurement with my database columns?

I've got one model with about 50 columns of measurement data, each with a different unit of measurement (ie. grams, ounces, etc.). What is a good way to associate units of measurement with columns in my database? The primary use for this is simply for display purposes. (Ruby on Rails)
EDIT: To clarify, my model is an object, and the attributes are different measurements of that object. So, an example would be if I had the model Car and the attribute columns :power, :torque, :weight, :wheelbase, etc. I would want car.power.unit to return hp and car.weight.unit to return lbs., etc. This way, I would be able to do something like this:
<%= car.power + car.power.unit %>
and it would return
400hp
Updated Answer
Since you're storing many columns of data, but each column is only one type, and your concern is strictly presentational, I would just use a decorator to accomplish what you need. See this railscast for an example of a great way to do this using Draper.
Basically, a decorator wraps your model with presentation specific methods, so instead of:
#CarsController.rb
def show
#car = Car.find(params[:id])
end
You would use
#CarsController.rb
def show
#car = CarDecorator.find(params[:id])
end
You would define a decorator like so:
class CarDecorator < ApplicationDecorator
decorates :car
def horsepower
model.power.to_s + "hp" #call to_s just in case
end
end
Then in your view any time you called #car.horsepower you would get 123hp instead of 123. In this way you can build a big long reusable list of presentation methods. You can share methods between objects using inheritance, and you can allow methods from the original model to be called as well. See the railscast and the docs etc. You can use Draper or you could roll your own presenter class if you don't want to use a library.
Previous Answer (Abridged):
I can see two nice, easy ways to do this:
1) Just add a text column for units to your data model. IE: to get "400hp" use [data.value,data.units].join
2) You could get a little richer association by having a Units model, perhaps with help from something like ActiveEnum.
You could add a unit model with a for attribute, where you save the attribute in the messurement, you want to apply the unit to. Example:
def Unit < ActiveRecord::Base
scope :for, lambda{|messurement| find_by_for( messurement.to_s ) }
end
This allows you stuff like:
<%= #car.torque + Unit.for(:torque).symbol %>
I do not know if this is of so much advantage, but its a way to solve your problem...

Resources