Archive for July, 2007

July 30th, 2007

Be Careful of those Plurals, and other Rails Relationship Gotchas

by Tim Cull

Ok, I’ve been banging my head against this one for a little while, so I want to make sure to share the solution.

In my Ruby on Rails project, I have a RegisteredUser class and a Person class. They have a one-to-one relationship with each other that I was trying to represent like so:

class Person < ActiveRecord::Base
  has_one :registered_users
end
class RegisteredUser < ActiveRecord::Base
  belongs_to :person
end

see the problem? I didn’t either for a while, but maybe the title of this post will give you a clue. The problem is with the pleurality of my has_one. It should be registered_user instead of registered_users. You really have to watch the plurality of relationships in Rails. If it’s one-to-one, then both sides better be singular. If it’s one-to-many, then the many side better be plural and the one side better be singular. It all makes a difference.

The error message I was getting was simply the not-very-helpful:

test_add_reporter_by_name(PersonControllerTest):
NoMethodError: undefined method `registered_user' for #<person :0x48ff2c4>
    d:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.1/lib/active_record/base.rb:1848:in `method_missing'
    D:/dev/rails/mystats/config/../app/controllers/person_controller.rb:54:in `add_reporter_by_name'
    d:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/action_controller/base.rb:1095:in `send'
    d:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/action_controller/base.rb:1095:in `perform_action_without_filters'
...snip...
    d:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/action_controller/test_process.rb:353:in `post'
    test/functional/person_controller_test.rb:79:in `test_add_reporter_by_name'

One other gotcha I ran into…if you have a has_and_belongs_to_many relationship (ie. a many-to-many) then you need to make sure your join table in between does not have an identity column. So, in my case I have another many-to-many relationship between RegisteredUser and Person that I call “authorized reporters”. This relationship says which RegisteredUsers are allowed to report a status for which People:

  has_and_belongs_to_many :authorized_reporters, :class_name =>'RegisteredUser', :join_table => 'authorized_reporters'

In between I have a join table called authorized_reporters:

class CreateAuthorizedReporters < ActiveRecord::Migration
  def self.up
    create_table :authorized_reporters, :id => false do |t|
      t.column :registered_user_id, :integer, :null => false
      t.column :person_id, :integer, :null => false
    end
  end
  def self.down
    drop_table :authorized_reporters
  end
end

There is one very important part of that migration: “, :id => false”. Without it, the many-to-many works in one direction, but not in the other (I don’t remember which direction works).

July 16th, 2007

It really does exist

by Tim Cull

I’ve finally pushed my Ruby on Rails project into production. The first thing I learned from this experience was that I have a lot to learn about pushing Rails projects to production. The documentation on this subject could use some help; maybe this is a good opportunity for someone to write a book!

I’ve got a lot more work to do on it, for sure. Not the least of which is thinking of a name that is less lame than “mystats”. Please, please send me a suggestion (or post a comment). I’m horrible at naming things.

July 13th, 2007

Once a Programmer not Always a Programmer

by Tim Cull

I was glad to see this post by Rob describing why he was so frustrated trying to stay out of management. I’ve had the same experience in spades.

Why is it that some companies don’t seem to have a good track for programmers to follow? Often, the only way to get “promoted” is to take on management responsibility, but the skills required to be a good coder are sometimes at odds with those required to be a good manager. So why not have a technical path towards recognition?

My biggest frustration when I am working as a technologist (as opposed to a manager, and I do often flip back and forth) is that fact that many organizations often play lip service to research, innovation and cross-pollination, but then don’t provide the time in project schedules to let technologists innovate, or work on project outside the company (like open source projects) or explore what other groups in the same company are doing. It’s all about “do what you have to do to get this project done right now.” I think removing some of those barriers could go a long way towards making technologists more happy.

July 3rd, 2007

More Rails Test Links

by Tim Cull

Thought I’d add these two links for reference:
A list of available assertions in Ruby
A list of available assertions in Rails

July 3rd, 2007

Rails Unit Testing is Great, Once You Figure it Out

by Tim Cull

After a couple of months away from it, I’ve picked up my Ruby on Rails project again. The first thing I wanted to do was make an honest developer of myself and make my build pass–up until now I’d been allowing myself to think of my project as experimental and was cutting a lot of corners just to figure things out.

First, let me state that the unit testing that’s built into Ruby and into Rails is exceptional. Without having to even think about it, the default build target will run your unit tests, functional tests, and insert and tear down data fixtures lickety split. After going to a session by Kevin Clark on unit testing in Rails at SD West, I was even farther ahead.

My first problem, though, was the point at which I wanted to do something slightly unusual like check for the presence or absence of a variable in the session. Now, I’ve been doing most of my development offline in coffee shops or on the bus, so I didn’t have access to excellent Rails unit testing tutorials online. I was on my own.

I did, however, have this skeletal unit test from the SD West session:

require File.dirname(__FILE__) + '/../test_helper'
require 'posts_controller'

class PostsController; def rescue_action(e) raise e end; end

class PostsControllerTest < Test::Unit::TestCase
  fixtures :posts

  def setup
    @controller = PostsController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  def test_show
    get :show, :id => posts(:ruby).id
    assert_response :success
    assert_equal posts(:ruby), assigns(:post)  # assigns looks at instance variables inside the controller
    assert_match( /#{posts(:ruby).title}/, @response.body)
  end

  def test_create
    startcount = Post.count
    post :create, :post => {:title => 'Hi SD West', :body => 'Here I am'}
    endcount = Post.count
    assert_equal 1, endcount - startcount
    assert_redirected_to :action => 'show', :id => assigns(:post)
  end
end

Here, the assigns hash will contain any instance variables I set in the controller. I figured, hell, there must be a session hash, too. So I tried this:

require File.dirname(__FILE__) + '/../test_helper'
require 'home_controller'

class HomeController; def rescue_action(e) raise e end; end

class HomeControllerTest < Test::Unit::TestCase
  fixtures :registered_users

  def setup
    @controller = HomeController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new

    @first_user = registered_users(:one)
  end

  def test_list
    #should fail if there's no registered user in the session
    get :list
    assert_response :success
    assert_template 'login'
    assert_nil session[:registered_user]

  end

end

Nope, didn't work. After much fumbling and messing around, I finally discovered that the session lives in that mysterious @response variable, as below. And this whole exercise has exposed to me my greatest frustration with Ruby (and other dynamic languages like Python, PHP, etc): if it's not strongly typed, then your IDE can't do much to help you. If I had been working in Java, I could have typed "super.<ctrl -space>" or "@response.<ctrl -space>" or etc. in Eclipse and seen immediately what the available methods and fields were. I didn't realize before just exactly how reliant I'd become on that before I started using more dynamic languages this last few years. I guess you can't have your cake and eat it, too.

For posterity, here is the real solution:

require File.dirname(__FILE__) + '/../test_helper'
require 'home_controller'

class HomeController; def rescue_action(e) raise e end; end

class HomeControllerTest < Test::Unit::TestCase
  fixtures :registered_users

  def setup
    @controller = HomeController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new

    @first_user = registered_users(:one)
  end

  def test_list
    #should fail if there's no registered user in the session
    get :list
    assert_response :success
    assert_template 'login'
    assert_nil @response.session[:registered_user]

  end

end