More on form_for(@non_arb)

•March 14, 2008 • 1 Comment

My previous post talked about how to make form_for(@non_arb) work. When we need it for multiple classes, we came up with a little bit meta-programming so that classes that needs such ability can simply say:

polymorphic_routes_for :person,
:update_path => :admin_person_path,
:create_path => :admin_people_path

As you can see, it also takes options, update_path and create_path, if you don’t specify the paths, it will infer from the first param, which would make update path to person_path and create path to people_path.

The magic behind the scene is in the following, it can be in a module that you can mix in, or in our situation, in a superclass, Presenter where all presenters inherit from.

class Presenter
  class << self
    def polymorphic_routes_for(symbol, options = {})
      self.class_eval "def id; #{symbol}.id; end"
      self.class_eval "def to_param; #{symbol}.to_param; end"
      self.class_eval "def new_record?; #{symbol}.new_record?; end"
      update_path = options&#91;:update_path&#93; || "#{symbol}_path"
      ActionView::Base.send :alias_method, "#{self.name.underscore}_path", update_path

      create_path = options&#91;:create_path&#93; || "#{symbol.to_s.pluralize}_path"
      ActionView::Base.send :alias_method, "#{self.name.underscore.pluralize}_path", create_path
    end

  end

&#91;/sourcecode&#93;

and here is the specs for it:
&#91;sourcecode language="ruby"&#93;
describe Presenter, "when specifying the polymorphic routes for a model" do
  before :each do
    ActionView::Base.class_eval("def test_model_path;end")
    ActionView::Base.class_eval("def test_models_path;end")
    class A < Presenter
      polymorphic_routes_for :test_model
      attr_reader :test_model
    end

    @presenter = A.new :test_model => mock("Model")
  end

  it "should generate id method" do
    @presenter.test_model.should_receive(:id).and_return "id"
    @presenter.id.should == "id"
  end

  it "should generate to_param method" do
    @presenter.test_model.should_receive(:to_param).and_return "to param"
    @presenter.to_param.should == "to param"
  end

  it "should generate new_record? method" do
    @presenter.test_model.should_receive(:new_record?).and_return "new record"
    @presenter.new_record?.should == "new record"
  end

  it "should alias update named route method to ActionView::Base" do
    ActionView::Base.new.should respond_to(:a_path)
  end

  it "should alias create named route method to ActionView::Base" do
    ActionView::Base.new.should respond_to(:as_path)
  end
end

describe Presenter, "when specifying the polymorphic routes for a model and specifying update_path option" do
  before :each do
    ActionView::Base.class_eval("def test_update_path; 'customized update path'; end")
    ActionView::Base.class_eval("def test_models_path;end")
    
    class A < Presenter
      polymorphic_routes_for :test_model, :update_path => :test_update_path
      attr_reader :test_model
    end

    @presenter = A.new :test_model => mock("Model")
  end

  it "should alias update named route method given by update_path option to ActionView::Base" do
    ActionView::Base.new.a_path.should == 'customized update path'
  end

end

describe Presenter, "when specifying the polymorphic routes for a model and specifying create_path option" do
  before :each do
    ActionView::Base.class_eval("def test_model_path; end")
    ActionView::Base.class_eval("def test_create_path; 'customized create path'; end")

    class A < Presenter
      polymorphic_routes_for :test_model, :create_path => :test_create_path
      attr_reader :test_model
    end

    @presenter = A.new :test_model => mock("Model")
  end

  it "should alias create named route method given by update_path option to ActionView::Base" do
    ActionView::Base.new.as_path.should == 'customized create path'
  end

end
Advertisements

form_for(@non_arb)

•March 3, 2008 • 1 Comment

In an erb template, you can have your form written as:

form_for(@arb)

where @arb is a ActiveRecord::Base object. form_for will smartly realize which url to generate based-on @arb’s state. For example, if I have

form_for(@person)

in the template and @person is a new record, the form_for helper will correctly infer that this form is for creating, thus generates the form submission url as people_path with POST. And if @person is not a new record, the helper will generate the url as person_path(@person) with a “PUT”. Benefits you can get from this are less code, and you can even merge new and edit forms all together.

In one instance, we want to use Jay Fields’ Presenter Pattern. So instead of @person, we use @person_presenter, and we still want to keep the simple syntax on the view, like form_for(@person_presenter). To make it work, we need to do the followings:

In your PersonPresenter class, we need to forward new_record? and to_param to person, something like this, or just use Ruby’s Forwardable.

def new_record?
person.new_record?
end

def to_param
person.to_param
end

Now the form_for method will generate person_presenters_path and person_presenter_path respectively. So we need to add two routes in routes.rb:


map.person_presenters 'people', :controller => 'people', :action => 'create', :conditions => { :method => :post }
map.person_presenter 'people/:id', :controller => 'people', :action => 'update', :conditions => { :method => :put }

Now the system works just fine.

Less is Better

•February 25, 2008 • 2 Comments

A great blog post by Szczepan Faber talked about 10 rules of unit testing. In this post, I want to discuss on the second rule in his post, which is: “It’s not only about test code but I need to say that anyway: Best improvements are those that remove code. Be budget-driven. Improve by removing. The only thing better than simple code is no code.”

To me, it means one fundamental thinking in coding in general, that is, “do the simplest thing that could possibly work”. I also refer this rule as Occam’s Razor.

This rule can apply to Rails migrations strategy as well. If your application is already in production, then you have no choice but using migrations. But if it’s still in development, I always avoid to add a migration script for any tiny change to my schema, instead, I would just change existing migration scripts.

So the rule for doing migration becomes: for each table, or tables associated by their function areas gets their own migration. To make changes, modify your scripts. For example, if I change column name from user_id to login_id, I will just change my script from

t.integer :user_id

to

t.integer :login_id

The downside of this approach is that each time you change it, you need to do a migrate:reset. But since it’s in development, who cares.

The gain is big, by doing this, my migrate directory always keeps small and clean. And this also solves a problem Obie Fernanandez mentioned in his great book “The Rails Way” which is, in a large team, developers can step into each other’s toes by having conflict sequence numbers, with huge number of migrations, that could be a nightmare. But if we just change scripts themselves, all we need to do is merging the same file, instead of renaming a lot of files.

You don’t have to find a mechanism to archive your migrations because you don’t need to archive.

As long as I can use this rule to achieve the same functionality that full migration approach offers, then it is a simpler way and, thus, better.

to_yaml_properties

•February 20, 2008 • 2 Comments

There are just so many little secrets in Ruby. Today we found one. We have a class and we want to be able to serialize it using to_yaml, but we just want to store some, not all, instance variables. After 10 minutes digging out, we found all we need is overwrite to_yaml_properties method, made it return an array of instance variables we want. Like this:


def to_yaml_properties
["@attr1", "@attr2"]
end

Returning Gracefully

•February 14, 2008 • Leave a Comment

I just learned from Marcel Molina a graceful way to return an object from a method. In old days, I often have to write a method like this:

   def born(params={})
     person = Person.new params
     person.do_something
     person.save!
     person
  end

The last line bothered me a lot, it looks ugly.

But just as usual, Rails addresses it, that is method returning. With this method, now I can modify my previous example to the following:

   def born(params={})
     returning Person.new(params) do |person|
       person.do_something
       person.save!
     end
  end

Basically what this method does is return the argument you give to the returning method, AFTER processing it further in the block. Simply beautiful.

For your convenience, here’s a link to the documentation. By the way it’s also very interesting to see what else rails offers in that class. They could come in handy sometimes. Here’s a link to an explanation of K Combinator. You can find tons of related links through our old dear friend google.

The best validation ever

•January 7, 2008 • Leave a Comment

My first name is Yi and I tried to register to a website the other night, and here is what I got:

There were problems with the following fields:

    * First name is too short (minimum is 3 characters)

Riding Rails on IntelliJ

•January 1, 2008 • 1 Comment

Textmate seems the “official” IDE for rails projects. But we have to develop under Windows, so no Textmate.

We used eclipse with radrails (Aptana) for a while, believe me, it’s not fun. Then we start to try IntelliJ now.

Ctrl+Shift+Alt+Space in IntelliJ gives you “word completion”, just like “Esc” in Textmate. And Ctrl+Space works for local variables nicely. And it even works for instance variables, but it needs long time to think. Ctrl+Space works partially in Eclipse, but not in a RSpec file where no explicit class declarations.

Global search on Textmate is a pain, especially if you forget to delete huge development.log and test.log. So I just grep extensively when on Macs. In IntelliJ, you can specify which directory to search with, this helps a lot. Even better, when you have to search the whole project, it will ask you if you want to skip development.log/test.log, very intelligent.

The amazing thing is that renaming classes name and methods name work perfectly in IntelliJ. This feature alone will save me countless time and energy.

Subversion support in Texmate is not so nice, I would just use terminal for subversion operations. In both Eclipse and IntelliJ, the supports are just awesome.

When you have conflicting files, the diff support in Textmate is primitive. And there is no good files/directories comparison tool under MacOS. Meld diff viewer is awesome, but it may take some effort to install it on Macs.

The biggest problem with IntelliJ is that it’s not very responsive, compared to Textmate. But it’s definitely faster than Eclipse.

We use Watir as our scenario test tool, so moving away from Windows doesn’t seem quite possible. This being said, it seems IntelliJ is our best bet.