Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker

Monthly Archives: May 2011

Pivotal Labs

Creating user-friendly validation messages with the Money gem

Pivotal Labs
Monday, May 23, 2011

Most of the apps that I work on involve dealing with money in some form. I’m a big fan of the Money gem, which allows you to
store currency values in cents in an integer column in the database, then turn it into a easy-to-user Money object.
One problem I have with the Money gem is that when it converts strings to a Money object it doesn’t store the original
value, which makes it hard to show friendly validation messages to users.

In this post I’ll explain how you can make the Money gem a bit friendlier to use. The story goes something like this:

As a customer
When I enter "$21.045" in to a money form field
I want to see a validation error saying it's an invalid amount
And I want to see "$21.045" in the form field
Because my credit card cannot be charged fractional cents
And I most likely made an error

Extend money

First, extend money and add a new field to store the original value:

# config/initializers/money_ext.rb

Money.class_eval do
  attr_accessor :original_value
end

Configure the ActiveRecord objects

Then, craft a composed_of declaration that stores the original value when it’s being set, which can be used by ActiveRecord objects:

class Product < ActiveRecord::Base
  composed_of :price,
              :class_name => "Money",
              :mapping => ["price_in_cents", "cents"],
              :converter => proc { |value|
                money = value.to_money
                money.original_value = value
                money
              }
end

Now when you assign a price to a Product, you can access its original value:

Product.new(:price => "$21.567").price.original_value # => "$21.567"

If you need this in multiple models, you can easily extract it to a module:

module SmartMoney
  def smart_money(column)
    composed_of column,
                :class_name => "Money",
                :mapping => ["#{column}_in_cents", "cents"],
                :converter => proc { |value|
                  money = value.to_money
                  money.original_value = value
                  money
                }
  end
end

class Product < ActiveRecord::Base
  extend SmartMoney
  smart_money :price
end

Expose the original value in forms

To show the users the original value in their forms, you can create a custom form builder for your app, and add a new
money_field method that will do the right thing, like so:

# app/helpers/my_custom_form_builder.rb

class MyCustomFormBuilder < ActionView::Helpers::FormBuilder
  def money_field(method, options = {})
    value = @object.send(method)
    formatted_value = value.original_value.presence || value.format
    text_field method, options.merge(:value => (formatted_value))
  end
end

# config/initializers/default_form_builder.rb

ActionView::Base.default_form_builder = MyCustomFormBuilder

The money_field method first checks for the presence of an original_value and shows it if it’s there, then
defaults to the format method if original_value is not present. You can now use this money_field like any other
form helper:

# in any view

<%= form_for @product do |f| %>
  <%= f.money_field :price %>
<% end %>

Add validations

Now that the Money object, the model and the view are configured properly, you can add custom validations that can access the
original value that the user entered. This Rails 3 validator is an example of one that only allows user input with up to 2 decimal places:

# app/validators/whole_cent_validator.rb

class WholeCentValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    _, cents = value.original_value.to_s.gsub(/[^0-9.]/, '').split(".")
    if cents && (cents.length > 2)
      record.errors[attribute] << (options[:message] || "must be a valid dollar value")
    end
  end
end

You can add this to the Product class like so:

class Product < ActiveRecord::Base
    validates :price,
              :whole_cent => {
                :message => "must be a valid dollar amount between $1.00 and $10,000.00"
              }
end

Summary

Even though it involves extending Money, adding custom form builder methods, creating custom validations and crafting
a non-standard composed_of declaration, it’s relatively simple to add user-friendly validations to Money fields in such
a way that it’s easy to use for all of your money fields app-wide.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Creating strongly-typed, app-wide, user-editable settings

Pivotal Labs
Sunday, May 22, 2011

I recently worked on an app where an admin user needed to be able to tweak an app-wide configuration settings. For example the default title for HTML pages, or the default commission for newly hired sales people. Some settings were text values, some dates, some numbers, and all had different validations.

In this post I’ll explain how you can easily solve this problem using unique STI (Single-Table-Inheritance).

Create the base model / migration

class ApplicationSetting
end

class CreateApplicationSettings < ActiveRecord::Migration
  def self.up
    create_table :application_settings do |t|
      t.string :type, :null => false
      t.string :value
    end

    add_index :application_settings, :type, :unique => true
  end
end

Notice that the type column, which indicates that the ActiveRecord model should use Single-Table-Inheritance, has a
unique constraint. That means that you can only have 1 row per type, which means that there will only ever be a single
instance of a given settings class.

The models

Let’s say the first application setting is the default page title, which is a String.

# app/models/application_settings/default_page_title.rb

class ApplicationSettings::DefaultPageTitle < ApplicationSetting
  validates :title, :presence => true, :length => {:maximum => 50}

  def self.get
    first || create!(:title => "Welcome to acme.com")
  end

  def title() value end
  def title=(value) self.value = value end

end

There are a few noteworthy concepts in this model. The first is that it aliases the value column to be something more
descriptive (title). The second is that there
is a validation on the aliased method, which means that you’ll get a more friendly error message like ‘Title can’t be blank’,
as opposed to ‘Value can’t be blank’.
Finally, there is a get method, that ensures that if no record exists in the database, one is created with a
sort of meta-default value. This comes in very handy when creating forms and working with the controllers.

Next let’s store an integer value, representing the default commission percentage for newly-hired salespeople:

class ApplicationSettings::DefaultCommission < ApplicationSetting
  DEFAULT_PERCENTAGE = 10

  validates :percentage, :numericality => {
    :less_than_or_equal_to => 100,
    :greater_than_or_equal_to => 0
  }

  def self.get
    first || create!(:percentage => DEFAULT_PERCENTAGE)
  end

  def percentage() value.to_i end
  def percentage=(value) self.value = value.to_i.to_s end

end

Note how by defining a setting-specific getter (percentage) and setter (percent=) it’s easy to store all values as
strings and then coerce the value into
something that can be more easily handled by rails view helpers and validations.

You may want to store each value in a different strongly-typed column in the database (like string_value, int_value, date_value etc…) and let rails handle the type casting,
and if you did, your code would be virtually identical:

class CreateApplicationSettings < ActiveRecord::Migration
  def self.up
    create_table :application_settings do |t|
      t.string :type, :null => false
      t.string :string_value
      t.integer :int_value
      t.date :date_value
      # etc...
    end

    add_index :application_settings, :type, :unique => true
  end
end

class ApplicationSettings::DefaultCommission < ApplicationSetting
  DEFAULT_PERCENTAGE = 10

  validates :percentage, :numericality => {
    :less_than_or_equal_to => 100,
    :greater_than_or_equal_to => 0
  }

  def self.get
    first || create!(:percentage => DEFAULT_PERCENTAGE)
  end

  def percentage() int_value end
  def percentage=(value) self.int_value = value end
end

The routes

As far as the routes go, you could map to a different controller for each settings class, or map to a single controller with custom actions – depends on your preference.
Here’s an example of mapping everything to one controller:

# config/routes.rb

namespace :admin do
  resources :application_settings do
    collection do
      put :default_page_title # => PUT /admin/application_settings/default_page_title
      put :default_commission
    end
  end
end

The view

# app/views/admin/application_settings/index.html.erb

<%= form_for ApplicationSettings::DefaultPageTitle.get, :url => default_page_title_admin_application_settings_path(@default_page_title), :as => :setting do |f| %>
  <%= f.error_messages %>
  <%= f.label :title %>
  <%= f.text_field :title, :size => 50, :maxlength => 50 %>
  <%= f.submit "Save" %>
<% end %>

<%= form_for ApplicationSettings::DefaultCommission.get, :url => default_commission_admin_application_settings_path(@default_commission), :as => :setting do |f| %>
  <%= f.error_messages %>
  <%= f.label :percentage %>
  <%= f.text_field :percentage, :size => 3, :maxlength => 3 %>
  <%= f.submit "Save" %>
<% end %>

From the form’s point of view, each of these objects is completely separate. Notice the :as => :setting option in form_for.
This ensures that when the params get to the controller, they can be accessed with params[:setting], as opposed to
params[:application_setting_default_page_title] – that’s an important step to keeping the controller DRY.

The controller

# app/controllers/admin/application_settings_controller.rb

class Admin::ApplicationSettingsController < ApplicationController
  def default_page_title
    update_setting ApplicationSettings::DefaultPageTitle
  end

  def default_commission
    update_setting ApplicationSettings::DefaultCommission
  end

  private

  def update_setting(klass)
    setting = klass.get
    setting.update_attributes(params[:setting])
    redirect_to admin_application_settings_path
  end
end

Since each settings class conforms to the same interface (get), and the view has ensured that the params get sent up
as params[:setting] the controller becomes pretty trivial.

Usage

Anywhere you need access to the value of a setting, you just call get on the appropriate settings object. Obviously if this were
production code you could cache those settings for performance.

References

I originally learned about this modeling pattern from Dan Chak’s book Enterprise Rails.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Using MySQL foreign keys, procedures and triggers with Rails

Pivotal Labs
Sunday, May 22, 2011

When I made the transition from an ASP.NET developer to a Rails developer, one of the biggest changes I noticed was that
the vast majority of Rails developers I worked with paid no attention to referential integrity at the database level,
and relied solely on Rails to make sure things worked as expected. Predictably, one of the biggest issues I’ve seen across
all the production Rails applications I’ve worked on is data integrity – especially duplicate data in rows that should be
unique, and orphaned records.

To their defense, it’s actually quite difficult to add support for referential integrity with Rails, and support for it
in common gems is limited. In this post, I’ll explain how to:

  • create foreign keys, views, procedures, functions and triggers with migrations
  • call routines from Rails
  • use views to back ActiveRecord objects
  • hack rake db tasks to dump and load a proper schema file
  • test using fixtures
  • work with FixtureBuilder

Creating foreign keys in migrations

There are several plugins and gems that help create foreign keys in migrations, but it’s pretty simple to roll your own.
Here’s a quick example:

module ForeignKeys

  def add_foreign_key(from_table, from_column, to_table, options = {})
    to_column = options.fetch(:to_column, 'id')
    suffix = options[:suffix]
    on_delete = options[:delete]
    on_update = options[:update]

    on_delete = 'SET NULL' if on_delete == :set_null
    on_update = 'CASCADE' if on_update == :cascade

    constraint_name = "fk_#{from_table}_#{to_table}"
    constraint_name += "_#{suffix}" unless suffix.nil?
    sql = "ALTER TABLE #{from_table} ADD CONSTRAINT #{constraint_name} FOREIGN KEY (#{from_column}) REFERENCES #{to_table}(#{to_column})"
    sql += "nON DELETE #{on_delete}" if on_delete
    sql += "nON UPDATE #{on_update}" if on_update
    execute sql
  end

  def remove_foreign_key(from_table, to_table, suffix = nil)
    constraint_name = "fk_#{from_table}_#{to_table}"
    constraint_name += "_#{suffix}" unless suffix.nil?

    # note, you may have to use DROP KEY here - see MySQL docs for details
    execute "ALTER TABLE #{from_table} DROP FOREIGN KEY #{constraint_name}"
  end

end

ActiveRecord::Migration.extend(ForeignKeys)

The in your migrations you can do things like:

def self.up
  add_foreign_key :some_table, :created_by_id, :users, :update => :cascade, :delete => :set_null, :suffix => :created_by_id
  add_foreign_key :some_table, :updated_by_id, :users, :update => :cascade, :delete => :set_null, :suffix => :updated_by_id
end

def self.down
  remove_foreign_key :some_table, :created_by_id, :users, :suffix => :created_by_id
  remove_foreign_key :some_table, :updated_by_id, :users, :suffix => :updated_by_id
end

Creating non-standard objects with migrations

Creating views, procedures, functions, and triggers in MySQL via migrations is actually much easier than creating them in the MySQL interactive shell.
In the shell, when you create routines you have to set a different delimiter before creating a proc, and then reset it when you are done.
ActiveRecord sets up a different end-of-statement flag (not a semi-colon), so you can just write the straight sql, like so:

class CreateSomeProc < ActiveRecord::Migration
  def self.up
    execute "DROP PROCEDURE IF EXISTS some_proc"

    sql = <<-SQL
      CREATE PROCEDURE some_proc(IN some_id INT)
      BEGIN
        INSERT INTO some_table (id)
        VALUES (some_id);

        UPDATE some_other_table
        SET some_column = some_column + 1
        WHERE id = some_id;
      END
    SQL

    execute sql
  end
end

Notice how you can have multiple semi-colons in the procedure / trigger definition. The only catch is that if you
want to drop an object, you’ll need to do that in a separate execute call, or the migration will raise an unhelpful sytax error.

Calling MySQL Procedures from Rails

Calling MySQL procedures from Rails is very straightforward – just use the execute method:

ActiveRecord::Base.connection.execute("CALL some_proc(#{some_id})")

If you want to return more than one value from a procedure, like a recordset, you’re on your own. You have to tweak the
connection to MySQL to allow for multiple results from procedures, and I haven’t had the pleasure of doing that yet.

Using views with ActiveRecord

Rails can use a database view as its table, and it’s as easy as setting the table name:

class SomeModelBackedByAView < ActiveRecord::Base
  set_table_name "some_view"
end

In MySQL views are read-only, and if you want that to be reflected in your model, you might want to do something like this:

class SomeModelBackedByAView < ActiveRecord::Base
  set_table_name "some_view"
  def readonly() true end
  before_destroy { raise(ActiveRecord::ReadOnlyRecord) }
end

If you want access to the magic id attribute for a column that’s not named id, you can also use set_primary_key.

Getting rake to work with a complete schema file

By default Rails dumps its database schema to a ruby file. Unless you are using a plugin that also dumps foreign keys etc.
the default ruby format will lose all of this information, which means your test database will differ significantly from
your development/production database. So to start you’ll need to tell Rails to use a SQL formatted schema file like so:

module YourApp
  class Application < Rails::Application
    config.active_record.schema_format = :sql
  end
end

Unfortunately, the built in structure dump tasks for MySQL do not include procedures, triggers or foreign keys. This means
that you need to override the default rake tasks with new ones. Rake does not make this easy, because by default if you define
a task with the same name as an existing task, it appends to the task, running the original one first, then yours.
So first you need to add some plumbing to allow you to erase and re-create rake tasks. To do so, put this in your top-level Rakefile:

require File.expand_path('../config/application', __FILE__)
require 'rake'

# http://blog.jayfields.com/2008/02/rake-task-overwriting.html
class Rake::Task

  def overwrite(&block)
    @full_comment = nil
    @actions.clear
    prerequisites.clear
    enhance(&block)
  end

  def abandon
    @full_comment = nil
    prerequisites.clear
    @actions.clear
  end

end

YourApp::Application.load_tasks

Now you can override the tasks you need. For db:test:prepare to work, you’ll need to override db:structure:dump and db:test:clone_structure.
In addition, you’ll probably want to add a task that’s equivalent to db:schema:load named db:structure:load.

The first task to override is db:structure:dump. In you’ll need to:

  • get the database credentials for the current environment
  • dump a schema file using mysqldump, being sure to supply all of the proper flags (mysqldump does not, by default, include procedures or triggers)
  • populate the schema migrations table

MySQL ships with a tool called mysqldump that can create usable sql schema files, so you can use that to do the heavy lifting.
Here’s what it looks like using a modern version of MySQL:

namespace :db do
  namespace :structure do |schema|
    schema[:dump].abandon
    desc "OVERWRITTEN - shell out to mysqldump"
    task :dump => :environment do
      config = ActiveRecord::Base.configurations[Rails.env]
      cmd = "mysqldump -u#{config['username']} -p#{config['password']} -d --routines --triggers --skip-comments #{config['database']} > db/development_structure.sql"
      system cmd
      File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
    end
  end
end

The --routines and --triggers flags tell mysqldump to include functions, procedures and triggers. The --skip-comments
flag tells mysqldump to not include the datetime that the file was dumped, as it causes unnecessary conflicts if you check your
structure files into version control.

Now that you have a complete snapshot of your database, you’ll need to be able to load it properly. The built-in Rake task assumes
that the generated sql file has single SQL statements delimited by 2 newlines, so it reads the SQL file, splits it on newlines
and then runs execute for each of them. However mysqldump produces a file that does not have a predictable number of
newlines between statements and has all kinds of characters that execute chokes on, so the default db:test:clone_structure task fails.

Here’s a working version that uses the mysql shell directly:

namespace :db do
  namespace :test do |schema|
    schema[:clone_structure].abandon
    desc "OVERWRITTEN - load the development_structure file using mysql shell"
    task :clone_structure => ["db:structure:dump", "db:test:purge"] do
      config = ActiveRecord::Base.configurations['test']
      cmd = "mysql -u#{config['username']} -p#{config['password']}  #{config['database']} < db/development_structure.sql"
      system cmd
    end
  end
end

For things like Continuous Integration boxes, it’s nice to be able to load a schema directly. For that, you can create another task that uses the mysql shell to load the schema:

namespace :db do
  namespace :structure do |schema|
    desc "load the development_structure file using mysql shell"
    task :load => :environment do
      config = ActiveRecord::Base.configurations[Rails.env]
      cmd = "mysql -u#{config['username']} -p#{config['password']}  #{config['database']} < db/development_structure.sql"
      system cmd
    end
  end
end

NOTE: if your database credentials include different host names, ports etc… you’ll have to add those to the system command.
The code above is just an example of how you can use mysqldump – it’s not meant to be a complete version you can copy to your app.

Now standard tasks like db:test:prepare will work as expected in development. If you have a Continuous Integration server
setup, you can easily create your test database like so:

rake db:test:purge db:structure:load RAILS_ENV=test

^—- The section above makes my eyes bleed —^

If you are thinking that the above rake tasks are brittle and may cause a maintenance headache when upgrading or switching
databases, you are correct. The Rails database rake tasks are a travesty and are perhaps the most poorly-written code in Rails.
Instead of using the built-in adapters and some polymorphism, almost every database rake task has an ugly case statement which,
when combined with Rake’s idiosyncrasies, makes it impossible to override without hackery. Unfortunately it’s very
time-consuming to refactor those tasks because:

  • there are no tests around them now, so you’d first have to write a test suite
  • many of the tasks shell out, so you’d have to test against multiple versions of each database (for example tools like mysqldump has different flags in different versions)

Working with test/spec fixtures

If you use foreign keys or triggers, you may run into problems loading fixtures in tests. One common issue I’ve noticed is when
you load all global fixtures:

# spec/spec_helper.rb

RSpec.configure do |config|
  config.global_fixtures = :all
end

If by coincidence your foreign key checks work when tables are deleted alphabetically, you’re OK, but if not you may
need to specify the order in which your fixtures are loaded, like so:

# spec/spec_helper.rb

RSpec.configure do |config|
  config.global_fixtures = *%w[ some_model_name some_other_model_name ]
end

If you use triggers, you may notice that some data is mysteriously lying around even after tests/specs finish. This
can happen for a number of reasons, such as individual specs being run with transactional_fixtures turned off. If that’s the case,
you may need to manually delete data from those tables right before your test suite starts, like so:

# spec/spec_helper.rb

ActiveRecord::Base.connection.execute "delete from some_problematic_table;"

RSpec.configure do |config|
  config.global_fixtures = *%w[ some_model_name some_other_model_name ]
end

Working with FixtureBuilder

I’m a big fan of FixtureBuilder, but its support for referential integrity is
poor in the currently released version. The main issue is that it doesn’t disable referential integrity when deleting
tables. Luckily, it’s easy to fix. In your fixture_builder file, add the following hack:

# spec/support/fixture_builder.rb

FixtureBuilder::Configuration.class_eval do
  def delete_tables
    ActiveRecord::Base.connection.disable_referential_integrity do
      tables.each { |t| ActiveRecord::Base.connection.delete(delete_sql % ActiveRecord::Base.connection.quote_table_name(t)) }
    end
  end
end

FixtureBuilder.configure do |builder|
  #...
end

NOTE: I currently have a pull request
that fixes these issues, so if you’d like to see these changes rolled in add a comment there – it might speed things up :)

ActiveRecord will report views as tables with many database adapters. However, you don’t want fixture_builder to try to
delete from or load data into a database view, so you’ll have to manually exclude your views from fixture_builder like so:

FixtureBuilder.configure do |builder|
  builder.skip_tables += ["active_ads_view"]
  builder.factory do
    #...
  end
end

Summary

Using foreign keys in Rails is still somewhat painful, but with the steps outlined above you can
get up and running in a few minutes. Using procedures, functions, triggers and views in ActiveRecord is straightforward,
once the proper rake tasks are in place.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Form-backing objects for fun and profit

Pivotal Labs
Saturday, May 21, 2011

In this post I’ll make the case for why form-backing objects help keep your app’s codebase clean and maintanable, and
I’ll show how to create, use and test a form-backing object.

Imagine that you are building a website where users can register and your stories look something like this:

  • When a visitor registers a User record and Business record are created
  • The user must accept the terms of service
  • The user must correctly answer a simple math or logic question to ward off bots
  • The site should log the IP address the of the user who registered
  • After the registration is complete, the user should get an email
  • If there are any problems or validation errors during registration, no User or Business should be created and nothing should be logged

These are pretty straightforward requirements, so you start coding:

Take 1: The Fat Controller

The first version might look something like this:

class UsersController < ApplicationController
  def new
    @question = AntiBotQuestion.random
    @user = User.new
  end

  def create
    @business = Business.new(params[:business])
    @user = User.new(params[:user])
    @question = AntiBotQuestion.find_by_id(params[:anti_bot_question_id])

    objects_were_saved = false
    begin
      ActiveRecord::Base.transaction do
        @business.save!
        @user.save!
        @business.memberships.create! :user => @user
      end
      objects_were_saved = true
    rescue
    end

    if objects_were_saved && @question.answer == params[:anti_bot_question][:answer]
      UserMailer.deliver_user_registered(@user)
      IpLogger.log(request.remote_ip)
      redirect_to root_path
    else
      render :action => "new"
    end
  end
end

If you’re like most Rails developers I know, you probably vomited a little in your mouth after looking at that code.
At first glance its biggest issue is that is violates the skinny-controller-fat-model guideline that’s become popular
recently, so following that guideline you decide to put your controller on a diet:

Take 2: The Fat Model

In an effort to make the controller skinny, you move most of the registration logic to the User model like so:

class UsersController < ApplicationController
  def create
    @user = User.new(params[:user])

    if @user.save
      redirect_to root_path
    else
      render :action => "new"
    end
  end
end


class User < ActiveRecord::Base
  belongs_to :membership
  accepts_nested_attributes_for :membership

  validates_confirmation_of :terms_of_service

  validate :on => :create do
    errors[:base] << "Incorrect!" if answer != question.answer
  end

  attr_accessor :ip_address, :question, :answer

  after_create do
    UserMailer.deliver_user_registered
    IpLogger.log(ip_address)
  end
end

Skinny controller, fat model, ship it – right? Well, if I had to make the choice, I would choose the fat-controller option any day.
While the fat-controller code is ugly, at least it’s isolated. The fat model code:

  • pollutes the entire model – tightly coupling User to Business
  • adding network calls in models
  • adding controller concerns like IP addresses to the model

Practically speaking, it adds overhead to every User creation throughout the app. Let’s say for example that you merge
codebases with another site (like through an acquisition) and you need to add all the other site’s users to your user
base – do you add fake IP addresses and confirmations, or do you add conditional logic to skip those callbacks? In addition,
you have overhead when creating a User in your test suite.

After looking at that, you might decide to introduce a form-backing object.

Take 3: Skinny Controller, Skinny Model, and Form-backing Object

Form-backing objects, also known as Presenters (not to be confused with the concept of view presenters), are objects
whose sole purpose is to take user-entered form data and perform some unit of work. Creating and testing form-backing objects
is simple. In this situation, you might add a Registration object.

The controller remains very simple:

class RegistrationsController < ApplicationController
  def new
    @registration = Forms::Registration.new
  end

  def create
    @registration = Forms::Registration.new(params[:registration].merge(:ip_address => request.remote_ip))
    if @registration.save
      redirect_to root_path
    else
      render :action => "new"
    end
  end
end

The form becomes much simpler than either of the cases above, since there are no nested forms or multiple instance variables:

<%= form_for @registration, :url => registrations_path, :as => :registration do |f| %>
  <%= f.error_messages %>
  <%= f.label :name %>
  <%= f.text_field :name %>
  <%= f.label :email %>
  <%= f.text_field :email %>
  <%= f.label :anti_bot_answer, f.object.anti_bot_question.text %>
  <%= f.text_field :anti_bot_answer %>
  <%= f.check_box :terms_of_service %>
  <%= f.label :terms_of_service %>
  <%= f.hidden_field :anti_bot_question_id, :value => f.object.anti_bot_question.id %>
  <%= f.submit %>
<% end %>

The form-backing object must conform to the ActiveModel interface, in addition to whatever interface you defined in your controller.
This is an example of an object that does everything necessary:

class Forms::Registration

  # ActiveModel plumbing to make `form_for` work
  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  def persisted?
    false
  end

  # Custom application code

  ATTRIBUTES = [:name, :email, :terms_of_service, :anti_bot_question_id, :anti_bot_answer, :ip_address]

  attr_accessor *ATTRIBUTES

  def initialize(attributes = {})
    ATTRIBUTES.each do |attribute|
      send("#{attribute}=", attributes[attribute])
    end
  end

  validates :terms_of_service, :acceptance => true

  validate do
    if anti_bot_answer != anti_bot_question.answer
      errors[:anti_bot_answer] << "Incorrect answer - are you a bot?"
    end
  end

  validate do
    [user, business, membership].each do |object|
      unless object.valid?
        object.errors.each do |key, values|
          errors[key] = values
        end
      end
    end
  end

  def anti_bot_question
    if anti_bot_question_id
      AntiBotQuestion.find_by_id(anti_bot_question_id)
    else
      AntiBotQuestion.random
    end
  end

  def user
    @user ||= User.new(:email => email)
  end

  def business
    @business ||= Business.new(:name => name)
  end

  def membership
    @membership ||= business.memberships.build(:user => user)
  end

  def save
    return false unless valid?
    if create_objects
      UserMailer.deliver_user_registered(user)
      IpLogger.log(user, ip_address)
    else
      false
    end
  end

  private

  def create_objects
    ActiveRecord::Base.transaction do
      user.save!
      business.save!
      membership.save!
    end
  rescue
    false
  end

end

In my opinion this type of form-backing object combines the best of all worlds – it keeps the controller skinny and the
view simple but it does’t pollute the domain at all.

Testing form-backing objects

Form-backing objects are just plain ruby objects, so testing them is very straightforward with unit tests. The only
thing that you probably want to do is make sure that your form-backing object is compatible with form_for by using
the ActiveModel::Lint::Tests. With Test::Unit it’s as simple as:

# http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/

class RegistrationTest < ActiveModel::TestCase
  include ActiveModel::Lint::Tests

  def setup
    @model = Forms::Registration.new
  end
end

If you prefer RSpec, you can easily add a shared example that calls through to ActiveModel::Lint::Tests like so:

# Taken from http://pivotallabs.com/users/mgehard/blog/articles/1639-making-sure-you-implement-the-activemodel-interface-fully
#
# spec/
# ├── spec_helper.rb
# └── support
#     └── shared_examples
#         └── active_model.rb

shared_examples_for "ActiveModel" do
  require 'test/unit/assertions'
  require 'active_model/lint'
  include Test::Unit::Assertions
  include ActiveModel::Lint::Tests

  before do
    @model = subject
  end

  ActiveModel::Lint::Tests.public_instance_methods.map { |method| method.to_s }.grep(/^test/).each do |method|
    example(method.gsub('_', ' ')) { send method }
  end
end

Once that’s in place, your spec is pretty simple:

require 'spec_helper'

describe Forms::Registration do

  # if your form backing object has required parameters
  # you can add them by overriding `subject` like so:
  #
  # let(:subject) { Forms::Registration.new(:email => 'some@email.com') }

  it_behaves_like "ActiveModel"

end

Staying Dry

If you have a lot of these form-backing objects, you can easily move all of that plumbing to a base class or module. Or
you can use an off-the-shelf gem like Josh Susser’s Informal or James Golick’s
Active Presenter

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ken Mayer

[Standup][sf] 5/20/2011 DVORAK? I hardly knew her!

Ken Mayer
Friday, May 20, 2011

Helps

“Can I use after_initialize to set up nested associations for use with Controller#new actions?”

Not recommended. It is better to write a custom #new_for_form method in your model that pre-populates your instance variables with the 1 or more nested objects. That way you can test drive it, too.

“DelayedJob YAML parser in Ruby 1.9 is having issues.”

The compiled-in psych library has issues, use the tenderlove/psych gem instead. You have to use Bundler, however. Invoking require "psych" will use the compiled-in version.

“RubyMine is not honoring a DVORAK keyboard layout properly (it is mapping keyboard shortcuts to the QWERTY layout)”

Use a hardware dongle.

“Jenkins is trying to kill off Postgres?”

Does anyone have Jenkins CI working with Postgres? It seems that after a build is complete, some java deep down inside Hudson is trying to parse the process table, and it is choking on the Postgres entries. (Postgres re-writes ARGV[0] to display status info, you can’t rely on it be the original command line.) It remains a mystery why Jenkins would even need this information.

“Is anyone using Chrome in CI? The windows are transparent”

Sounds pretty. Odd, but pretty.

Interesting

“brew info … is your friend”

Can’t remember how to start or stop a daemon under OS X? If you installed it via Homebrew you are in luck:

brew info <foo>

Will print out the original install instructions, including exact lines to pass to launchctl(1), etc.

“More ‘special’ keywords to avoid in your models: target and source“

ActiveRecord Polymorphic Associations use a method called target internally. If you have an attribute in your model / database, also called target, then strange things will happen.

“Paperclip’s <model>.attachment.exists? is slow; it goes out to S3 (or whatever the store is)”

And it happens in strange places. Be warned. Or mock out your network calls.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Code Monkey

Alex Chaffee
Friday, May 20, 2011

So I didn’t go to whatever was going on in Baltimore this week, but I did do a whole bunch of open source coding over the past week or two:

  • TestFirst has a totally revamped design, including the downloadable (or cloneable) student exercises, and Learn JavaScript is now a first-class citizen
  • Wrong‘s expect alias now plays nicer with RSpec’s expect
  • Rerun got a few new command-line options, including --clear and --exit so you can now easily rerun regular scripts (like rerun -cx rake test) when their files change
  • I submitted a patch to RubyGems to make the warningitis less ZOMG and more KTHXBY
  • Fonzie is a bookmarklet that tells you what font you’re looking at
  • Twitter RSS is a bookmarklet that brings back the RSS link to the New Twitter UI
  • Showoff works better with nested bullets and missing showoff.json files — so go nag Scott if you want him to accept my patches ;-)
  • I also finally got my git pre-commit hook correctly stripping whitespace, and

A lot of these projects, especially TestFirst, are aching for improvement, so if you feel like contributing code or courseware, or even just feature requests, please get in touch!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Andrew Kitchen

Standup 5/19/2011: Down on the form

Andrew Kitchen
Thursday, May 19, 2011

Help

“What does it take to get formtastic and paperclip working together out of the box?”

An enquiring Pivot would like to know.

Interesting

*”Did you know Skype history is regex-aware?”

Now go ahead and correct yourself before resending that message…

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ian Zabel

[Standup][NY] Thu 5/19/2011: Tomorrow is Friday; Saturday comes afterward

Ian Zabel
Thursday, May 19, 2011

Interesting Things

  • Schubert mentions that if url_for is blowing up on you, you’ve got fundamental problems. Think of this as the canary in the coal-mine. For example, if you’re trying to use namespaces in your rails controllers and there’s a pluralization problem, url_for will fail fast and hard.

  • Kris Hicks let us know about Git from the bottom up, a free book that looks to be a great read for Git beginners and not-so-beginners alike.

  • Kris also mentioned /proc/[pid]/status, which is available on most Linux distros. It contains lots of useful information about any process, such as process state, memory sizes, etc. See PROC(5) for more.

Stretch!

Worst turnout so far this week… three of us did some neck rolls. Let’s step it up tomorrow!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ken Mayer

[Standup][sf] 2011-05-18 Dropsick

Ken Mayer
Wednesday, May 18, 2011

Help

  • rake db:drop deliberately ignores exceptions and does not return a non-zero error code to the shell. A pivot has a need to know that the database was, indeed dropped, without errors. How to do it? Don’t rewrite the ‘db:drop’ task; it is a messy 40+ line task that needs to connect to database adapters, etc. You could run rake db:version — it will return an exit(1) if there’s no database there.

  • Can Rails scaffold handle nested resources properly? “No” was the almost instant, if not prescient response. Use one of the alternatives.

Interesting

  • Alex Young’s blog post, “Bulletproof Deployment: Put Down the Pickaxe” describes a 90-line shell script that does most of what Capistrano or Vlad does.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Dan Podsedly

New in Pivotal Tracker: Custom Point Scales

Dan Podsedly
Wednesday, May 18, 2011

You can now create custom point scales for your Pivotal Tracker projects. This is one of our most commonly requested features, especially from teams that practice, and/or use the planning poker form of estimation.

The default point scales of 1/2/3, 1/2/4/8, and 1/2/3/5/8 remain in place, unchanged. To use a different set of point values for your project, go to your project settings, choose ‘custom’ in the point scale dropdown, and enter a list of numbers, separated by commas.

When using a custom point scale, the estimate values will appear as numbers, instead of the usual bars that you see when using one of the default point scales.

Unestimated stories will show the first 5 point values as buttons. If your point scale has more values, you can choose one of them to estimate a story by clicking the ‘+’ button on the right.

In general, we believe in breaking down projects and features into fine grained stories, with small point estimates. This gives your team an opportunity to uncover and discuss all of interesting, and potentially risky parts of a feature early, and allow you to de-prioritize parts that are less important. However, we’re also fans of having choices when it comes to process, and customizable point scales will allow you to experiment.

We’d love to hear your feedback, either in comments here, via email, or over at Twitter!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (781)
  • rails (113)
  • testing (88)
  • ruby (83)
  • ruby on rails (70)
  • jobs (62)
  • javascript (55)
  • techtalk (44)
  • rspec (38)
  • ironblogger (32)
  • productivity (30)
  • activerecord (29)
  • gogaruco (29)
  • git (28)
  • nyc (27)
  • rubymine (26)
  • bloggerdome (23)
  • mobile (22)
  • process (21)
  • pivotal tracker (21)
  • cucumber (20)
  • design (19)
  • jasmine (19)
  • ios (18)
  • webos (17)
  • objective-c (17)
  • android (16)
  • tracker ecosystem (16)
  • palm (16)
  • "soft" ware (16)
  • fun (15)
  • ci (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • bdd (14)
  • gem (13)
  • css (13)
  • tdd (13)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
  • mojo (10)
  • chef (10)
  • api (10)
Subscribe to Community Feed
  1. ←
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. →
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >