Decoupling from Rails

Here is a great talk called Decoupling from Rails by the late Jim Weirich.

This talk is most famous for being taken up by David Heinemeier Hansson as an example of “test induced design damage” during his Is TDD Dead? conversation series with Kent Beck and Martin Fowler. Putting that debate aside, though, here are some things this talk got me to understand more deeply or start thinking about:

Differentiating between “framework” code that we don’t own, and “application” code that we do. It’s interesting to think about defining clear boundaries between these two types of code, and about decoupling them as much as possible. (It’s worth thinking about even if you don’t end up doing it in the end!).

Many people encourage writing wrappers around external APIs we use in our application. It’s considered good practice to write and interact with a PaymentGateway wrapper we do own, which wraps a Braintree gem which we don’t own, rather than interacting directly with Braintree throughout the application. This is the same concept, except applied to Rails.

Injecting Rails objects as a dependency into plain old ruby objects. Rails is still the “context” in which other things are done, but you can dynamically swap in other contexts that respond to the same methods the Rails context does:

class EmployeesController < ApplicationController
  def create
    CreateRunner.new(self)
  end
end

class CreateRunner
  attr_reader :context

  def initialize(context)
    @context = context
  end

  def do_something
    context.do_something
  end
end

Wrapping ActiveRecord models up in a business logic layer using simple delegator. This is something I’ve heard a lot about but haven’t personally started doing yet. It was great to see a live-coding example of making it work. (At around minute 56 of the talk).

Some nice text-editing moments. Jim had something automatic set up for extracting variables into methods.

I hope you enjoy the talk as much as I did!

Advertisements

Sandi Metz does Magic

This week, I’ll be posting links to some great conference talks that I’ve either attended in person or seen online.

Sandi Metz gave a really great talk on proper testing, called The Magic Tricks of Testing at RailsConf 2013. I can’t wait to put some of her advice into action!

Here are some memorable quotes from the talk:

Integration tests are proof that the beast is alive!

And:

This is our job… To find the simplicity that lives at the heart of complexity.

Geographic Data, Rails, and Carmen

Instead of using the carmen-rails gem as an interface to carmen’s .yml geographic data, I am trying out a new way: putting carmen’s data directly into my db, and making Country, Subregion, and City into regular ActiveRecord::Base classes, making use of the composite_keys gem.

Also, instead of storing the ISO codes in the database, I am storing the English place-names themselves.

The rake task to move the data from carmen to the db is like this.

And the models are like this.

Pros:

  • Easier to search database by English country name, because names, rather than codes, are stored in the db.
  • Easier to add user-editable attributes (eg., travel expenses for each subregion) to Country and Subregion models.
  • After importing carmen’s data into your db, eliminates dependencies on carmen and carmen-rails
  • In carmen-rails, it can be tricky to display the place names in the view. Sometimes the value stored in the db is a code to look up a Carmen::Country. But other times, it is a custom place name to be displayed as-is. The logic to figure out how to display each record is too complicated. With this new way, the view code is simplified to @order.country_name

Cons:

  • Harder to translate and localize
  • Introduces new dependency on the composite_keys gem
  • Doesn’t follow the ‘Rails way’ because database keys are not an ‘id’ integer column
  • Doesn’t follow ISO international standards for country codes

So this alternate way makes sense if:

  • The app is mostly monolingual
  • Users need to search the database for place names, like in a full text search
  • Users need to edit custom Country or Subregion attributes
  • There is no pressing need to use ISO standard codes for places

Shoulda-Matchers and database-level not-null constraints

The shoulda-matchers gem makes it easy to test basic functionality of your app (mostly the model and controller layers it seems), without writing a whole lot of custom code.

However, when using shoulda-matchers, if you have a database-level NOT NULL constraint on your model, then you may have to adjust how you write the tests in order for them to function properly.

The following test of the uniqueness validation on unique_column won’t work if you have a NOT NULL constraint on the required_column at the database level:

describe SomeModel do
  it { should validate_presence_of(:required_column) }
  it { should validate_uniqueness_of(:unique_column) }
end

Instead, you first create a valid record (here this is done using a FactoryGirl factory), and then you run the test on that pre-existing record, as such:

  describe SomeModel do
    it { should validate_presence_of(:required_column) }

    let(:some_model) { create(:some_model) }
    subject { some_model }
    
    it { should validate_uniqueness_of(:unique_column) }
  end

This is the explanation, from the rdoc:

– (Object) validate_uniqueness_of(attr)

Ensures that the model is invalid if the given attribute is not unique. It uses the first existing record or creates a new one if no record exists in the database. It simply uses `:validate => false` to get around validations, so it will probably fail if there are `NOT NULL` constraints. In that case, you must create a record before calling `validate_uniqueness_of`.

Using seeds.rb data for testing is a bad idea

My app’s database has some static tables for which the data will stay more or less unchanged throughout the life of the app. Two of the many examples are:

  • List of world cities
  • List of roles that users can be assigned to

I was using seeds.rb to seed my production app, and I used the following code

    namespace :db do
      namespace :test do
        task :prepare => :environment do
          Rake::Task["db:seed"].invoke
        end
      end
    end

located in lib/tasks/test_seed.rake

which seeded the database each time I ran

rake test:prepare

This was a BAD IDEA, because:

  • With more complex model specs, I have to clean out all the data in the db before running the test suite, otherwise I will get lots of failed tests that actually should be passing.
  • But when I cleaned out the db using the database_cleaner gem, that always got rid of my seeded data, which also returned lots of failed tests that should have been passing.
  • To make the test suite run correctly, I had to run
    rake test:prepare

    before every single test suite.

Instead, I refactored all my tests to create cities and roles on the fly using FactoryGirl, with code like this:

let(:user) { FactoryGirl.create(:user) }
FactoryGirl.create(:role_assignment, user: user, role: FactoryGirl.create(:role, name: "admin"))

I had been using FactoryGirl to create most of my test data even before this, but hadn’t realized before that FactoryGirl could be used even for data that would normally come from seeds.rb