Reddit. Quora. ProductHunt. What do all of these websites have in common? Besides their rabid fan bases, that is. They all enable their community to interact with content via simple voting buttons that users can click on to promote or demote content. Luckily for Ruby developers, there's an awesome gem I came across this week that quickly builds the same functionality. Here's a tutorial on how to get started with acts_as_votable.
First, copy the gem name and version number into your Gemfile.
gem 'acts_as_votable', '~> 0.10.0'
And install it with bundle
bundle install
Next, generate and migrate a simple migration file using the acts_as_votable syntax
rails g acts_as_votable:migration
rake db:migrate
Now for this example I'm going to use a model called Link, but you can add the following to whatever model in your application you want to make votable. In app/models/link.rb, I have:
class Link < ActiveRecord::Base
belongs_to :user
acts_as_votable
end
Technically, that's all you need to do to initialize the Gem's functionality, but let's confirm that it's working using the Rails Console. In this example, the method liked_by will establish an up-vote for "twitter" from the user, "matt." Then, we can check the number of likes that twitter has, which now should be one.
For a variety of other useful helper methods, be sure to check out the documentation on Github.
Now that the basic database relationship has been confirmed behind the scenes, let's set up the views to interact with our new functionality. To begin with, we'll need some routes. In app/config/routes.rb, we'll need to add another block to our links section.
resources :links do
member do
put "like", to: "links#upvote"
put "dislike", to: "links#downvote"
end
end
This block invokes upvote and downvote methods in the links controller, but those methods don't exist yet. So let's navigate over to app/controllers/links_controller.rb to set them up.
def upvote
@link = Link.find(params[:id])
@link.upvote_by current_user
redirect_to :back
end
def downvote
@link = Link.find(params[:id])
@link.downvote_by current_user
redirect_to :back
end
Finally, we'll need to add some buttons to our views to implement the voting functionality in the browser. I've got a simple view in app/views/links/index.html.erb with a loop through all of the links in my link model. It looks like this:
<% @links.each do |link| %> <h4><%= link_to link.title, link %><br> Submitted <%= time_ago_in_words(link.created_at) %> by <%= link.user.email %> </h4> <% end %>
Before the <% end %> tag, I'm going to add in some buttons and corresponding methods that will integrate our voting functionality so that any user can now contribute both up and down votes on any link.
<%= link_to like_link_path(link), method: :put do %> Upvote <%= link.get_upvotes.size %> <% end %> <%= link_to dislike_link_path(link), method: :put do %> Downvote <%= link.get_downvotes.size %> <% end %>
This will create some basic links that, when clicked on, will update with the amount of respective upvotes or downvotes for each link. Very cool!
At this point, you can also observe how the redirect_to :back action in the controller ensures that even though an action is submitted, the user stays on the same page after voting. After testing the functionality of your new feature, you might also want to style your buttons to make them more distinctive.
Special thanks to MasterRuby, Mackenzie Child, and of course Ryan T for making the Gem accessible and easy to use. If you have any questions, feel free to let me know in the comments below!