CommonThread

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

Filed Under: | Tags:

Comments

  1. Florian 01.28.07 / 08AM
    Hi, I found your blog via google by accident and have to admit that youve a really interesting blog :-) Just saved your feed in my reader, have a nice day :)
  2. Fritz Savage 11.12.08 / 18PM
    zwzdfkcgj5sfh4cv

Have your say

A name is required. You may use HTML in your comments.




Recent Articles

Categories