11.13.07
ActiveRecord tasklist exercise: adding task state
Last time I used ActiveRecord to persist basic tasks. On the list of features to implement was task state such as “New” or “Complete”.
I wanted to make sure that tasks were used definable and that the relationship at the class level did more than just expose an int that the user had to map to the proper state.
I started by defining the new SQL schema for tasks with states. The schema I created follows:
CREATE TABLE tasks ( `id` INT PRIMARY KEY NOT NULL AUTO_INCREMENT, `title` VARCHAR(255) DEFAULT NULL, `state_id` INT DEFAULT NULL );CREATE TABLE states ( `id` INT PRIMARY KEY NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL );
I did not realize that the name of the state_id column needed to be “state_id”. This makes sense and reveals a bit about how ActiveRecord works without the attribution requirements that other ORM systems have.
Next I added a new class for the State and added a relationship to Task. My thinking was “Each Task has one state. So use has_one.” It looked like:
class Task < ActiveRecord::Base has_one :state end class State < ActiveRecord::Base end
Marginally more experienced people probably already see the problem. The error I got when trying to run was:
Exception: Mysql::Error: Unknown column 'states.task_id' in 'where clause': SELECT * FROM states WHERE (states.task_id = 17) LIMIT 1
Ok - that’s backwards from what I expected. I assumed that since the Task “has_one” State that the ID being looked for would be the state ID, not the task ID. I did some digging and found that belongs_to and has_one are commonly confused cousins. And I found this blog post about it. So I need to change has_one to belongs_to and I need a has_many relationship defined as well.
Now the code looks like:
class Task < ActiveRecord::Base belongs_to :state end class State < ActiveRecord::Base has_many :task end
That worked as expected (I should say “as hoped” since my expectation was that has_one would work). I have to admit I need more time to wrap my noggin around has_one vs. belongs_to.
Now with this in place I was able to write the following code:
state_new = State.create "name" => "New"
state_ip = State.create "name" => "In Progress"
state_complete = State.create "name" => "Complete"Task.create "title" => "Attend Raleigh Ruby Brigade November Meetup", "state" => state_new
Task.create "title" => "Attend mid-day scrum", "state" => state_complete
Task.create "title" => "Add state support to task list", "state" => state_ip
Task.find(:all).each { |t| puts "#{t.title} (#{t.state.name})" }
Which had the following output:
Attend Raleigh Ruby Brigade November Meetup (New) Attend mid-day scrum (Complete) Add state support to task list (In Progress)
Which was exactly what I expected.