Posts Tagged ‘github’

Chores: a test driven website – Part 9 (the lost episode)

Sunday, May 10th, 2009

This is part of the Chores series of posts.

Well it’s been a while since National Testing month and there has been a lot going on both in my world and in the Ruby Community. I’ve been doing a good deal of reading, deployed a Monorail website and been generally busy. Unfortunately, I haven’t focused on building Chores, my test driven chore delivering website.

Today I’ve changed all that by dusting off chores and updating my gems. The first thing that I noticed was that I needed to add some configuration for Webrat for it to function within Cucumber. I needed to add the following to the env.rb file:

Webrat.configure do |config|
  config.mode = :rails
end

Next I see that I’m getting an error on the new action for Children because the @child variable is nil. Let’s write a functional test to fix that.

Here is my first pass at the functional test and controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'test_helper'
class ChildrenCanCrud < ActionController::TestCase
  tests ChildrenController
 
  specify "test that it should respond to index" do
    get :index
    assert_response :success
    assert_not_nil assigns(:children)
  end
 
  specify "that it should pass child on new" do
    get :new
    assert_response :success
    assert_not_nil assigns(:child)
  end
 
  specify "that it should create child" do
    assert_difference 'Child.count' do
      post :create, :child => { :parent => Identity.make, :identity => Identity.make }
    end
 
    assert_redirected_to children_url
  end
 
end

./app/models/child.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Child < ActiveRecord::Base
  attr_accessor :open_identifier
  before_create :set_identity
 
  belongs_to :identity,             :foreign_key => :child_id,
                                    :class_name => 'Identity'
 
  belongs_to :parent,               :foreign_key => :parent_id,
                                    :class_name => 'Identity'
 
  validates_presence_of :open_identifier,  :message => 'can not be blank.',
                                           :on => :create
  validates_presence_of :identity,         :message => 'can not be blank.',
                                           :on => :update
  validates_presence_of :parent,    :message => 'can not be blank.'
 
  def set_identity
    self.identity = Identity.find_or_make_if_valid @open_identifier
  end
end

./app/models/identity.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Identity < ActiveRecord::Base
  include OpenIdAuthentication
 
  validates_presence_of :identifier,        :message => "can not be blank."
 
  validates_uniqueness_of :identifier,      :message => "is already in use, please use another."
 
  validates_length_of :identifier,          :maximum => 100,
                                            :allow_nil => true,
                                            :message => "is too long."
 
  validate_on_create :valid_id
 
  has_many :parents,             :foreign_key => :parent_id,
                                 :class_name => 'Child'
  has_many :children,            :foreign_key => :child_id,
                                 :class_name => 'Child'
 
  def open_id
    return nil if self.identifier.nil?
    self.identifier[7..-2]
  end
 
  def identifier= identifier
    raise "Cannot update identifier." unless new_record?
    set_identifier identifier
  end
 
  def open_id= open_id
    raise "Cannot update open_id." unless new_record?
    set_identifier open_id
  end
 
  def valid_id
    errors.add(:identifier, "is not a valid Open ID.") if @invalid_id
  end
 
  def self.find_by_open_id open_id
    id = OpenIdAuthentication::normalize_identifier(open_id)
    find_by_identifier open_id
  end
 
  def self.find_or_make_if_valid open_id
    begin
      id = self.find_by_open_id Identity.normalize_identifier(open_id)
    rescue OpenIdAuthentication::InvalidOpenId
      return nil
    end
 
    unless id
      id = Identity.new :identifier => open_id
      id.save
    end
    id
  end
 
    def Identity.normalize_identifier id
    begin
      identifier = OpenIdAuthentication::normalize_identifier(id)
    rescue OpenIdAuthentication::InvalidOpenId
      identifier = nil
    end
    identifier
  end
 
  protected
  def normalize_id id
    identifier = Identity.normalize_identifier id
    unless identifier
      @invalid_id = true
      identifier = id
    end
    identifier
  end
 
  def set_identifier id
    write_attribute(:identifier, normalize_id(id))
  end
end

and

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ChildrenController < ApplicationController
  def index
    @children = Child.find(:all)
  end
 
  def new
    @child = Child.new
  end
 
  def create
    @child = Child.new(params[:child])
    if @child.save
      flash[:message] = "Child added."
      redirect_to children_url
    else
      flash[:error] = "Error saving Child."
      render :action =>  "new"
    end
  end
end

update to ./test/unit/child_test.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
require 'test_helper'
 
class ChildShouldBeValidated < ActiveSupport::TestCase
 
  specify "that a parent is required" do
    child = Child.new
    child.open_identifier = valid_open_id
    invalid child
    child.parent = Identity.make
    valid child
  end
 
  specify "that a child is required" do
    child = Child.new :parent => Identity.make
    invalid child
    child.open_identifier = valid_open_id
    valid child
  end
 
  specify "message for a null identity" do
    child = Child.new :parent => Identity.make
    validation_message_for child, :open_identifier, "can not be blank."
  end
 
  specify "message for a null parent" do
    child = Child.new :identity => Identity.make
    validation_message_for child, :parent, "can not be blank."
  end
end
 
class ChildShouldCreateIdentity < ActiveSupport::TestCase
 
  specify "that open_identifier is used to pass the OpenId" do
    child = Child.new :parent => Identity.make
    child.open_identifier = valid_open_id
    assert child.save
  end
end

./test/functional/children_controller_test.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require 'test_helper'
 
class ChildrenCanCrud < ActionController::TestCase
  tests ChildrenController
 
  def setup
    @identity = Identity.make
  end
 
  specify "test that it should respond to index" do
    get :index, nil, build_session_hash_for(@identity)
    assert_response :success
    assert_not_nil assigns(:children)
  end
 
  specify "that it should pass child on new" do
    get :new, nil, build_session_hash_for(@identity)
    assert_response :success
    assert_not_nil assigns(:child)
  end
 
  specify "that it should create child" do
    assert_difference 'Child.count' do
      post :create, {:child => { :open_identifier => "cheese.example.com" }}, build_session_hash_for(@identity)
    end
 
    assert_redirected_to children_url
  end
 
end

./test/test_helper.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require File.expand_path(File.dirname(__FILE__) + "/blueprints")
require 'test_help'
require 'machinist' #if you installed as gem rather than plugin
require 'faker'
 
class ActiveSupport::TestCase
  self.use_transactional_fixtures = true
  self.use_instantiated_fixtures  = false
 
  def invalid model
    assert !model.valid?
  end
 
  def valid model
    assert model.valid?
  end
 
  def validation_message_for model, column, message
    invalid model
    assert_equal message, model.errors.on(column)
  end
 
  def valid_identifier
    "http://test.example.com/"
  end
 
  def build_session_hash_for identity
    @session_hash = {'identity_id' => identity.id}
  end
 
  def valid_open_id
    "test.example.com"
  end
 
  def invalid_open_id
    "bad_id"
  end
 
end
 
def specify *args, &block
  test(*args, &block)
end

.. interestingly enough I find that my view that I wrote at some point expects a field called nickname

1
2
3
4
5
6
7
8
9
<% form_for(@child) do |f| %>
  <%= f.error_messages %>
  <%= f.label :nickname %>
  <%= f.text_field :nickname %>
  <%= f.label :open_identifier %>
  <%= f.text_field :open_identifier %>
  <%= f.submit "Add" %>
 
<% end %>

So I quickly create and run a migration to add the field.

~/working/chores (chores9)$ ruby script/generate migration add_nickname_to_child nickname:string
      exists  db/migrate
      create  db/migrate/20090402191006_add_nickname_to_child.rb

which can should be migrated with.

rake db:migrate

Here is the design I settled on.

As you can see, I’ve pushed it out to github. I’ve tagged it with “post_9″. I am hoping this will help those that want to follow along and reduce the amount of code that I need to post while writing this up. Maybe I’ll write up a post on git and github.

Update: I apologize this is all getting muddled in my brain as well. Between a change in rails versions and the passage of time. I hope to get this all straightened out in the next post.