Refactoring: Implementing filtering June 16th, 2008

For a project at Openminds we needed an implementation for filtering some data from a list with the aid of checkboxes. Our first solution looked like this:

def index
  session[:order_filter] = params[:filter] if params[:filter] 
  session[:order_filter] ||= { :active => '1', :inactive => '1' } 

  if session[:order_filter][:active] == '0' && session[:order_filter][:inactive] == '0'
    @orders = []
  elsif session[:order_filter][:active] == '0'
    @orders = @user.orders.find(:all, :conditions => {:state => 'active'})
  elsif session[:order_filter][:inactive] == '0'
    @orders = @user.orders.find(:all, :conditions => {:state => 'inactive'})
  else
    @orders = @user.orders.find(:all)
  end
end

So this code takes the filter-parameters from params, or initializes them from defaults, and then does the correct finds.

In the next iteration, I stumbled onto this code and thought to myself “This can be done better”. After making sure the index action was sufficiently covered with tests, I had a go at it, this is what I came up with:

def list
  if filter.active? && filter.inactive?
      @orders = @user.orders.all
  elsif filter.active?
     @orders = @user.orders.active
  elsif filter.inactive?
    @orders = @user.orders.inactive
  else
    @orders = []
  end
end

protected
def filter
  return @filter if @filter
  session[:order_filter] = params[:filter] if params[:filter]
  session[:order_filter] ||= { :active => '1', :inactive => '1' }

  @filter = SearchFilter.new("Order", session[:order_filter])
end
helper_method :filter

# search_filter.rb
class SearchFilter
  attr_reader :name

  def initialize name, hash
    @name = name
    hash.each do |key, value|
      instance_eval <<METHOD
        def #{key}?
          #{value} == 1
        end
METHOD
    end
  end
end

This code is more readable in my opinion. I extracted the session-storing and parameter-juggling to the filter-method, which then returns a SearchFilter object. Thanks to SearchFilter I can now query filter for set values in a nice way. I can also call filter in my views, which is nicer then calling session[:order_filter][:active]. I also used named_scope instead of doing the finds manually, which improves readability a lot too!

How would you refactor this?

tags: l 2 comments »

acts_as_state_machine with auto named_scope powers May 21st, 2008

Rails 2.1 (Soon to be released?) brings us integrated named_scope‘ing. I’ve already mentioned named_scope here and there, and you can find a complete writeup on Ryan Daigle’s Blog

While working on projects on Rails Edge, we used to have the same pattern over and over.

class Mail < ActiveRecord::Base
  include AASM

  aasm_state :created
  aasm_state :sending
  aasm_state :sent
  aasm_state :failed
  aasm_state :delayed

  named_scope :created, :conditions => {:status => 'created'}
  named_scope :sending, :conditions => {:status => 'sending'}
  named_scope :sent, :conditions => {:status => 'sent'}
  named_scope :failed, :conditions => {:status => 'failed'}
  named_scope :delayed, :conditions => {:status => 'delayed'}
end

For each state we had, we created a named_scope, because it is so great and easy to be able to say Mail.failed.each .... However, this isn’t really DRY, and we can see a clear pattern here. When you think about it, you really should have a scope for every state you define. So I forked from AASM on github and started hacking away.

The result:

class Mail < ActiveRecord::Base
  include AASM

  aasm_state :created
  aasm_state :sending
  aasm_state :sent
  aasm_state :failed
  aasm_state :delayed
end

...
>> Mail.failed
=> []
>> Mail.sent
=> [#<Mail id: 1, email: "jan@domain.com", content: "hallo", :aasm_state: "sent", created_at: "2008-05-21 15:39:38", updated_at: "2008-05-21 15:39:38">, #<Mail id: 2, email: "jan@domain.com", status: "sent", created_at: "2008-05-21 15:39:39", updated_at: "2008-05-21 15:39:39">]

You can find my named_scope enabled AASM on github. At the moment only the master branch has been patched with my functionality, I’ll do the no_aasm_prefix branch somewhere this evening / tomorrow. Let me know if you’re using it, and what could be better This has all been implemented in the AASM Core now.

For those interested in the code, this is the bit that does the magic.

module AASM::NamedScopeMethods
  def self.add_named_scope base
    # Don't add unless it's a class which understands `named_scope`
    return unless base.respond_to? :named_scope

    base.extend AASM::NamedScopeMethods::ClassMethods
    base.class_eval do
      class << self
        alias_method :aasm_state_without_named_scope, :aasm_state
        alias_method :aasm_state, :aasm_state_with_named_scope
      end
    end
  end

  module ClassMethods
    def aasm_state_with_named_scope name, options = {}
      aasm_state_without_named_scope name, options

      self.named_scope name, :conditions => {self.aasm_column => name.to_s} unless self.scopes.include?(name)
    end
  end
end

Gotcha

While testing the plugin we found out 1 gotcha to this: do not define a aasm_state :new, because this will override the constructor of the class, and you’ll lose all creating-functionality.

Update

This is now default AASM behaviour as of this commit

tags: l 3 comments »

#2 May 13th, 2008

My 5 Rails Tips blog post has won second place in the RailsCast contest (Actually it was third, but for some reason the numbering is different there).

This is a great boost for me, to know I’m doing a good job posting this stuff. I also won a few things:

All great prices, and I’ll certainly be doing some reviews of them here! I’m allready excited to start using the 16Bugs plan, because good bugtracking was something I was missing in some of my projects..

A big thanks goes out to Ryan Bates for organizing and judging this contest (and picking me as a winner)!

tags: l 1 comments »

As seen in the Commit History: script/dbconsole May 8th, 2008

While going to the recent Rails Git logs, I stumbled across a cool new feature: dbconsole.

We all know and love the Rails console, which gives us an easy interface to our Rails environment, but sometimes we have to be in the database itself.. When this happened, most of the time we had to look up how our database was called, what kind of database it was, basically look at our database.yml file, but no more!

jan:railsproject jan$ script/dbconsole 
SQLite version 3.5.6
Enter ".help" for instructions
sqlite> select * from products;
...

and with easy access to each environment:

jan:railsproject jan$ script/dbconsole production
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 5.0.41 MySQL Community Server (GPL)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql>

How cool is that!

tags: l 3 comments »

Don't be too DRY - Filter Abuse April 29th, 2008

Controller Filters are a great way to add behaviour to your controller which isn’t directly connected to your actions. The Hitchhiker’s Guide to the GalaxyRails Documentation has this to say on the subject of Filters

Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do authentication, caching, or auditing before the intended action is performed. Or to do localization or output compression after the action has been performed. Filters have access to the request, response, and all the instance variables set by other filters in the chain or by the action (in the case of after filters). source

A good example to this is authentication. A controller action’s role isn’t authentication, but you should be sure that whoever reaches that action is authenticated. This can be solved with a simple before_filter

class PostsController < ApplicationController
  before_filter :check_auth, :except => [:show, :index]

  def index
    @posts = Post.all
  end

  def new
    @post = Post.new
  end 

  ...
  private
  def check_auth
    User.authenticate(session[:user]) || redirect_to login_path
  end
end

Lately, however, I’ve seen some very bad use of this functionality. We all like the Dont Repeat Yourself principle, but this shouldn’t interfere with code readability. Actions should be descriptive. When reading an actions code, you should know what it does.

This is bad:

class PostsController < ApplicationController
  before_filter :find_post, :only => [:show, :edit, :update]
  before_filter :all_posts, :only => [:index]
  before_filter :find_comments, :only => [:show]

  def index
  end

  def show
  end

  def edit
  end

  def update
    @post.update_attributes(params[:post)
    redirect_to @post
  end
  ...
  private
  def find_post
    @post = Post.find(params[:id])
  end

  def all_posts
    @posts = Post.find(:all)
  end

  def find_comments
    @comments = Comment.find_by_post_id(params[:id])
  end
end

The reason people use filters this way is to be ultra DRY. You don’t need to do Post.find(params[:id]) 3 times in your controller, and it works this way.

I personally hate this pattern, and everyone who uses it. This code may be DRY, but it’s not readable at all.

When I see an action with nothing in it, and I look at the view template and see @post being used, I have to look back at the controller, look at which before_filters apply, find the methods called, interpret what is in there.

Other than that, it also removes the ability to do effective Action Caching, because action caching will interpret before_filters, thus running the SQL queries the find-methods trigger.

If you have too much code you’d have to replicate, there are other solutions. For example if you had

class PostController
  def show
    @post = Post.find(params[:id], :conditions => {:site_id => current_site.id, :deleted_at => nil}, :include => :comments)
  end

  def edit
    @post = Post.find(params[:id], :conditions => {:site_id => current_site.id, :deleted_at => nil}, :include => :comments)
  end

You should refactor this with a model-action instead of going for a before_filter. This could become

class Post
  named_scope :active, :conditions => {:deleted_at => nil}, :include => :comments
end

class PostController
  def show
    @post = current_site.posts.active.find(params[:id])
  end

  def edit
    @post = current_site.posts.active.find(params[:id])
  end
end

Believe me, readability is way more important then being DRY

tags: l 0 comments »

5 Rails Tips April 25th, 2008

Ryan Bates at railscasts.com has written out a contest where he asks to publish your own Ruby on Rails tips, 5 of them, and let him know about it. I’m keen on the idea of sharing some Rails wisdom with the world, so here are my humble 5 Rails Tips.

using alias_method_chain to improve your models

In Ruby on Rails projects I like to use the Fat Models, Skinny Controllers principle. Sometimes I have models with associations that are quite evident, and where I want it to be very transparent how to set them. For example, I have a Post, which belongs to a category, but I want to be able to create a post like this:

Post.new :title => "Lorem Ipsum", :content => "Lorem Ipsum dolor...", :category => 'Lorem'

And this should create the Lorem category, or use the one which exists. To reach that goal, I use alias_method_chain

class Post < ActiveRecord::Base
  belongs_to :category

  def category_with_name_recognition=(name)
    if name.is_a? Category
      self.category_without_name_recognition = name
    else
      self.category_without_name_recognition = Category.find_or_initialize_by_name(name)
    end
  end

  alias_method_chain :category=, :name_recognition
end

The alias_method_chain function magically aliases category= to category_without_name_recognition, and then replaces category= to link to category_with_name_recognition. That way I can do a call to the old function. For more info on aliasmethodchain, take a look at your local active_support/core_ext/module/aliasing.rb file.

This comes in very handy when category is an autocomplete_field!

everything you always wanted to know about rake tasks, but were too afraid to ask

Everybody knows the default rake tasks (rake db:migrate, rake test), but do you know the next ones:

  • rake db:create:all –> This will create databases for all defined environments in your database.yml file.
  • rake routes –> This will print out all routes your application knows
  • rake db:fixtures:load –> This will load your test fixtures in your current environments database, I use this all the time

To see all rake tasks, you can just run rake --tasks for a list of all tasks with a short description. For a full description of all tasks, run rake --describe

use read_attribute and write_attribute

If you’re overriding an attribute setter or getter in your models, it’s not possible setting or getting these attributes like you normally would, because that would cause an infinite loop. Luckily, there are methods you can use to get and set the attribute. Lets take a User object for example:

class User < ActiveRecord::Base
  ...

  def password=(pass)
    write_attribute(:password, Security.encode(pass))
  end

  def password
    Security.decode(read_attribute(:password))
  end
end

using your own date and time formats

I personally don’t want to be bothered with time formatting, I can read about any format the internet throws at me. Sadly, some of my clients have different, strong opinions on the way time should be formatted on their website. You really don’t want to be bothered with using strftime everywhere. Luckily, Rails has a solution to this problem.

In your environment.rb, you can define your own time formats:

ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
  :client_readable => "%A %B %d at %H:%M", # => Friday April 25 at 11:10
  :client_reports => "%d/%m/%Y at %H:%M"   # => 25/04/2008 at 11:10
)

And then you can use this formatting in your views really easy:

Last updated on <%= @post.updated_at.to_s(:client_readable) %>
# => "Last updated on Friday April 25 at 11:20"

you need plugins!!!

Rails is a great framework, but it doesn’t implement every need of every project. Most of the time however, you’re not the first person to need feature A, or something B, most of the time the feature you want is already in a plugin. These are 5 plugins I can’t live without:

cool Rails 2.1 features

I couldn’t constrain myself to 5 rails tips. Today, and today only, you get a free 6th tip!

If you’re running on Rails Edge (which I do for most projects), or you’re reading this when Rails 2.1 is already out, there are a bunch of cool new features that aren’t documented yet.

render :partial => form
# Before Rails 2.1
<% form_for @client do |form| %>
  <%= render :partial => 'form', :object => form %>
  <%= form.submit "Save" %>
<% end %>

# Now, with sparkly new Rails
<% form_for @client do |form| %>
  <%= render :partial => form %> # Automaticly renders _form.erb 
  <%= form.submit "Save %>
find(:last)
# Before Rails 2.1
@last_post = Post.find(:first, :order => 'created_at DESC')

# with Rails 2.1
@last_post = Post.find(:last, :order => 'created_at')
Easy finding
# Before Rails 2.1
Post.find :all
Post.find :first

# with Rails 2.1, thanks to scoping
Post.all
Post.first
Post.last

These are all small changes that I find particularly handy in my day-to-day work. For the “big” changes, you should read Ryan’s Scraps, he keeps track of all big changes in Rails.

And with that, I’m all out of tips, at least for now. Comments are always appreciated!

tags: l 2 comments »

Testing your Rails Plugins April 24th, 2008

A great feature in Ruby is the integrated testing. Ruby on Rails also implements and extends the Ruby testing framework, and if you’re not testing your Ruby code you should start doing it now!

Normally, when I write tests for a class, I mock out everything not related directly to the class (I do this with mocha). For example, when writing tests for the Mollom gem, I mocked out the whole XMLRPC API, because I can’t rely on them to give me all test cases.

# from http://github.com/DefV/ruby-mollom/tree/master/test/mollom_test.rb
def test_server_list
  xml_rpc = mock
  xml_rpc.expects(:call).
          with('mollom.getServerList', is_a(Hash)).
          returns(['http://172.16.0.1', 'http://172.16.0.2', 'https://172.16.0.2'])

  XMLRPC::Client.stubs(:new).with('xmlrpc.mollom.com', '/1.0').returns(xml_rpc)

  assert_equal [
          {:ip => '172.16.0.1', :proto => 'http'}, 
          {:ip => '172.16.0.2', :proto => 'http'}, 
          {:ip => '172.16.0.2', :proto => 'https'}
         ], @mollom.server_list
end

This way, I’m only testing the behaviour of that specific class, so if something fails somewhere, I can pinpoint the problem, while being sure it’s not a problem in the Mollom API.

While writing a test for some view plugin the other day, I was doing the same thing. I stubbed out everything from ActionView/ActionController which I used (content_tag and url_for and such). Suddenly I realised that wasn’t a good idea.

Rails plugins tests should use the Rails Framework, because the plugins themselves rely on it deeply. To be sure the plugin keeps working while the framework changes, I just have to run my tests, which is exactly what I want.

So I do it this way now:

# from http://github.com/DefV/title_helper/tree/master/test/title_helper_test.rb
require 'test/unit'
require File.dirname(__FILE__) + '/../../../../config/boot.rb'
require File.dirname(__FILE__) + '/../../../../config/environment.rb'

class TitleHelperTest < Test::Unit::TestCase
  def setup
    @helper = ActionView::Base.new
  end

  # Replace this with your real tests.
  def test_title_method_with_no_title_set
    assert_equal "foobar", @helper.title(:site_name => 'foobar')
  end
  ...
end

How do you test your Rails Plugins?

tags: l 0 comments »

Symbols vs Strings April 22nd, 2008

When to use Symbols and when to use Strings in Ruby is really a personal preference. Most of the time I program without taking the difference in performance in consideration.

I just use a few simple rules

in a hash it’s always :key => ‘value’

redirect_to :controller => 'pages', :action => 'index'

special keywords are symbols

Page.find(:all)
Page.find(:first)
redirect_to(:back)

attributes and status’es are symbols

update_attribute(:name, 'Jan')
comment.state = :approved

Except for those rules it’s really what feels right while coding it.

tags: l 1 comments »