#364 Active Record Reputation System
Jul 09, 2012 | 10 minutes | Active Record, Plugins
If you need to calculate an average user's rating or sum up a number of votes, consider using the activerecord-reputation-system gem. Here I will cover the basics and also briefly present a from-scratch solution.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
I don't know what makes this gem different from the existing
acts_as_votable
gem https://github.com/ryanto/acts_as_votable?Maybe it's about something not covered in this episode, which is indirect reputation computing? Look at examples: https://github.com/twitter/activerecord-reputation-system/#usage-example
Aha, I see, thanks for sharing. May be
acts_as_votable
replaces the doing from scratch approach.Thanks for the episode!
Is there a best practice for leaving room for new "haikus" to still have a shot at appearing at the top?
Would it be a good idea to prepend @haikus in the index action with a few of the more recently created haikus?
Great episode as always.
I found we can apply this from-scratch solution to implement facebook-like 'Like' button. Or is there any gem to do this already?
If you need it simple then
acts_as_votable
will be a solution for you.activerecord-reputation-system
has some advanced options.It's worth noting that sorting things by
positive votes - negative votes
is not usually what you want to sort by.That's a great article. Thanks for sharing!
Just a note (although it's already mentioned in the episode): The code of the
voted_for?
method is inefficient. The problem is that it actually instantiates an ActiveRecord object which, in this case, is rather unnecessary.Instead of this
you should use the
exists?
method:If you are using scopes or something like that, you can also suffix it to a relation:
In both cases, rather than instantiating an ActiveRecord object, it would just call the connection's
select_value
which is way more efficient.Side note: The
exists?
and its siblingany?
are also helpful for use cases where you want to render a collection or, if it's empty, show the user a message like "No comments yet" or whatever.thanks for clarifying this
Awesome, thanks!
I've taken the concept of if a user has voted for something and taken it a step further. The user can see if they have up voted something or down voted something.
This came in handy when I was checking to see if the user has up voted or not so I could change the image.
Won't this result in n+1 problems, where you're doing a query for each answer?
Can you elaborate on what you mean by n+1 problems?
Hi,
I tried your approach but with a variation. It doesn't work for me. Can you please check out the description of this problem?
http://stackoverflow.com/questions/15511820/activerecord-reputation-system-gem-extra-functionality-implementation
I've also used cancan to limit the user from voting on their own Answer/Question/Post.
thanks..
Thanks, BTW :vote is an action, yours might be different.
How would this work with other query parameters such as searches or paging? It seems that a simple search parameter added to the model is not recognized after the find_with_reputation call.
Hi binaryx,
try the reputation-system-from-scratch. I got this to work with will_paginate.
Hey... ... extremely late on this one - but just started a new app and needed reputation with pagination
By placing the "find_with_reputation" after pagination (will_paginate) works.
hi i followed all the tutorial but when i wanna show the links to vote, it says that function can't accept my voteable model as parameter...maybe it's compulsory that user is the creator of the voteable model? in my app the admin creates voteable resources, the user creates other kind of resources...could someone help me?
I'm wondering if it's possible with this gem to sort by the reputation table (RSEvaluation) only objects that have certain properties, such as
created_at > 1.day.ago
? Other voting systems offer a tally/query method, but with ar-rep-system I'm struggling to write similar functionality.I was actually looking for a solution how to eagerly-load if the user voted on the answer, so that I can display a list of answers and tell if the user voted on them without doing the additional query.
I guess this could be done with some joins, but I wasn't able to do it with the activerecord-reputation-system.
Did anyone manage to do this?
With activerecord-reputation-system 1.3.0,
You can use evaluated_by method to do this.
e.g.
will return all Haiku instances that are evaluated by @user.
Not working...
undefined method `add_evaluation' for nil:NilClass
Anyone know why the reputation system isn't providing this method?
It looks like you are calling add_evaluation on nil value. You should make sure the variable does not have nil value.
my error when I'm trying to get total_votes:
what's wrong?
Is it possible to use this to make a review system? For example: Have a five star rating system with comments/text belonging to each vote?
I am trying to implement the same thing. Here is my post on stackoverflow:
http://stackoverflow.com/questions/12496333/stange-behaviour-with-twitter-activerecord-reputation-system-average-rating
I tried to follow the same methodology as Ryan has here for the Haiku voting but I must be missing something on my structure.
The 'average' that I get back is not as expected and almost like the it's tracking the previous ratings.
Did you ever figure this out? I'm looking to do the same thing.
Hi Ryan,
I had a problem with your reputation-system-from-scratch, that all haikus with more than one vote were ordered as they had only one vote. I added the
SUM()
function to theby_votes
class method to solve this. I also had to addgroup
to the query to get more than one haiku. WithoutSUM()
andgroup
I got duplicate haikus.@Gerald: Thanks so much for this fix. This totally solved my problems :)
I have a Locations model with an belongs to association to my Areas model. The Areas model is setup with the Ancestry gem where I can build an arbitrary hierarchy of 'Areas' Say like this:
So in my Locations model I have a area_id column and it's easy enough to call Locations.find(1).area.name = 'Child Area 1.1.2' and I can sort easy enough on this.
Now I have also created methods using ancestry functions in the Locations model to return the parent area's like this:
It's a bit of a hack but work except I can't sort on it. If I call Locations.all I can loop through and display a table etc. on a view and can create a sort function on the last child (i.e. area.name) but sorting based on area_level_1 fails. I am guessing there is an ugly manual SQL way to do this but I was hoping to perhaps find a more elegant way.
Where is really gets ugly is I am trying to leave the structure open enough where the area_id could only be 1 or 2 levels deep (i.e. id = 1 or id = 2 in my example data).
Excellent episode, hmm but how can I add some ajax to avoid reload the page when someone votes??
For accessing the total number of votes for a haiku, we need to use
haiku.reputation_for(:votes)
instead ofhaiku.reputation_value_for(:votes)
.For ajax voting...
http://railscasts.com/episodes/136-jquery-ajax-revised
... it was very simple to setup.
why does the line
@haiku.add_or_update_evaluation(:votes, value, current_user)
generate the error:application controller:
How can this be corrected?
Hey if you get: undefined method `reputation_value_for'
change `reputation_value_for' to 'reputation_for'
reputation_value_for was deprecated and replaced by reputation_for, see commit (10/03/2012)
reputation_for(reputation_name, *args)
https://github.com/twitter/activerecord-reputation-system/commit/552c04b0dead76cd79fb3915845b17b6a8de0cca
Also normalized_reputation_value_for was deprecated and replaced by normalized_reputation_for
normalized_reputation_for(reputation_name, *args)
THANKS !
Hey all, like @Mark says above, be sure to use
reputation_value_for
.Also, the reputation system requires different names for all types of reputations (ie. users and haikus can't share the same reputation name), so the code from the example would be more like:
you mean reputation_for?
I suppose that if you can decide the value of the evaluation, you could use this gem for rating 1-5 stars right? or would it be crazy?
I did this with the jquery-raty-rails gem and it works quite well.
My current problem is displaying the value of the vote the current_user gave to my model. The average vote for my model is no problem via Model.reputation_for(:votes)
Here's a simple multi-rating system for Photos based of Ryan's videocast. It is simple: just pass the value as the params[:type]: vote_photo_path(photo, type: "1"). Aalso using twitter/bootstrap/font_awesome for the vote icons
vote:
Notice the current_user cannot vote on their own photos;
And I included a 'status' attribute for 'Approved' or 'Denied' the display/hide the photo from public viewing.
Automatic Quality Control: Notice the automatic status change to 'Denied' is ONLY after 10 votes and with an average value less than 3.
vote_range:
This is in the application_controller.rb because I use the reputation system on several models: Ads, Photos, Profiles.
vote_reset:
This allows for resetting the values.
voteable?:
Allows control of who can vote
I hope this helps
Help me not working method voted for? After the user has voted
This a newbie question:
I am getting an error if the user is not logged in. How do you enforce signup if they want to vote, while still allowing them to look at the page if they haven't signed up?
The error I get is "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id"
If you're using Cancan, you would stick 'skip_authorization_check :only => [:show]' in the appropriate controller.
Hi everyone, I'm trying this gem but I have different scenario. What I need this gem is for assign "points" for a "Employee" model, every-time when a "Manager" create "Job" entry. I have written a question on this on stackoverflow - appreciate if someone can help me out.
http://stackoverflow.com/questions/14556247/rails-reputation-system-for-a-content-management-system
I also have an issue, but with the SQL that is produced by the relationship between
User
andmicropost
(likehaiku
in the Railscast).I posted it on stack overflow, would love it if someone could help.
Hi guys, I am very very new to ROR so I really appreciate any help.
When I place the
in my post model, my page shows up as
"undefined method `has_reputation' for #Class:0x007fe5708753e0"
and then reloading turns it into
"undefined method `key?' for nil:NilClass"
If I take away these codes in my post model, the page will show fine then I try to click on the "up" button, and this shows up
undefined method `add_evaluation' for #Post:0x007fe57100e2c8
anyone can help me out? thank you so much
this is my post_controller
def vote
value = params[:type] == "up" ? 1 : -1
@post = Post.find(params[:id])
@post.add_or_update_evaluation(:votes, value, current_user)
redirect_to :back, notice: "Thank you for voting"
end
somehow it now works out of nowhere. i think restarting the server helps!
Hi. I am trying to implement a reset voting feature. So when a user voted up and clicks again on thumbs up, the vote should be reset. I tried to delete evaluation but it does not work. Any clue or someone implement something similar?
Thanks
I've implemented "un-voting". This railscast is dated if you're using the latest version of activerecord-reputation-system. Forget the voted_for? method on the user model and use
evaluators_for
.Here's my vote method:
I'm tracking up votes and downvotes with 2 parameters. If anyone knows a way to keep it to 1 parameter but show # of upvotes and # of downvotes, let me know!
As much as I love Ryan and all the Railscasts, this one is a bit confusing, for this reason:
When Ryan talks about "reputation_value_for" (now "reputation_for") as giving him the "total number of votes," it is in fact giving him the SUM OF THE VOTE VALUES. So, if one is looking for the actual total number of votes, regardless of vote value, the returned value is incorrect.
In fact, as far as I can tell, there is no generic way to get the total number of votes on a model without assigning a constant "1" to a vote tally value and using reputation_for(:vote_tally). So, in order to end up with:
78% of 281 voters like this Haiku. You like it, too.
...there is a need to add a THIRD reputation to get the count of ReputationSystem::Evaluations on the Haiku model, since we need to use:
has_reputation :votes, source: :user, aggregated_by: :sum
has_reputation :avg_vote, source: :user, aggregated_by: :average
...in order to get the user's vote (assigned as 1 = like or 0 = unlike) as well as the average vote. So we add:
has_reputation :vote_tally, source: :user, aggregated_by: :sum
Then, in the vote action in the controller:
v_value = params[:type] == "up" ? 1 : 0
@haiku.add_or_update_evaluation(:votes, v_value, current_user)
@haiku.add_or_update_evaluation(:avg_vote, v_value, current_user)
@haiku.add_or_update_evaluation(:vote_tally, "1", current_user)
If there's an easier way to get the total number of votes on a model, I'd love to hear about it.
As said above, in your model
def count_positive
evaluations.where( target_type: self.class, target_id: self.id, value: 1.0 ).count
end
def count_negative
evaluations.where( target_type: self.class, target_id: self.id, value: -1.0 ).count
end
then you can sum up both in your view
or
def count_total
evaluations.where( target_type: self.class, target_id: self.id, value: 1.0 ).count + evaluations.where( target_type: self.class, target_id: self.id, value: -1.0 ).count
end
Do you know what the tax on SQL would be for this? I just used one reputation to count positive (votes up) and another to count negative (votes down).
Any suggestions for how to implement caching of activerecord-reputation scores? Calculating these for each call creates a ton of overhead in my application.
Any idea for how to adapt user.voted_for? for a specific metric? I'm tracking up votes and down votes separately for comments in my app and would like to display a "toggled" look if a user has already voted.
This is my vote method on my comments controller:
Figured it out. The most recent version (2.2) has the
evaluators_for
method which reports users which have votes on a resource with the "metric".This is my vote method which now includes "un-voting":
my buttons vote other comments up....help!
github.com/egong/scribe
Does this gem work in Rails 4? Documentation is unclear. I could greenfield a 3.2 app if need be.
Hey everyone, so I am trying to use this on Rails 4 with Devise, is this not supported?
I get this error:
activemodel-4.0.0/lib/active_model/deprecated_mass_assignment_security.rb:14:in attr_accessible': attr_accessible is extracted out of Rails into a gem. Please use new recommended protection model for params(strong_parameters) or add protected_attributes to your Gemfile to use old one. (RuntimeError)
Should I be using the "Protected Attributes" or having strong parameters defined in a controller somewhere, but which fields?
Thank you.
The gem hasn't been updated for Rails 4.
@Dimitri have you found a solution for this issue? Thanks
hey @ryan! I used your home brew in a rails 4 app, but the
self.by_votes
method doesn't seem to work. As is... it orders things, but it duplicates each item for each time they've been voted on. I was wondering if you had any ideas?Has anyone tried to implement an Activity tracker (see ep. 407, et.al) with this?
THIS GEM DOES NOT WORK WITH RAILS 4. Don't even try it. The employee who made it formerly worked for Twitter, then left Twitter, and no one else on the team is interested in supporting it. Recommend the Thumbs Up gem. https://github.com/bouchard/thumbs_up
UPDATE: Rails 4 support was just merged into this, so it should work with Rails 4 now.
UPDATE:
for evaluation I used:
so change is just class_name