Category Archives: Enterprise Software

Work and non-work related coding. Firefox and Thunderbird for the enterprise.

belong_to RSpec matcher

I was extending acts_as_commentable and needed a good RSpec test to check the returned objects from its finder methods belonged to the correct user. For example, Comment.find_comments_by_user( :some_user ) should all belong_to :some_user. I’ll be darned if that doesn’t look like a RSpec description. Since there is no all_belong_to matcher, I wrote one.

module ActiveRecordValidations
  class BelongTo
    def initialize(expected)
      @expected = expected
    end
 
    def matches?(args)
      args.all? do |target|
        @target = target
        @target.send(@expected.class.to_s.downcase) == @expected
      end
    end
 
    def failure_message
      "expected #{@target.inspect} to all belong to #{@expected}"
    end
 
    def negative_failure_message
      "expected #{@target.inspect} not to all belong to #{@expected}"
    end
  end
 
  def belong_to(expected)
    BelongTo.new( [expected] )
  end
 
  def all_belong_to(expected)
    BelongTo.new( expected )
  end
end

The matches? method takes an array of objects and goes through them with all? checking that they have a belongs_to the expected thing. Using the example above, each comment object returned by Comment.find_comments_by_user would get tested if comment.user== @user.

–Dean

Concise Signup & Signin Pages

Login, Signup

We are presented with these quick forms all the time. While it is easy to create standard login and signup pages, Amazon.com has a good one:

Amazon Sigin Image

What makes it good?

First of all, the prompts are written in plain English. Amazon sells to a wide slice of the population, meaning that about 15% of their customers are probably not very technology-saavy. (-2?) Anything they can do to ease the operation helps their customers buy.

Secondly, when a user visits Amazon.com, there is only one link: “Your account” instead of separate login and signup links. Simple is generally better.

Also note the standard “forgot your password” link plus an additional “has your email address changed?” question. Both are useful to have close at hand.

Implementation

Rails 2.0 strongly encourages you to design RESTful applications. Login forms are associated with Session objects, while signup forms go with User objects (rather, Brewer objects in our case). A simple redirect in the SessionsController#create method takes care of pointing a user in the right direction.

class SessionsController < ApplicationController
  def create
    if params[:signin_action] == 'new_user'
      redirect_to new_brewer_path( :brewer => {:email => params[:email]} )
    else
      # Do sigin stuff
    end
  end
end

Note that we pass params[:email] to the new_brewer_path so that field is automatically populated on the next page. If you are using the generated scaffold, you will have to change your BrewersController#new method to instantiate a new @brewer object:

class BrewersController < ApplicationController
  def new
    @brewer = Brewer.new(params[:brewer])
  end
end

Lastly, here is the extra test:

class SessionsControllerTest < Test::Unit::TestCase
  def test_should_redirect_to_new_brewer_if_asked
    an_email = "dean@brewsession.com"
    post :create, :email => an_email, :signin_action => 'new_user'
    assert_redirected_to new_brewer_path(:brewer => {:email => an_email} )
    assert_nil session[:brewer_id]
  end
end

In a later post I will talk about how to implement the change password action in a RESTful way.

–Dean

Could be an Obstacle

Hi Reader,

I am going to tell you the truth. I was not looking forward to writing the javascript necessary to get BrewSession going. For one thing I am a stickler for strict separation of behavior and content. Cascading Style Sheets are great because they keep the page content from getting lost in the necessary formatting to make a pretty-looking page – there is no equivalent for keeping javascript from obfuscating the stuff on your page.

I have a working prototype for keeping javascript off the page, but I feel like it is just one more thing I need to finish before I can get to the real programming. MochiKit is nice to program with, but laying the foundation for a cool sparkley web site was dragging on.

Enter the Google Web Toolkit. It “…is an open source Java software development framework that makes writing AJAX applications … easy for developers who don’t speak browser quirks as a second language.” Using swing-like widgets and panels, you can quickly put together a functioning app. I do not know swing or even much java, however that is not the obstacle to which I alluded in this post’s title.

I’ve been steaming out a mini-app for … let’s call them a client. It’s quite easy and I’m pleased with the toolkit. I can already see the pieces working together for BrewSession. Now round-about to the problem. It is not currently well-integrated with Rails.

Allow me to geek out on you. Rails is an MVC architecture with great M and C tools and decent V tools. The community believes in doing it yourself, leaning towards being elitist about it. I already have a great start to developing BrewSession into a full-fledged REST service thanks to Rails. I still love working with ruby and am going to keep rails on the server side. All I need to do is get BrewSession, written in GWT, to consume the REST service.

Searching the google group for the toolkit brings up a whole lotta nothing, so it looks like I have to do it myself. The way things are going I will someday refer to myself as a programmer rather than a sys admin.

–Dean

Hey, my first rails plugin

Ruby on Rails allows for plugins to extend its functionality. My first attempt at one is nothing special but I am going to blog about it anyway 😀

I needed a way to check if a number is within a range before saving a record to the database. Rails has a number of good validations built in, but not one that does that. In two or three hours, I was able to piece together a simple plugin that adds “validates_range_of” to my AR models. The code is a little ugly until I can get Greg to install a syntax highlighting plugin for this blog.

module ActiveRecord
module Validations
module ClassMethods
def validates_range_of(*attrs)
options = {  :on => :save  }
options.update(attrs.pop) if attrs.last.is_a?(Hash)
attrs.flatten!
unless options[:message]
if options[:maximum] && options[:minimum]
options[:message] = 'must be betweeen ' + options[:minimum].to_s + ' and ' + options[:maximum].to_s
elsif options[:maximum]
options[:message] = 'must be less than or equal to ' + options[:maximum].to_s
elsif options[:minimum]
options[:message] = 'must be greater than or equal to ' + options[:minimum].to_s
else
raise ArgumentError, 'Range unspecified.  Specify the :maximum and/or :minimum.'
end
end
 
validates_numericality_of( attrs, options )
 
validates_each(attrs, options) do |record, attr_name, value|
if ((!options[:maximum].nil?) && (!options[:minimum].nil?)) &&
(value > options[:maximum] || value < options[:minimum])
record.errors.add( attr_name, options[:message] )
elsif (!options[:maximum].nil?) && value > options[:maximum]
record.errors.add( attr_name, options[:message] )
elsif (!options[:minimum].nil?) && value < options[:minimum]
record.errors.add( attr_name, options[:message] )
end
end
end
end
end
end

As you can see this plugin extends AR::Validations. So my models can use validates_range_of to check minimum and maximum values. Let’s look at the BJCP Scoresheet I needed it for:

class BjcpScoresheet < ActiveRecord::Base
belongs_to :brew_session
validates_associated :brew_session
validates_range_of :brew_session_id, { :minimum => 1, :message => 'does not belong to a brew session' }
validates_range_of :aroma_score, { :minimum => 1, :maximum => 12, :only_integer => true }
validates_range_of :appearance_score, { :minimum => 1, :maximum => 3, :only_integer => true }
validates_range_of :flavor_score, { :minimum => 1, :maximum => 20, :only_integer => true }
validates_range_of :mouthfeel_score, { :minimum => 1, :maximum => 10, :only_integer => true }
validates_range_of :impression_score, { :minimum => 1, :maximum => 10, :only_integer => true }

How nice – reusable user input validation.

–Dean