Posts Tagged ‘ruby on rails’

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.

Configuring gems for your rails app

Friday, May 8th, 2009

Until I found this (recently), I have been guilty of not configuring my gems for my rails apps.  Apparently these features have been around for a while and can be really helpful when deploying or doing team development with rails and gems.

What it does

  • Requires gems automatically
  • Documents the gems you used
  • Allows you to specify gems by environment.   This is key so you don’t have to require testing gems for a production environment.
  • Provides rake tasks for freezing,  installing and checking the status of the required gems.
  • helps to ensure that functionality you depend on gems for will be working.

Rake Tasks

Here is a listing of gem related rake tasks.

 rake gems                             # List the gems that this rails application depends on
 rake gems:build                       # Build any native extensions for unpacked gems
 rake gems:install                     # Installs all required gems.
 rake gems:refresh_specs               # Regenerate gem specifications in correct format.
 rake gems:unpack                      # Unpacks all required gems into vendor/gems.
 rake gems:unpack:dependencies         # Unpacks all required gems and their dependencies into vendor/gems.
 rake rails:freeze:gems                # Lock this application to the current gems (by unpacking them into vendor/rails)
 rake rails:unfreeze                   # Unlock this application from freeze of gems or edge and return to a fluid use of system gems
  • rake gems:unpack variants are helpful to make sure that you deploy the same gems from your development environment to your production environment, even if it’s on a shared host.
  • rake rails:freeze:gems does the same for the rails gems.
  • A simple rake gems will give you an overview of your gems like this:
rubyyot@atlas:$ rake gems
(in /home/rubyyot/working/examples)
 - [F] ruby-openid
 - [F] paperclip
 - [I] RedCloth
 - [F] sqlite3-ruby
 - [F] ruby-openid >= 2.0.4
I = Installed
F = Frozen
R = Framework (loaded before rails starts)

Example

Here are my configured gems from a project I’m currently working on.

config/enviroment.rb
  config.gem "ruby-openid", :lib => 'openid'
  config.gem "paperclip"
  config.gem "RedCloth", :lib => 'redcloth'
config/environments/development.rb
  config.gem "sqlite3-ruby", :lib => "sqlite3"
config/environments/test.rb
 config.gem "notahat-machinist", :lib > 'machinist', :version => '0.3.1'
 config.gem "faker", :version => '0.3.1'
 config.gem "sqlite3-ruby", :lib => "sqlite3"
config/environments/production.rb
config.gem "mysql", :lib => "mysql"

More

If you are interested, there is a Railscast episode on this topic.

GTD on Rails with Annotations

Thursday, May 7th, 2009

You are in the zone.

Beautiful code is flowing from your fingertips and forming on screen, resolving into a true work of art.

Your tests cover just the right amount of your code, and the few tests that don’t pass are failures rather than errors; regardless they are corrected within seconds.

The design has taken on a life of it’s own, and it is spreading tendrils like wisps of smoke outwards in all directions.

It’s clear that this project will be ready for it’s first release ahead of schedule, when it happens.

Your thoughts diverge in multiple directions.  You cannot decide which task to persue.  You choose to complete the code that will make your current failing test pass.  But, those other things were pretty important too.  You freeze.  Concentration is lost.  It all falls apart.

Recently I found some rails rake tasks that will help in this situation or anywhere else that you want to place a marker to come back to later; perfect for code reviews as well.

rake notes                                # Enumerate all annotations                                                                                       
rake notes:custom                         # Enumerate a custom annotation, specify with ANNOTATION=WTFHAX                                                   
rake notes:fixme                          # Enumerate all FIXME annotations                                                                                 
rake notes:optimize                       # Enumerate all OPTIMIZE annotations                                                                              
rake notes:todo                           # Enumerate all TODO annotations

simply mark the code you are intersted in remembering such as the following

class ExampleController
  def new
    @design = Design.new
    #TODO wookie
  end

  def create
    #OPTIMIZE this needs help
    #FALAFEL yummy in sandwiches.
    @design = Design.new(params[:design])
    @design.profile = Identity.find(current_identity).profile

    #FIXME This was supposed to support xml!
    if @design.save
      flash[:message] = "Design added."
      redirect_to design_url(@design)
    else
      flash[:error] = "Error saving Design."
      render :action => :new
    end
  end
end

when you run rake notes you get

rubyyot@atlas:$ rake notes
(in /home/rubyyot/working/example)
app/controllers/example_controller.rb:
  * [ 10] [TODO] wookie
  * [ 14] [OPTIMIZE] this needs help
  * [ 19] [FIXME] This was supposed to support xml!

Notice you get the exact location of the note along with the type and the text of the note.

Alternatively you could use custom tags with this like this.

rubyyot@atlas:$ rake notes:custom ANNOTATION=FALAFEL
(in /home/rubyyot/working/example)
app/controllers/example_controller.rb:
  * [ 15] yummy in sandwiches.

Sqlite3-ruby gem not working on Windows

Tuesday, May 5th, 2009

If sqlite3 is having problems building native extensions on Windows, uninstall your current version of the sqlite3-ruby gem.

gem uninstall sqlite3-ruby

and install version 1.2.3

gem install sqlite3-ruby -v1.2.3

Rockstar Coder T-Shirt

Saturday, May 2nd, 2009
Image from back of shirt

Image from back of shirt

I recently reserved the domain KungFuTees.com to work with my kids to make a variety of T-Shirts that are fun, interesting and, ahem “hep”.  So far the project has been a lot of fun and it has been great working together with my kids on a project.  The T-Shirts are printed up by zazzle and it’s been a great opportunity to practice and learn our drawing and image manipulation skills.

Currently the project is still in it’s infancy, but I wanted to make this shirt I thought of today while out walking.

Are you a Rockstar?

Let people know it!  The front of this shirt is blank and the back is a faux Nutritional Facts label similar to those you might find on any packaged food product.

Ideas?

Did I get the image wrong?  Can you think of an ingredient that you would rather wear?  Let me know!