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.
