You’re working with a brand new library or feature, and you just can’t understand the documentation. Maybe it’s hard to get set up, so you have to bounce between RDocs until you learn how everything fits together. Maybe the documentation doesn’t even exist. Or maybe you just learn best by seeing lots of examples.

You need some help. But where do you learn how to use a feature, if not its documentation?

One of my favorite GitHub features

When you want to go beyond the documentation and see real-world uses of a library or feature, use GitHub’s code search.

For example, say I just read the Rails guide on Conditional GET Support. I feel like I understand it, but I want to see some more examples before I use it for real.

I could hop onto GitHub search, and look for "if stale?". Then, in the sidebar, pick code and Ruby:

And now I have tons of great examples of how stale? could be used.

Not all of the examples are interesting. But in one listing, I notice the author using Post.maximum:

app/controllers/archives_controller.rb
1
2
3
4
5
6
7
class ArchivesController < ApplicationController
  def index
    if stale?(last_modified: (Post.maximum(:updated_at) || EPOCH).utc, public: true)
      @months = Post.find_all_grouped_by_month
    end
  end
end

That seems like a great way to use stale?. And if I hadn’t seen this example, I might not have thought to try it.

How should you use what you find?

When you search open-source code, you get hundreds of examples for nearly any feature. You’ll notice where the documentation makes sense, and where it’s over-simplified. And you’ll see objects working together in real code. That will teach you more about how classes are connected than any RDoc can.

Still, use the examples to learn new things, not to cargo cult into your own project. Using code without understanding it is the fastest way to a poorly designed system.

In my example, maximum might not be the right thing to do in my project. I shouldn’t copy the line without learning more about it. Without understanding when it makes sense to use. But examples can teach you things you didn’t know before. And they’re a great way to decide what to learn about next.

Like any search, it’ll take some practice to get good results. But I’ve learned a lot from the code I’ve found, and I know you will too.

When I was first learning the board game Go, I bought and read a few introductory books. They taught me the rules, and some basic strategy. I knew that there was a fun game there. But I just wasn’t seeing it.

When it came time to play my own games, I was confused and stressed out. There were 361 possible moves I could make, but which of those was the best one? Not even that, which of those were even acceptable? Using the information I got from the first books, I could maybe narrow decent moves down to about a hundred, but I still had trouble knowing where to start, and what to do. And it just wasn’t fun to me.

I felt like I couldn’t even play the game unless I studied everything first.

So, on a friend’s recommendation, I picked up another book. The book was called “The Second Book of Go.” It had short chapters, each on a specific topic that beginners tend to struggle with. And it gave me some processes I could follow, so I could know that I wasn’t making insane moves.

It taught me the reason behind crazy-looking strategies, and it taught me tricks to make better decisions and evaluate my own moves. And it showed me that even wrong moves had value.

When I started playing again, I began to have fun.

As long as I’ve written here, I’ve heard from people learning Rails that were in the same boat as I was learning Go. They heard it was supposed to be fun, but it was actually just frustrating. They didn’t know where to start. The things they learned didn’t stick with them. They couldn’t spend time learning consistently enough to grow as a Rails developer. Most of all, they were overwhelmed, because the space of possibilities in front of them seemed too big.

If this sounds familiar, I wrote a book for you. It’s meant to bring the joy of Ruby and Rails to those who were about to give up. To show that with a little structure, a little practice, and a little curiosity, you can write your own Rails applications, and have fun doing it.

It’s available for pre-sale, starting today. It’s 25% off until it’s finished. And when you buy it, you’ll get today’s draft immediately, and updates to it from here on out.

I hope you enjoy it.

(This is a short excerpt from Practicing Rails. Sign up here to get the first chapter free!)

So, you’re working on a new app, and Rails just generated a test for you:

test/models/bug_test.rb
1
2
3
4
5
6
7
require 'test_helper'

class BugTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

You uncomment it, come up with a name, and you’re ready to write your test, right? But then what? What do you write first? What should your test code look like?

If you follow a simple pattern, you’ll turn those stubbed out lines of code into clear, well-structured test cases.

The three-phase test pattern

Your test cases should work in three phases:

  1. First, you set some stuff up (“Arrange”)
  2. Then, you do something (“Act”)
  3. Then, you make sure that what you expected to happen, actually happened. (“Assert”)

For instance, imagine you were testing a method on an array in Ruby. Following this pattern, your test could look like:

1
2
3
4
5
6
7
8
9
10
test "Array#sort will sort an array of numbers" do
  # arrange
  unsorted_array = [7, 4, 2, 3]

  # act
  sorted_array = unsorted_array.sort

  # assert
  assert_equal [2, 3, 4, 7], sorted_array
end

Simple enough. But every part of the test has a place to go, and each stage of the test almost tells you how to write it.

Sometimes, you won’t need an Arrange phase, or the Act and Assert phases will be combined. But it still helps to think about all three phases as you write your tests.

The Assert phase gotcha

There’s a trick to the Assert phase: you shouldn’t use the same logic that the Act phase used in the Assert phase. You should always take two paths to the same answer. Otherwise, you won’t notice bugs in the code you’re calling, because it’s just getting called again in the Assert phase.

For example, if you’re doing some math:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test "average returns the average of a set of numbers" do
  # arrange
  numbers = [1, 2, 3, 4]

  # act
  average = numbers.average

  # assert

  # this is bad
  assert_equal [1, 2, 3, 4].average, average

  # this is better
  assert_equal 2.5, average
end

Calling [1, 2, 3, 4].average again in the Assert phase is bad, because average could return almost anything and that assertion would still pass.

Here, that’s pretty clear. But even when things get more complicated, make sure you’re not just running the same code twice. Otherwise you’re only verifying that your method was called, not that it worked the way you expect it to.

Usually, the easiest way to take a second path to the answer is to find the answer by hand and hardcode it. It can be brittle, but it’s better than your tests breaking without you realizing it.

Why three phases?

If you split your tests into those three phases, you have simpler questions to answer. Instead of “How should I write this test?”, you can focus on each phase: “How should I set this test up?”, “What am I testing?”, “What should the answer look like?”

These questions still might not have easy answers, but the answers will be a lot easier than thinking about the entire test at once. And if you’re lucky, you can even share phases between related tests, making your next test much less painful to write.

A few weeks ago, I wrote about how RubyGems manages Ruby’s load path. But Rails doesn’t use RubyGems directly — it uses Bundler to manage its gems.

If you don’t know how Bundler works, the way gems get pulled into Rails might seem a little too magical. How does adding a line to a Gemfile get code into your app? How do Bundler, Rails, and RubyGems work together to make handling dependencies easy?

Why Bundler?

I think of Bundler as a strict gem manager. That is, Bundler helps you install the right versions of the gems you need, and forces your app to only use those versions.

This turns out to be really helpful. To understand why, you have to go back to the world before Bundler.

Before Bundler, it was still pretty easy to install the right versions of your gems with some kind of setup script:

bin/setup
1
2
3
gem install rails -v 4.1.0
gem install rake -v 10.3.2
...

(That is, as long as Rails 4.1’s dependencies don’t conflict with Rake 10.3.2’s dependencies!)

But what happens when you’re working on a few different Rails apps, each depending on different versions of gems? Unless you’re really careful, you’ll run into the terrible gem activation error:

gem_error
1
2
Gem::Exception: can't activate hpricot (= 0.6.161, runtime),
already activated hpricot-0.8.3

Ugh. That message still gives me nightmares. It usually meant you’re in for a day of installing and uninstalling gems, so you can get just the right versions on that machine. And all it takes is an accidental gem install rake to completely mess up all of your careful planning.

rvm gemsets helped with this problem for a while. But they needed some time to set up, and if you accidentally installed into the wrong gemset, you’d be back to the same problem. With Bundler, you rarely have to think about your dependencies. Your apps usually just work. And Bundler takes a lot less setup than gemsets did.

So, Bundler does two important things for you. It installs all the gems you need, and it locks RubyGems down, so those gems are the only ones you can require inside that Rails app.

How does Rails use Bundler?

At its core, Bundler installs and isolates your gems. But that’s not all it does. How does the code from the gems in your Gemfile make it into your Rails app?

If you look at bin/rails:

bin/rails
1
2
3
4
5
6
7
8
#!/usr/bin/env ruby
begin
  load File.expand_path("../spring", __FILE__)
rescue LoadError
end
APP_PATH = File.expand_path('../../config/application',  __FILE__)
require_relative '../config/boot'
require 'rails/commands'

You’ll see that it loads Rails by requiring ../config/boot. Let’s look at that file:

config/boot
1
2
3
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' # Set up gems listed in the Gemfile.

Hey, it’s Bundler! (Also, I just learned you can choose a different Gemfile to use by setting the environment variable BUNDLE_GEMFILE. That’s pretty cool.)

bundler/setup does a few things:

  • It removes all paths to gems from the $LOAD_PATH (which reverses any load path work that RubyGems did).
  • Then, it adds the load paths of just the gems in your Gemfile.lock back to the $LOAD_PATH.

Now, the only gems you can require files from are the ones in your Gemfile.

So all the gems you need are on your load path. But when you use RubyGems by itself, you still have to require the files you need. Why don’t you have to require your gems when you use Rails with Bundler?

Take a quick look at config/application.rb, which runs after Rails boots:

config/application.rb
1
2
3
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

It’s Bundler again! Bundler.require requires all the gems in all the groups you pass to it. (By “groups”, I mean the groups you specify in your Gemfile.)

Which groups are in Rails.groups, though?

railties/lib/rails.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   # Returns all rails groups for loading based on:
    #
    # * The Rails environment;
    # * The environment variable RAILS_GROUPS;
    # * The optional envs given as argument and the hash with group dependencies;
    #
    #   groups assets: [:development, :test]
    #
    #   # Returns
    #   # => [:default, :development, :assets] for Rails.env == "development"
    #   # => [:default, :production]           for Rails.env == "production"
    def groups(*groups)
      hash = groups.extract_options!
      env = Rails.env
      groups.unshift(:default, env)
      groups.concat ENV["RAILS_GROUPS"].to_s.split(",")
      groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) }
      groups.compact!
      groups.uniq!
      groups
    end

Well, that explains that. Rails.groups is going to be [:default, :development] when you’re running Rails in development mode, [:default, :production] in production mode, and so on.

So, Bundler will look in your Gemfile for gems belonging to each of those groups, and call require on each of the gems it finds. If you have a gem nokogiri, it’ll call require "nokogiri" for you. And that’s why your gems usually just work in Rails, without any extra code on your part.

Know your tools

If you understand the tools you use well, it’ll be easier to work with them. So if you find yourself using something all the time, it’s worth taking a few minutes to dig into it a little more.

If you’re working in Ruby and Rails, you’ll use gems every day. Take the time to learn them well!

There are lots of good places to learn Ruby. But learning isn’t just reading books or watching videos. It’s running head-first into a problem, getting stuck, struggling, getting frustrated, looking things up, having it click, playing around with it, and finally (finally!) getting something working.

You have to use the things you learn, or they won’t stick with you. And there are a few great ways I’ve found to do just that.

Ruby Quiz

Ruby Quiz is a group of over 150 short, interesting problems to solve with Ruby. Things from converting Roman Numerals to generating ASCII-art dungeons. Each problem has solutions, too — you can see different approaches to the same question. Ruby Quiz has been around forever, and it’s still a lot of fun.

exercism.io

exercism.io starts like Ruby Quiz — you’ll build solutions to small programming problems. But in exercism, after you submit your solution, you share it with other people. You’ll review your code, and refactor it to make it even better.

Your goal with exercism isn’t just working code. It’s refactoring toward small, simple code. You’ll practice your refactoring and object-oriented design skills. And those will stick with you through your entire programming career.

Try it in a tiny app

Programming challenges are great for building your general Ruby knowledge. But sometimes you’ll want to try a feature you just learned about, so you can understand it better.

So, try it in an app. Generate a new Rails app with a scaffold or two. Dedicate it to playing with the feature you want to learn. Even if you’re not doing something Rails-specific, Rails’ code generators are great for trying new things without much setup. You don’t have to worry about setting up tests, requiring the right files, getting Rake set up, or anything like that.

This is how I’ve been playing with the new Rails 4.2 features, and it’s the way I try most of the things I’ve written about here. (It’s also the topic of the first chapter of my book).

What have you forgotten?

I have a long, long list of things I’ve forgotten because I never used them. And that’s just not a good use of my time.

So, balance your reading and viewing with some practice and play. Do some challenges, or build some tiny apps. You’ll be surprised how much more quickly you’ll pick stuff up.

Most of the time, gems in Ruby Just Work. But there’s a big problem with Ruby magic: when things go wrong, it’s hard to find out why.

You won’t often run into problems with your gems. But when you do, Google is surprisingly unhelpful. The error messages are generic, so they could have one of many different causes. And if you don’t understand how gems actually work with Ruby, you’re going to have a tough time debugging these problems on your own.

Gems might seem magical. But with a little investigation, they’re pretty easy to understand.

What does gem install do?

A Ruby gem is just some code zipped up with a little extra data. You can see the code inside a gem with gem unpack:

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
~/Source/playground jweiss$ gem unpack resque_unit
Fetching: resque_unit-0.4.8.gem (100%)
Unpacked gem: '/Users/jweiss/Source/playground/resque_unit-0.4.8'
~/Source/playground jweiss$ cd resque_unit-0.4.8
~/Source/playground/resque_unit-0.4.8 jweiss$ find .
.
./lib
./lib/resque_unit
./lib/resque_unit/assertions.rb
./lib/resque_unit/errors.rb
./lib/resque_unit/helpers.rb
./lib/resque_unit/plugin.rb
./lib/resque_unit/resque.rb
./lib/resque_unit/scheduler.rb
./lib/resque_unit/scheduler_assertions.rb
./lib/resque_unit.rb
./lib/resque_unit_scheduler.rb
./README.md
./test
./test/resque_test.rb
./test/resque_unit_scheduler_test.rb
./test/resque_unit_test.rb
./test/sample_jobs.rb
./test/test_helper.rb
~/Source/playground/resque_unit-0.4.8 jweiss$

gem install, in its simplest form, does something kind of like this. It grabs the gem and puts its files into a special directory on your system. You can see where gem install will install your gems if you run gem environment (look for the INSTALLATION DIRECTORY: line):

1
2
3
4
5
6
~ jweiss$ gem environment
RubyGems Environment:
  - RUBYGEMS VERSION: 2.2.2
  - RUBY VERSION: 2.1.2 (2014-05-08 patchlevel 95) [x86_64-darwin14.0]
  - INSTALLATION DIRECTORY: /usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0
  ...
1
2
3
~ jweiss$ ls /usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0
bin           bundler     doc         gems
build_info    cache       extensions  specifications

All of your installed gem code will be there, under the gems directory.

These paths vary from system to system, and they also depend on how you installed Ruby (rvm is different from Homebrew, which is different from rbenv, and so on). So gem environment will be helpful when you want to know where your gems’ code lives.

How does gem code get required?

To help you use the code inside of your gems, RubyGems overrides Ruby’s require method. (It does this in core_ext/kernel_require.rb). The comment is pretty clear:

core_ext/kernel_require.rb
1
2
3
4
5
6
7
8
9
10
11
  ##
  # When RubyGems is required, Kernel#require is replaced with our own which
  # is capable of loading gems on demand.
  #
  # When you call <tt>require 'x'</tt>, this is what happens:
  # * If the file can be loaded from the existing Ruby loadpath, it
  #   is.
  # * Otherwise, installed gems are searched for a file that matches.
  #   If it's found in gem 'y', that gem is activated (added to the
  #   loadpath).
  #

For example, say you wanted to load active_support. RubyGems will try to require it using Ruby’s require method. That gives you this error:

core_ext/kernel_require.rb
1
2
3
4
LoadError: cannot load such file -- active_support
  from (irb):17:in `require'
 from (irb):17
 from /usr/local/bin/irb:11:in `<main>'

RubyGems looks at that error message, and sees that it needs to find active_support.rb inside of a gem, instead. To do that, it’ll scan through its gems’ metadata, looking for a gem that contains active_support.rb:

1
2
irb(main):001:0> spec = Gem::Specification.find_by_path('active_support')
=> #<Gem::Specification:0x3fe366874324 activesupport-4.2.0.beta1>

Then, it activates the gem, which adds the code inside the gem to Ruby’s load path (the directories you can require files from):

1
2
3
4
5
6
irb(main):002:0> $LOAD_PATH
=> ["/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0/x86_64-darwin14.0"]
irb(main):003:0> spec.activate
=> true
irb(main):004:0> $LOAD_PATH
=> ["/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/i18n-0.7.0.beta1/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/thread_safe-0.3.4/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/activesupport-4.2.0.beta1/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0/x86_64-darwin14.0"]

Now that active_support is on the load path, you can require the files in the gem just like any other piece of Ruby code. You can even use the original version of require, the one that was overwritten by RubyGems:

1
2
irb(main):005:0> gem_original_require 'active_support'
=> true

Cool!

A little knowledge goes a long way

RubyGems might seem complicated. But at its most basic level, it’s just managing Ruby’s load path for you. That’s not to say it’s all easy. I didn’t cover how RubyGems manages version conflicts between gems, gem binaries (like rails and rake), C extensions, and lots of other stuff.

But knowing RubyGems even at this surface level will help out a lot. With a little code reading and playing in irb, you’ll be able to dive into the source of your gems. You can understand where your gems live, so you can make sure that RubyGems knows about them. And once you know how gems are loaded, you can dig into some crazy-looking loading problems.

Can you learn Rails before learning Ruby? You can, for a little while. But as your app grows past the REST, generated-scaffold, twitter-clone stage, you’re going to run into some walls. Sure, you’ll learn some Ruby through osmosis. But that’ll take forever.

If you want to learn enough Ruby to master Rails, study Ruby on its own. But there are tons of Ruby books out there. I counted four shelves the last time I was in a bookstore. A lot of them are good. But there are a few that are in a class of their own. And they’ll help you through the entire process of learning Ruby.

From the beginning

Rails might be your first framework, and Ruby your first language. If you’re new to programming in general, you’ll want to start with Learn to Program. It’ll teach you to write your own programs in Ruby, even if you’ve never written code before. It’ll start you off with good habits. And I know lots of people that had a lot of fun with this as their first-ever programming book.

Next, Eloquent Ruby will teach you to write Ruby that looks like Ruby. (Which is really important to Rubyists). You’ll learn how to define classes, how to create methods on the fly, and why you’d choose one way of writing Ruby code over another.

After reading Eloquent Ruby, you’ll know idiomatic Ruby. And you’ll have an easier time reading and understanding the code you encounter.

When things get tougher

Once you read those books, you’ll know how to write Ruby code, but you’ll still have trouble organizing it. When does a method go into one class instead of another? When are classes too big? What should you do when you wrote a method so complicated that you can’t understand it?

Confident Ruby and Practical Object-Oriented Design in Ruby are the gems of intermediate Ruby development. Read these, and your Ruby code will be forever changed for the better.

How’s that?

Confident Ruby will teach you to write readable Ruby code by following some simple, general patterns. It’s about writing code that’s clear about what it does. Code that pushes decisions, duplication, and edge cases away from itself, so you don’t have to think about the complicated parts when you don’t need to. Code that Just Works.

Practical Object-Oriented Design in Ruby is about applying good design to your code. You’ll learn how to structure your objects, methods, and communication between them, so your code is easier to use and test. It’s about making your code flexible, without applying too much structure. And writing tests that you won’t hate fixing when your code changes.

Ongoing education

Next, you’ll learn more by seeking out lots of ideas, and learning more about the ones that seem interesting. Blogs are good, but there’s been an explosion of other great resources, released regularly, on a variety of Ruby topics.

Some of my favorites are:

RubyTapas and Ruby Steps are both paid services. But they each have some episodes / newsletters available for free, so you can see if they’re your style before you sign up.

I’ve learned a ton about Ruby from years of random articles and screencasts. It’s not the most organized way to get information, but it works.

Bringing it back to Rails

Rails is Ruby. The time you spend learning Ruby will teach you about Rails, and vice versa.

So learning Ruby first isn’t a requirement for using Rails. You can do a lot with Rails even if you have to muddle through the Ruby.

But it’s not the best way to do it. You’ll have to learn Ruby eventually. And when you do, you can use these books to build a solid foundation.

(I sent this post to my list a few months back. If you like it, and want to read more like it, you should sign up!)

I ran into a weird situation the other day with one of my apps.

Say you have an Article model, and these articles are first created as drafts. These drafts should be lightweight. You want to be able to create and edit them right away, without needing a body, or even a title.

But when you publish these articles, you need to have some extra validation. They should have a title, a body, and all the rest of that stuff.

You could write up a bunch of if statements to do the checking yourself. But you wouldn’t have all the nice form handling you get from Rails. And I’ve found that going with Rails for the things Rails handles well will save you a lot of headache later on. Is there still a way to use validations for this?

if and unless?

Validations support :if and :unless parameters, but this isn’t always so clean:

app/models/article.rb
1
2
3
4
5
6
7
8
9
class Article < ActiveRecord::Base
  # validate in draft mode
  validates_presence_of :author_id

  # validate only when published
  validates_presence_of :body, unless: lambda { |o| o.draft? }

  ...
end

Besides, this isn’t the way I want to publish an article. I want to think about publishing an article as an action, not a state the article is in, so setting and checking an attribute isn’t the right answer.

A little-known, lightly documented Rails feature

This problem is a perfect fit for Rails’ validation contexts. I first learned about custom validation contexts from a gist DHH wrote. But it was hard to find a good explanation of what these were, and how to use them.

The documentation for custom contexts is pretty thin. The only mention of it I could find in the API docs was here. And the API docs for these validations don’t mention custom contexts at all! This makes it especially hard — if you don’t know what something’s called, how do you find more documentation on it?

I finally found a good overview here: http://blog.arkency.com/2014/04/mastering-rails-validations-contexts/. It’s worth a read. Let’s see what it looks like for our example:

app/models/article.rb
1
2
3
4
5
6
7
8
class Article < ActiveRecord::Base
  # validate in draft mode
  validates_presence_of :author_id

  # validate only when published
  validates_presence_of :body, on: :publish
  ...
end
app/models/article_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ArticleController < ApplicationController
  respond_to :html

  def create
    @article = Article.create(article_params)
    respond_with @article
  end

  def publish
    @article = Article.find(params[:id])
    @article.attributes = article_params
    @article.save(context: :publish)
    respond_with @article
  end

  private
  def article_params
    params.
      require(:article).
      permit(:body, :title, :author_id)
  end
end

Not too bad! The only weird thing is having to use save instead of update in the publish method. That happens because update can’t take a validation context as a parameter. (Maybe that’s an opportunity for a pull request?)

Beyond saving

Custom validation contexts are useful for more than just saving a record in different ways. Contexts work with valid?:

1
@article.valid?(:publish)

so you can use validation contexts anywhere you want to answer the question: “Does this object have this property? And if not, why not?”

DHH creates a method ending in -able? that delegates to valid?(:context)). I really like the look of that:

app/models/article.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Article
  validate :has_been_published, on: :view
  validate :is_not_spam, on: :view

  def viewable?
    valid? :view
  end

  private

  def has_been_published
    if published_at.future?
      errors.add(:published_at, "is in the future")
    end
  end

  def is_not_spam
    if is_spam?(body)
      errors.add(:body, "has been detected as spam")
    end
  end
end

This way, when you check it, you get more information than just true or false:

1
2
3
unless @article.viewable?
  # @article.errors is now filled out
end

Is the article viewable? Well, why not?

Just lightweight enough

There are a few other ways you could get this kind of validation behavior. You could build custom ActiveModel service objects with their own validations. You could use :if or :unless on your validations. But like a lot of the stuff in Rails, custom validation contexts are a good compromise between readability and flexibility.

The Rails 4.2 announcement had some interesting news about the upcoming Rails 5: It’s probably going to require Ruby 2.2. Which will make it the first Rails version to take advantage of all the good stuff from Ruby 2.

The post mentioned garbage collected symbols and keyword arguments. But to me, one of the most interesting Ruby 2 features is Module#prepend.

The good (and bad) of alias_method_chain

When I first learned Rails, alias_method_chain totally blew my mind. It really showed off how flexible Ruby could be.

With just a single line of code, you could completely change the way a method worked. You no longer needed to hack around libraries to add the code you wanted, you could just add it on the fly. alias_method_chain led to my first patches to gems, which led to my first pull requests, which led to my first open source contributions.

But just like monkey patching, alias_method_chain got overused, and its problems started to become obvious:

  • The method names it generates are confusing, which makes errors hard to find and debug. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person
  def greeting
    "Hello"
  end
end

module GreetingWithExcitement
  def self.included(base)
    base.class_eval do
      alias_method_chain :greeting, :excitement
    end
  end

  def greeting_with_excitement
    "#{greeting_without_excitement}!!!!!"
  end
end

Person.send(:include, GreetingWithExcitement)

If you had an error in Person#greeting, the backtrace would tell you that an error actually happened in Person#greeting_without_excitement. But where is that method even defined? I don’t see it anywhere. How do you know which greeting method is the one with the bug in it? And the method names get even more confusing the more you chain.

  • If you call alias_method_chain twice with the same parameters on the same class, you could cause a stack overflow. (Can you see why?) This doesn’t normally happen, as long as your require statements are consistent about which paths they use. But it’s super annoying if you frequently paste code into a Rails console.

  • And the rest of the things described by Yehuda Katz’s blog post. This post convinced a lot of Rails devs to start abandoning alias_method_chain in favor of module inheritance.

So, why is it still used?

You can replace most alias_method_chains by overriding those methods in a module, and including those modules into your child classes. But that only works if you want to override your superclass, not your class itself. That is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ParentClass
  def log
    puts "In parent"
  end
end

class ChildClass < ParentClass
  def log
    puts "In child"
    super
  end

  def log_with_extra_message
    puts "In child, with extra message"
    log_without_extra_message
  end

  alias_method_chain :log, :extra_message
end

If you ran ChildClass.new.log, you’d see:

1
2
3
In child, with extra message
In child
In parent

if you tried to use modules instead of alias_method_chain, you could get the output to be:

1
2
3
In child
In child, with extra message
In parent

But you cannot match the original output without changing the log method in ChildClass. Ruby inheritance doesn’t work that way. Well, it didn’t.

What changed in Ruby 2.0?

Until Ruby 2.0, there was no way to add code below a class, only above it. But with prepend, you can override a method in a class with a method from a module, and still access the class’s implementation with super. So, using our last example, we could get the original output with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ParentClass
  def log
    puts "In parent"
  end
end

module ExtraMessageLogging
  def log
    puts "In child, with extra message"
    super
  end
end

class ChildClass < ParentClass
  prepend ExtraMessageLogging
  def log
    puts "In child"
    super
  end
end
1
2
3
In child, with extra message
In child
In parent

Perfect.

If prepend is still hard to wrap your head around, think of it as doing something like this:

1
2
3
4
5
class NewChildClass < ChildClass
  include ExtraMessageLogging
end

ChildClass = NewChildClass

Except it won’t mess with your class names, and it affects objects that already exist.

(Yes, you can reassign class names in Ruby. No, it’s probably not a great idea.)

What does this mean for Rails?

So, the last excuse for using alias_method_chain is gone in Ruby 2.0. We could take one of the few remaining examples of alias_method_chain in Rails:

rails/activesupport/lib/active_support/core_ext/range/each.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'active_support/core_ext/module/aliasing'

class Range #:nodoc:

  def each_with_time_with_zone(&block)
    ensure_iteration_allowed
    each_without_time_with_zone(&block)
  end
  alias_method_chain :each, :time_with_zone

  def step_with_time_with_zone(n = 1, &block)
    ensure_iteration_allowed
    step_without_time_with_zone(n, &block)
  end
  alias_method_chain :step, :time_with_zone

  private
  def ensure_iteration_allowed
    if first.is_a?(Time)
      raise TypeError, "can't iterate from #{first.class}"
    end
  end
end

and replace it with a module, instead:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'active_support/core_ext/module/aliasing'

module RangeWithTimeWithZoneSupport #:nodoc:

  def each(&block)
    ensure_iteration_allowed
    super(&block)
  end

  def step(n = 1, &block)
    ensure_iteration_allowed
    super(n, &block)
  end

  private
  def ensure_iteration_allowed
    if first.is_a?(Time)
      raise TypeError, "can't iterate from #{first.class}"
    end
  end
end

Range.send(:prepend, RangeSupportingTimeWithZone)

It’s cleaner, Range#each doesn’t get renamed, and ensure_iteration_allowed isn’t monkey-patched in.

Use inheritance, not patches

Ruby gives you a ton of flexibility, and that’s one of the reasons I love it. But it also has a powerful object model. So when you want to inject your own code, try leaning on modules and inheritance before you hack it in. Your code will be much easier to understand and debug, and you’ll avoid some of the harder-to-detect side effects of something like alias_method_chain.

alias_method_chain was one of the coolest methods I was introduced to in Rails. But its days are numbered. We’ve outgrown it. And I’m not going to miss it when it’s gone.

No app survives first contact with actual users. Once people start to use it, they’re going to run into errors.

So, once in production, most apps will have a way of tracking and reporting errors. You could go simple with exception_notification or use a webapp like Honeybadger or Raygun.

But soon, you’ll see the same few exceptions over and over. Maybe a web service you depend on isn’t totally stable. Or the people using your site typo’d their email, so none of your messages are going through. Exceptions should be exceptional, they should be unexpected. But how unexpected can an error be if you see it thirty times a day?

There are better ways of solving these problems than reporting and ignoring. Most noisy exceptions fall into a few basic categories. And for each of these categories, you can use patterns to cut down the noise and make your users happier at the same time.

The network is down!

Few apps work alone. Most communicate with other apps. But when your geolocation API goes down or ec2 has a hiccup, you don’t want to get spammed with thousands of exceptions that you can’t do anything about.

When you deal with unreliable services, try the Circuit Breaker pattern, from Michael Nygard’s Release It:

The basic idea behind the circuit breaker is very simple. You wrap a protected function call in a circuit breaker object, which monitors for failures. Once the failures reach a certain threshold, the circuit breaker trips, and all further calls to the circuit breaker return with an error, without the protected call being made at all.

So, when a service goes down, you’ll automatically stop trying to connect to it. You’ll just go on without that functionality. You can even make it self-healing, so your app will automatically check the service again after a certain amount of time.

The circuit breaker pattern is designed to prevent cascading failures, but you can also use it to limit exception notifications. With this pattern, you really only need to get notified when it trips and when it fails to recover. It can turn thousands of exceptions into a few. If that’s still too many, you can tell the breaker to only report after it’s failed a few retries in a row. (Or find a different, more reliable service!)

Using this pattern takes work, but it also makes the experience better for your users. Instead of a hard error page, they’ll see a message saying that this feature isn’t working right now, and they should try again later. It’s better information for them, delivered at the right time.

Turns out gmaaaail.com isn’t what you meant

Another kind of exception I see a lot comes from bad user data.

For example, say someone typo’d their email when they signed up. They said, “justinweiss@gmaill.com”, but meant “justinweiss@gmail.com”. The first could theoretically be a valid email address, but all the emails you send bounce. And you get notified about those bounces by your email provider.

These notifications are just noise.

Instead, take a two-sided approach. Prevent the bad data up-front, and disable the feature and notify the user if it fails later on.

For email, I’ve used the mailcheck-js gem to spellcheck things like “gmail.com” and “yahoo.com” when new users register:

Then, if an email still bounces later on, turn off email to that user.

Once you turn the feature off for someone, you also need to tell them that it’s disabled and how to fix it. A banner on the top of the site is usually a good answer. Something like “We weren’t able to send your last few emails, so we’ve turned off sending emails to you. Click here to update your email address, and we’ll turn them right back on.”

You’ll get better data, and the user’s emails won’t just go into the void. Way better than those errors you’ve just been ignoring.

404s and RoutingErrors

You probably want to know about broken links or assets on your site. But those things don’t belong in your exception tracker.

For these, and other “half-expected errors”, batch them up and handle them all at once. You don’t need to get notified about them as they happen. You want pull, not push.

Things like RoutingErrors and 404s can be handled with something like Google Webmaster Tools, which will show you the pages Google knows about that are throwing 404s. Or you could run something like link-checker to check the links on your site as part of your pre-release process.

Exceptions should be actionable

It should be rare to get an exception email. Too much noise in your error tracker will keep you from seeing and fixing real problems right away.

If you’re more annoyed than embarrassed about the exceptions you see, you have a noise problem. Use the patterns here to cut down on that noise and give your users a better experience at the same time.

I’ve talked about a few of the noisy exception categories I’ve seen most often. But I’m sure I haven’t seen them all. Which exceptions annoy you the most in your apps? Do they fit into any of these categories, or do they define a new one? How do you keep them from bothering you a few hundred times a day?