Why I don’t use let/let! in my RSpec

At work, we’re deciding on our test-writing style: let/let! blocks like let(:arg) { 5 } vs. instance variables defined in a setup method like @arg = 5.

  • I’ve found no advantage to let; but I have experienced disadvantages.
  • I’ve found no disadvantages to instance variables.

And so, 👍 for instance variables.


I’ve written many specs and have read the rspec docs, betterspecs.org many times.

I don’t use let/let! because

  • the purported advantage of lazy evaluation is never actually realized. I’m most always running all the tests in a file, and so there’s no time efficiency gain;
  • the API [let and let!] and their magic increase the code’s complexity and so must be balanced out by some other advantage;
  • Let introduces magic and apparently nondeterministic behavior which has broken my tests, and I’ve only been able to fix by converting to easy-to-understand @- variable instantiations.
  • Let has the problem of introducing non-ruby syntax — something that looks like an automatic variable isn’t one anymore.

So for me, let is fixing a problem I don’t have, and in doing so, introduces new problems.

DigBang: Safely unsafe Ruby hash traversal

Here’s Hash#dig!, a handy little piece of code I wrote for a project. It’s similar to Ruby 2.3’s Hash#dig, but raises an exception instead of returning nil when a key isn’t found.

places = { world: { uk: true } }

places.dig  :world, :uk # true
places.dig! :world, :uk # true

places.dig  :world, :uk, :bvi # nil
places.dig! :world, :uk, :bvi # KeyError:
                              #   Key not found: :bvi

#dig is to #[] as #dig! is to #fetch

Ruby 2.3 introduces the new Hash#dig method for safe extraction of a nested value. It’s the equivalent of a safely repeated Hash#[].

#dig!, on the other hand, is the equivalent of a safely repeated Hash#fetch. It’s “safely unsafe” 😉 raising the appropriate KeyError anywhere down the line, or returning the value if it exists. This turns out to be very easy to implement in the functional style:

def dig!(*keys)
  keys.reduce(self) { |a, e| a.fetch(e) }
end

The full source code

Goodbye “X for Y”: the cryptic Ruby error is becoming friendlier

Anyone who’s used Ruby has seen this message:

r.rb:1:in `name': wrong number of arguments (3 for 2) (ArgumentError)

This particular error has been driving me nuts for years. It’s just so unnecessarily difficult to interpret — especially if Ruby’s not the only language you use. I never remember which number is which.

Compare to Python:

TypeError: name() takes exactly 2 arguments (3 given)

This is better in several ways. The unambiguity; the tone (no “wrong”); the lack of line-noise characters; the plain-English sentence. Note also that the numbers are presented in the opposite order from Ruby. Not a problem in itself, but it makes the Ruby equivalent especially hard to interpret because it requires memorizing the difference.

Potentially as soon as Christmas, though, Ruby’s output look will look like this:

r.rb:1:in `name': wrong number of arguments (given 3, expected 2) (ArgumentError)

This is a great step in the right direction.

It turns out that the issue had been raised two years ago. I gave the topic a bump, and Martin Dürst quickly commited the improved code. I am incredibly appreciative, and happy to see that core Ruby developers value improvements like these. In fact, there’s an open Request for Comments on improvements to Ruby’s error messages.

Self-validating Ruby objects with ActiveModel Validations

I’m importing lots of CSV restaurant inspection data with Ruby, and I need to make sure the cleaned up data matches the spec. For example, a violation must have a business_id and date. It can optionally have a code and description. My goal was to be able to write a class like this:

class Violation < ValidatedObject
  attr_accessor :business_id, :date, :code, :description

  validates :business_id, presence: true
  validates :date, presence: true, type: Date
end

…and it would just work, Rails-style:

Violation.new do |v|
  v.business_id = '1234'
  v.date = '2015-01-15'
end
# => ArgumentError: date must be of the class Date

Violation.new do |v|
 v.date = Date.new(2015, 1, 25)
end
# => ArgumentError: business_id is required

Violation.new do |v|
 v.business_id = '1234'
 v.date = Date.new(2015, 1, 25)
end
# => new instance Violation<...>

ActiveModel::Validations was refactored out of ActiveRecord to enable just this sort of use case. This is because the awesome list of built-in validations (here and here) and methods like #valid? are useful in a variety of contexts, not just in Rails.

It turned out that it was easy to write a small base class with the ActiveModel::Validations mixin to make the checking automatic upon initialization:

require 'active_model'

class ValidatedObject
  include ActiveModel::Validations

  def initialize(&block)
    block.call(self)
    check_validations!
  end

  def check_validations!
    fail ArgumentError, errors.messages.inspect if invalid?
  end
end

For my importing and parsing purposes, I created a small custom TypeValidator:

 # Ensure an object is a certain class. This is an example of a custom
 # validator. It's here as a nested class for easy access by subclasses.
 #
 # @example
 #   class Dog < ValidatedObject
 #     attr_accessor :weight
 #     validates :weight, type: Float
 #   end
 class TypeValidator < ActiveModel::EachValidator
   def validate_each(record, attribute, value)
     return if value.class == options[:with]

     message = options[:message] || "is not of class #{options[:with]}"
     record.errors.add attribute, message
   end
 end

Here’s the finished ValidatedObject, and an example subclass: information about a CSV feed. This set up is working great; the only change I can see making soon is changing ValidatedObject to be mixed in via include rather than subclassing.

Thanks to:

Test Driven Devops

Illustration: 500 - Internal server errorI like to apply Test Driven Development to my sysadmin work. For example, every time I add a new redirect to a web server configuration I want to make sure I haven’t broken anything else. Further, I want my SSL configurations proactively checked daily for any possible error. I use Ruby RSpec and write tests like these:

describe 'My app' do
  context 'www.myapp.com' do
    it { should be_up }
    it { should have_a_valid_cert }
  end

  it 'serves the www.myapp.com page without redirecting' do
    expect('http://www.myapp.com/about').to be_status 200
  end

  it 'only serves via www' do
    expect('http://myapp.com').to redirect_permanently_to 'http://www.myapp.com/'
  end

  it 'forces visitors to use https' do
    expect('myapp.com').to enforce_https_everywhere
  end
end

When I want to make a configuration change, I first write a test for the desired outcome. Naturally, it fails while the old tests pass. I then work on the config change, re-running all the tests as I go, and am finished when they all pass. I also run these automatically from a cron job to get pro-active notification of new problems.

The phrases such as have_a_valid_cert are custom RSpec matchers; they’re added into the RSpec environment by this open source library on Github.  I’m also working on an app to run these specs in the cloud.

See also

Meteor Won’t Kill Rails Anytime Soon

This is in response to Why Meteor will kill Ruby on Rails. A pretty big claim, for sure. Now, Meteor is worth checking out, but Josh didn’t name any of the reasons why I and a whole lot of others choose Rails. So here we go.

It was its built-in support for these got me interested:

…and then its convention-over-configuration kept me hooked.

It’s like Rails has taken the best practices we’ve learned about building good software, and made them the default. And Django is on the same path. Josh credits Rails’ popularity on convention-over-configuration. That’s definitely helped. Meteor looks very interesting, but until it supports more of the above, we’re talking apples and oranges.