Archive for June, 2006

Acts As Voteable Rails Plugin

Right on the heels of my release of acts_as_commentable, I am now making available a acts_as_voteable Ruby on Rails plugin. The Acts As Voteable plugin allows for model to be voted on by users.

To install this plugin run the following command:

script/plugin install http://juixe.com/svn/acts_as_voteable

The installation process will add several ruby files in the vendor/plugins directory. Create a new rails migration and cut and past the following self.up and self.down methods:

def self.up
  create_table :votes, :force => true do |t|
    t.column :vote, :boolean, :default => false
    t.column :created_at, :datetime, :null => false
    t.column :voteable_type, :string, :limit => 15,
      :default => "", :null => false
    t.column :voteable_id, :integer, :default => 0, :null => false
    t.column :user_id, :integer, :default => 0, :null => false
  end

  add_index :votes, ["user_id"], :name => "fk_votes_user"
end

def self.down
  drop_table :votes
end

Once you have installed the plugin you can start using it in your ActiveRecord models simply by calling the acts_as_voteable method.

class Post < ActiveRecord::Base
  acts_as_voteable
end

To cast a vote for a post you can do the following:

vote = Vote.new(:vote => true)
post = Post.find(params[:id])
post.votes << vote

ActiveRecord models that act as voteable can be queried for the positive votes, negative votes, and a total vote count by using the votes_for, votes_against, and votes_count methods respectively. Here is an example:

positiveVoteCount = post.votes_for
negativeVoteCount = post.votes_against
totalVoteCount = post.votes_count

And because the Acts As Voteable plugin will add the has_many votes relationship to your model you can always get all the votes by using the votes property:

allVotes = post.votes

Technorati Tags: , , , , , ,

Rails Recipes

I just received Rails Recipes by Chad Fowler (I original ordered this book through Amazon back in February). This book is jam packed with 70 recipes covering user interface, database, controller, testing, and big-picture, and email recipes. The recipes that I am most interested in are Converting to Migration-Based Schemas, Make Dumb Data Smart with composed_of(), Cleaning Up Controllers with Postback Actions, Write Code That Writes Code, Automating Development with Your Own Generators, Getting Notified of Unhandled Exceptions, Syndicate Your Site with RSS, and Making Your Own Rails Plugins (I wrote my own recipe on rails plugins).

Rails Recipes picks up where Agile Web Developement with Rails leaves off. I got started with Rails just by reading the first few chapters of Agile Web Development with Rails. Now that I am getting into some more advanced features of the framework I will depend on Rails Recipes. Another book that has been helpful in my rails development has been Ruby in a Nutshell by Ruby’s author Yukihiro Matsumoto. These three books form the holy trinity of Ruby on Rails.

Technorati Tags: , , , , , , ,

Rails Plugin Tutorial

There are other Rails Plugin tutorials out there but I found holes in the details they provided so I felt I need to write this Ruby on Rails plugin tutorial. This tutorial is written with the experience of developing the acts_as_commentable plugin.

To get started you will need to create your plugin directory under the vendor/plugins for your Rails application. You will need a init.rb file and a lib directory. Here are

To get started run the generate plugin script to create the necessary directory structure in your Rails application.

script/generate plugin acts_as_commentable

This command should have created the following files and directories:

create  vendor/plugins/acts_as_commentable/lib
create  vendor/plugins/acts_as_commentable/tasks
create  vendor/plugins/acts_as_commentable/test
create  vendor/plugins/acts_as_commentable/README
create  vendor/plugins/acts_as_commentable/Rakefile
create  vendor/plugins/acts_as_commentable/init.rb
create  vendor/plugins/acts_as_commentable/install.rb
create  vendor/plugins/acts_as_commentable/lib/acts_as_commentable.rb
create  vendor/plugins/acts_as_commentable/tasks/acts_as_commentable_tasks.rake
create  vendor/plugins/acts_as_commentable/test/acts_as_commentable_test.rb

In this tutorial we are just going to edit init.rb and acts_as_commentable.rb. We are also going to add a comment.rb file for our ActiveRecord model.

For the Rails environment to find you plugin correctly you need to edit the init.rb. For the acts_as_commentable plugin I had to add the following lines:

require 'acts_as_commentable'
ActiveRecord::Base.send(:include, Juixe::Acts::Commentable)

These two lines of code will enhance the ActiveRecord class to include the methods defined in the Juixe::Acts:Commentable module. The Juixe::Acts::Commentable module contains most of the implementation for this plugin.

Now let me define the basic structure of the acts_as_commentable plugin.

module Juixe
  module Acts #:nodoc:
    module Commentable #:nodoc:

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

      module ClassMethods
        def acts_as_commentable
          include Juixe::Acts::Commentable::InstanceMethods
          extend Juixe::Acts::Commentable::SingletonMethods
        end
      end

      module SingletonMethods
        # Add class methods here
      end

      module InstanceMethods
        # Add instance methods here
      end
    end
  end
end

Even though I still haven’t defined any methods there is a lot going on in this stub. The include and extend functions add functionality to a class or module from the named module. I have have written extensively about mixin modules with classes with include and extend in Ruby Mixin Tutorial.

Just to flush out the stub for this plugin let add and edit the Comment ActiveRecord class in comment.rb file.

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

At this point any model can act as commentable by just adding one line in you ActiveRecord class. Lets say you are writing a blog application you maybe define a Post mode as such:

class Post < ActiveRecord::Base
  acts_as_commentable
end

At this point you have a working plugin. You can add static and instance methods in the SingletonMethods and InstanceMethods, respectively, that will be available to any model that acts as commentable. Of course you can also add methods to the Comment class. For a complete listing of the code for the Acts As Commentable plugin see the project’s site.

One final note, when editing a plugin I found it necessary to restart the server.

Technorati Tags: , , , , ,

Acts As Commentable Plugin

I am happy to announce that I have released the acts_as_commentable plugin, my first Ruby on Rails plugin. The Acts As Commentable plugin allows for comments to be added to your Rails ActiveRecord classes.

To install the Acts As Commentable plugin run the following command:

script/plugin install http://juixe.com/svn/acts_as_commentable

The installation process will add several ruby scripts in the vendor/plugins directory. Create a new rails migration and cut and past the following self.up and self.down methods:

def self.up
  create_table :comments, :force => true do |t|
    t.column :title, :string, :limit => 50, :default => ""
    t.column :comment, :string, :default => ""
    t.column :created_at, :datetime, :null => false
    t.column :commentable_id, :integer, :default => 0, :null => false
    t.column :commentable_type, :string, :limit => 15,
      :default => "", :null => false
    t.column :user_id, :integer, :default => 0, :null => false
  end

  add_index :comments, ["user_id"], :name => "fk_comments_user"
end

def self.down
  drop_table :comments
end

Once you have installed the plugin you can start using it in your ActiveRecord class simply by calling the acts_as_commentable method.

class Post < ActiveRecord::Base
  acts_as_commentable
end

To add a comment to a post object you can do the following:

comment = Comment.new(:title => titleStr, :comment => commentStr)
logger << "COMMENT #{comment.comment}n"
post.comments << comment

Or you could have use the add_comment method on post.

post.add_comment comment

You can also use the post’s comments property to read all comments for the given post. Once a comment has been added to a post you can always reference the post object using the comment’s commentable property.

comment.commentable # references the post

One note, the default implementation of Acts As Commentable requires you to use a user model to link all comments to a user. This requirement can easily be removed or enhanced in the Comment class. But if you have a user model you can retrieve all comments for a user by executing the following statement:

comments = Comment.find_comments_by_user(userInstance)

If you want to retrieve only the comments for a user for a particular model you can do something like:

postComments = Post.find_comments_by_user(userInstance)

If you have any comments, questions, and/or suggestions please don’t hesitate to drop me a line.

Technorati Tags: , , , , ,

Ruby Mixin Tutorial

In Java you just have classes (both abstract and concrete) and interfaces. The Ruby language provides classes, modules, and a mix of both. In this post I want to dive into mixins in Ruby.

In the Ruby language a mixin is a class that is mixed with a module. In other words the implementation of the class and module are joined, intertwined, combined, etc. A mixin is a different mechanism to the extend construct used to add concrete implementation to a class. With a mixin you can extend from a module instead of a class. Before we get started with the mixin examples let me first explain what a module is.

I think of a module as a degenerate abstract class. A module can’t be instantiated and no class can directly extend it but a module can fully implement methods. A class can leverage the implementation of a module by including the module’s methods. A module can define methods that can be shared in different and seperate classes either at the class or instance level.

Let me define a module, albeit a trivial one, that would convert a numeric integer value to English.

# Convert a integer value to English.
module Stringify
  # Requires an instance variable @value
  def stringify
    if @value == 1
      "One"
    elsif @value == 2
      "Two"
    elsif @value == 3
      "Three"
    end
  end
end

Note that the Stringify module makes use of a @value instance variable. The class that will be mixed with this module needs to define and set a @value instance variable since the Stringify module uses it but does not define it. In addition to instance variables a module could invoke methods defined not in the module itself but in the class that it will be mixed with.

Now let me construct a self contained module that is not dependent on the implementation of any class that it can be mixed with.

# A Math module akin to Java Math class.
module Math
  # Could be called as a class, static, method
  def add(val_one, val_two)
    BigInteger.new(val_one + val_two)
  end
end

The methods in the Math module are intended to be invoked like class methods, also known as static methods. The add method in the Math module accepts two integer values and returns an instance of BigInteger. Let me now define the mixin BigInteger class.

# Base Number class
class Number
  def intValue
    @value
  end
end

# BigInteger extends Number
class BigInteger < Number

  # Add instance methods from Stringify
  include Stringify

  # Add class methods from Math
  extend Math

  # Add a constructor with one parameter
  def initialize(value)
    @value = value
  end
end

Read more »

Common Groovy Errors

At my work we are using Groovy extensively and often mix Java syntax in Groovy. I want to post some common Groovy language errors that I think everybody in my team has committed at one point when we forget that Groovy is not Java. The first error that I will mention is that use of array initializers. In Java you can construct an array using an initializer like this:

String[] array = new String[]{"one", "two", "three"}

In Groovy, when we attempted to use this construct we would get a nasty and hard to understand compiler exception. Groovy is an expressive language and you can construct an initialized list in the following fashion:

def list = ["one", "two", "three"]   // ArrayList

The list variable is an instance of ArrayList. If you require an array you need to cast the list such as:

def arr = (String[])list   // String array

Now, this leads me to the second compiler error that I have encountered in Groovy. In Groovy I keep writing for loops the Java way:

for(int i; i < list.size(); i++) {
   System.out.println(list.get(i));
}

From my understanding Groovy only supports the for/in loop construct. If you need to iterate over a list using an index you could do the following:

for(i in 0 .. list.size()-1) {
   println list.get(i)
}

If you don’t need the index you can loop over an list in by using the each method provided by the Groovy Development Kit (GDK):

list.each {
   println it
}

Since I mention how to construct an initialized list in Groovy, let me also cover maps. In Groovy you can construct a map by using the following construct:

def map = [name:'Juixe', date:new Date()]

Since map is an instance of HashMap you can retrieve values using the get method but Groovy provides a different mechanism. In Groovy you can access a value from a map like this:

println map['name']   // returns Juixe

In the examples above I have mentioned how to create lists and maps with data. If you need to create a list or map without data you can use one of the following statements:

def emptyList = []
def emptyMap = [:]

A third error that I have commited is that I keep thinking of the each closures as for loops. I have received compilation erros because I try to continue or break out of a closure loop. Closures are code block so you can break out of them using the return keyword. I continue to the next element in a list do something like the following:

[1, 2, 3, 4, 5].each {
  if(it == 3)
	return
  println it
}

Technorati Tags: , , ,

Next Page »