CommonThread

Displaying articles with tag

acts_as_voteable (0.1.1) - Now playing nicely with rails 1.2

Posted by ben, Thu Feb 01 06:33:00 UTC 2007

the acts_as_voteable plugin was updated to version 0.1.1 to refactor several methods and most importantly get rid of things that were deprecated in the 1.2 release of rails. Namely the ’:dependent => true’ was changed to ’:dependent => :destroy’

If you need help installing or updating the plugin you may read my post on the script/plugin command

0 comments | Filed Under: | Tags:

acts_as_voteable - Installing and Using the Plugin

Posted by ben, Wed Jan 24 19:24:00 UTC 2007

If you read the original acts_as_voteable post about creating an acts_as plugin for rails then here is the installation instructions I promised. If you didn’t read it, then you have no idea what I am talking about, still here it is.

Installation is easy given the script/plugin command. This automates the svn export of the plugin and manages source repositories of known plugins. Simply run …

script/plugin install http://svn.comthread.com/public/plugins/tags/stable/acts_as_voteable/

That will export the current version to your vendor/plugins/ folder. See script/plugin post to get instructions for removing or updating this plugin. Also, see how to install the plugin from a repository.

Be sure to read the README file included. It has instructions for creating the migration necessary to hold the votes. This is the migration necessary for the current version:

def self.up
 create_table :votes do |t|
   t.column :voter_id, :integer
   t.column :voter_type, :string
   t.column :voteable_id, :integer
   t.column :voteable_type, :string
   t.column :score, :integer
   t.column :created_at, :datetime
 end
end

def self.down
 drop_table :votes
end

To use the plugin simply add the line acts_as_voteable on any ActiveRecord object. For example …

class Widget < ActiveRecord::Base
  acts_as_voteable
end

Now we can cast our votes on widgets. You have the following methods available for any object that uses acts_as_voteable:

  • vote_for
  • vote_abstain
  • vote_against
  • vote_score
  • votes_for
  • votes_abstain
  • votes_against
  • voted?
  • vote

The vote_[for/abstain/against] methods all take a voter as a parameter. This is the person or thing casting the vote. It is a ploymorphic association so you can put any type of object you want to in there. It was intended to hold the user that casted the vote. A voter can only cast one vote per object.

The vote_score tallies up all the votes and gives you a score as an integer based on 1 point for votes_for, 0 points for votes_abstain & -1 points for votes_against

The votes_[for/abstain/against] methods all return the total number of votes for that particular sentiment.

The voted? method takes a voter as a parameter and returns a boolean telling if that voter has already cast a vote for that object.

The vote method takes a voter as a parameter and returns the score [1, 0, -1] of that voters vote for the current object and nil if there is no vote.

Also, you can call votes to return all of the votes for an object since the association is joined as votes.

1 comment | Filed Under: | Tags:

acts_as_voteable

Posted by ben, Wed Jan 17 16:31:00 UTC 2007

I realize that there are a couple of acts_as_voteable plugins out there, I have seen two others, but I needed the experience writing a plugin in rails and personally I like mine better :)

I will walk you through the process of writing a ruby on rails plugin and show you how I wrote mine.

Step 1 – setup plugin skeleton

script/generate plugin acts_as_voteable

that will setup the folder structure for your new plugin named ‘acts_as_voteable’

Step 2 – add any additional models we will need

You can add any additional model you will need in the lib folder. We will be adding our vote class so we will add vote.rb to the lib folder.Since the vote will need to have an association with the user that made the vote and the object being voted for we will add those.

belongs_to :voteable, :polymorphic => true
belongs_to :voter, :polymorphic => true

You should have noticed something a little different about these associations from the ones you normally use, we included olymorphic => true. Polymorphic associations are a pretty slick feature in rails, they allow you to join to another object without having to know its typeahead of time. Rails handles all of the connection code for you and you can use it like a normal association. The only difference is in the migration you need to make 2 columns for the association.

t.column :voteable_id, :integer
t.column :voteable_type, :string

The first one you should be used to from any other belongs_to but the second one tells rails what the object type is. This is fairly transparent to you when you are working with the class because you just assign an object to voteable and rails splits the two parts up when it saves. We will look at the other side of that polymorphic association in the next section.

Step 3 – extend ActiveRecord functionality

we will be writing an ActiveRecord plugin (which is one of the easier plugins) so we will want to edit the lib/acts_as_voteable.rb file in our plugin directory to extend that class. Since we are writing an acts_as plugin we will want to put our mixin code where rails keeps the rest of the acts_as functionality, ActiveRecord::Acts, we do this with the following code

module ActiveRecord
  module Acts #:nodoc:
    module Voteable #:nodoc:
      # acts_as_voteable code here
    end
  end
end

The first 2 lines are to insert a mixin into the already existing module ActiveRecord::Acts and the third creates your module Voteable. So once you have created the plugin you could reference your module with ActiveRecord::Acts::Voteable. The #:nodoc: tells ruby not to include that module in the RDoc documentation.

We will want to separate our class methods, instance methods & singleton methods into separate modules so that we can include them separately into the ActiveRecord where we want them.

module ClassMethods
  def acts_as_voteable(options = {})
    write_inheritable_attribute(:acts_as_voteable_options, {
      :voteable_type => ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
    })

    class_inheritable_reader :acts_as_voteable_options

    has_many :votes, :as => :voteable, :dependent => true

    include ActiveRecord::Acts::Voteable::InstanceMethods
  end
end

module InstanceMethods
  def vote_for(voter)
    votes << Vote.new({:voteable => self, :voter => voter, :score => 1}) unless voted?(voter)
  end

  ...

  def voted?(voter)
    votes.any? { |vote| vote.voter == voter }
  end
end

There are a couple of things to notice in this section of code …

The only method available to an ActiveRecord class is the acts_as_voteable method until that method is called then it includes the instance methods once it is called.

I told you we would discuss the other side of polymorphic associations and here it is. When you make an association to a polymorphic association you must use the :as to tell the association what field that association belongs to. In the case of the votes object we are telling the has_many association that the ActiveRecord extended by acts_as_voteable will have many votes and the ActiveRecord class should be stored in the voteable field.

In the InstanceMethods module, I abreviated the snippet here for readability, you will notice that many of the methods take a parameter called voter. This is intended to be a user but it can be anything that is casting that particular vote. The votes are restricted to one per voter. Voter is also stored in a polymorphic association so you can place any kind of object in here.

You will also notice a section with write_inheritable_attribute and class_inheritable_reader. This is just there so that I have to ability to extend the plugin with options passed to the acts_as_voteable call if I choose to do so in the future.

Step 4 – wire this up in the init.rb

require 'acts_as_voteable'
ActiveRecord::Base.send(:include, ActiveRecord::Acts::Voteable)

require File.dirname(__FILE__) + '/lib/vote'

The first line is fairly obvious we want to include the ‘acts_as_voteable’ class with all of our functionality in it. The last line is to include the model we created to assist this plugin. The second line is the magic line. This tells the ActiveRecord::Base class to include all of the modules and methods from the ActiveRecord::Acts::Voteable module. That is only half of the magic though. The other half is added in the ActiveRecord::Acts::Voteable module with the following code.

def self.included(base)
  base.extend(ClassMethods)
end

This is a callback function for the include method that passed the object that include was called on. So base will be the ActiveRecord::Base and the methods in the ClassMethods module will be added to base. Now you can call acts_as_voteable from any ActiveRecord object. And once you do, as you can see from the previous section, all of the instance methods are added to that ActiveRecord class.

And that’s about all there is to creating a acts_as plugin in rails. Once I get the code into SVN I will update this post with instructions for installing the plugin. Here are the instructions for installing the plugin

2 comments | Filed Under: | Tags: