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
Related
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
i have a User model and a Post model. User has_many Posts; Post belongs_to User.
I want to create a table that lists a User and the total number of Posts per user as long as the post's public_flag is 't'. Is this possible directly in the view?
In my controller:
#users = User.all
In my view:
<% #users.each do |user| %>
<%= user.posts.size%>
<% end %>
Of course, this gets ALL of the user's posts regardless if the public_flag is true. Is there any way to set a condition in the View, or will I need to do something fancy in the controller (AR Query?).
Thanks for your help in advance!
You can daisy-chain restrictions for models, like so:
user.posts.where(:public_flag => 't').count
The advantage of using count over size is that instead of pulling all the posts into memory and counting them, ActiveRecord will execute a count query against the database and save a lot of processing.
user.posts.find_by_public_flag(true).count
I have three models
User has_many :articles
Article has_many :daily_view_metrics & belongs_to :user
DailyViewMetric belongs_to :article
I want each user to have an overview of the metrics for their articles. At the moment I pass #user = User.find(params[:id]) in the show action of the users controller to open a specific users's show page. I also pass in #articles = Article.where(user_id: params[:id]).load in order to limit the available articles to only the user's articles.
Since the DailyViewMetric model has the same article multiple times (at different days) I need to aggregate them into a new array of arrays. I.e. I need
article_id date views
1 feb-01 20
1 feb-02 50
2 feb-01 30
to be returned as [[1,70],[2,30]] so I can sort it according to user wishes. How do I do this? Do I make a named scope in the DailyViewMetric model? or in the user_helper.rb?
It would be nice if I could have something that I can run newArray.each do |a| on in my view to make a table with stuff like <%= a.article_id %> and <%= sumviews %>, etc.. with something I can pass in the user_id so the aggregate is limited to his/her articles
You should be able to do it in the following way
Long form:
views_array = []
#articles.each do |a|
a.views do |v|
views_array << [a.id, v.date, v.count]
end
end
Short form:
#articles.collect {|a| a.daily_view_metrics.collect {|dv| [a.id, dv.date, dv.count] } }
I have the following line of code
<% map = options_for_select(User.all.map {|u| [u.first_name+" "+u.last_name, u.id]}) %>
which grabs the first and last name of a user and submits its ID in a form. Now I have added a few users and they are not in alphabetical order. How can I sort this map by first name?
You can also use order to get the rows already ordered from the database:
<% map = options_for_select(User.all.order(:first_name).map {|u| [u.first_name+" "+u.last_name, u.id]}) %>
May be...
<% map = options_for_select(User.all.map {|u| [u.first_name+" "+u.last_name, u.id]}.sort) %>
You should never use queries on the views. You should use views only for presentation, and all the logic on the Models and or the Controllers.
Also, respecting Fat Models, Skinny Controllers Best Practice:
In practice, this can require a range of different types of refactoring, but it all comes down to one idea: by moving any logic that isn’t about the response (for example, setting a flash message, or choosing whether to redirect or render a view) to the model (instead of the controller), not only have you promoted reuse where possible but you’ve also made it possible to test your code outside of the context of a request.
Finally, in this case it's best to use a scope to reuse it later.
Use a scope on User model, and have a name method:
class User < ActiveRecord::Base
scope :order_by_name, ->(first_name, last_name) { order("#{first_name} ASC, #{ last_name} ASC") }
def name
"#{first_name} #{last_name}"
end
end
If you're calling your line from users/index, create an instance variable to load the users collection like this:
class UsersController < ApplicationController
def index
#users = User.order_by_name
end
end
Then you would call on the view using options_from_collection_for_select like this:
<% map = options_from_collection_for_select(#users, :id, :name) %>
I recommend you to do both the CONCAT and ORDER in the Database for performance reasons
User.select("CONCAT(u.first_name, ' ', u.last_name), u.id").order("u.first_name")
This User.all.map could be a bottleneck of your application
Live site: http://iatidata.heroku.com
Github: https://github.com/markbrough/IATI-Data
Based on aid information released through the IATI Registry: iatiregistry.org
I'm a bit of a Rails n00b so sorry if this is a really stupid question.
There are two key Models in this app:
Activity - which contains details
such as recipient country, funding
organisation
Transaction - which contains details such as how much money (value) was committed or disbursed (transaction_type), when, to whom, etc.
All Transactions nest under an Activity. Each Activity has multiple Transactions. They are connected together by activity_id. has_many :transactions and belongs_to :activity are defined in the Activity and Transaction Models respectively.
So: all of this works great when I'm trying to get details of transactions for a single activity - either when looking at a single activity (activity->show) or looping through activities on the all activities page (activity->index). I just call
#activities.each do |activity|
activity.transactions.each do |transaction|
transaction.value # do something like display it
end
end
But what I now really want to do is to get the sum of all transactions for all activities (subject to :conditions for the activity).
What's the best way to do this? I guess I could do something like:
#totalvalue = 0
#activities.each do |activity|
activity.transactions.each do |transaction|
#totalvalue = #totalvalue + transaction.value
end
end
... but that doesn't seem very clean and making the server do unnecessary work. I figure it might be something to do with the model...?! sum() is another option maybe?
This has partly come about because I want to show the total amount going to each country for the nice bubbles on the front page :)
Thanks very much for any help!
Update:
Thanks for all the responses! So, this works now:
#thiscountry_activities.each do |a|
#thiscountry_value = #thiscountry_value + a.transactions.sum(:value)
end
But this doesn't work:
#thiscountry_value = #thiscountry_activities.transactions.sum(:value)
It gives this error:
undefined method `transactions' for #<Array:0xb5670038>
Looks like I have some sort of association problem. This is how the models are set up:
class Transaction < ActiveRecord::Base
belongs_to :activity
end
class Activity < ActiveRecord::Base
has_and_belongs_to_many :policy_markers
has_and_belongs_to_many :sectors
has_many :transactions
end
I think this is probably quite a simple problem, but I can't work out what's going on. The two models are connected together via id (in Activity) and activity_id (in Transactions).
Thanks again!
Use Active Record's awesome sum method, available for classes:
Transaction.sum(:value)
Or, like you want, associations:
activity.transactions.sum(:value)
Let the database do the work:
#total_value = Transaction.sum(:value)
This gives the total for all transactions. If you have some activities already loaded, you can filter them this way:
#total_value = Transaction.where(:activity_id => #activities.map(&:id)).sum(:value)
You can do it with one query:
#total_value = Transaction.joins(:activity).where("activities.name" => 'foo').sum(:value)
My code was getting pretty messy summing up virtual attributes. So I wrote this little method to do it for me. You just pass in a collection and a method name as a string or symbol and you get back a total. I hope someone finds this useful.
def vsum collection, v_attr # Totals the virtual attributes of a collection
total = 0
collection.each { |collect| total += collect.method(v_attr).call }
return total
end
# Example use
total_credits = vsum(Account.transactions, :credit)
Of course you don't need this if :credit is a table column. You are better off using the built in ActiveRecord method above. In my case i have a :quantity column that when positive is a :credit and negative is a :debit. Since :debit and :credit are not table columns they can't be summed using ActiveRecord.
As I understood, you would like to have the sum of all values of the transaction table. You can use SQL for that. I think it will be faster than doing it the Ruby way.
select sum(value) as transaction_value_sum from transaction;
You could do
#total_value = activity.transactions.sum(:value)
http://ar.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html