---
title: 'Check your return values: the Web'
teaser: 'Learn how to apply a little forethought and discipline to provide your users
  a more informative and stable Web experience.

  '
tags: web,rails
author: Mike Burns
published_on: 2020-08-10
---

The main take-away from this post is to **always check return values**. A
corollary is to **design the user experience to include error messages in a
helpful manner**.

## Design-driven Error Messages

[Design-driven development][ddd] is the art of building a product from the user's
perspective first, then doing the bare minimum to make that work. In a Rails
app that would be HTML/ERb and CSS, then [just enough JavaScript] and
controller glue to make that work, and just enough model and service object
code to make the controllers work. If this sounds obvious, well, you should
have seen what we did before!

[ddd]: https://blog.prototypr.io/design-driven-development-36a30dd8088c
[just enough JavaScript]: https://www.smashingmagazine.com/2009/04/progressive-enhancement-what-it-is-and-how-to-use-it/

When following a design-driven development paradigm, it's easy to excitedly
plan out the happy path, ignoring all the error points. This is one of the
reasons we find value in pairing an experienced developer with our designers
during prototyping or product design sprints: they often know, from experience,
what kinds of errors to expect from various flows.

As a common example, when navigating from the login page to the dashboard --
when a user signs in -- there is a chance that the user will fail to
authenticate correctly and must be told this. This means that the login form
needs a place for an error message. Since this is a security-related concern,
thought must be given not only to the error message itself (don't report "you
left the `t` off the end of your password"), but also to the placement of the
message: if you flag the "password" field as the error, that reveals that the
user has an account on the system. That is both a privacy concern and also an
indicator that the attacker is onto something and should keep trying passwords
for this username.

As a more tricky example, consider a more complex flow: a mobile app
communicating with a HTTP API, with a request that has both a synchronous
component and an asynchronous, background job. Let's take a cart checkout flow,
since those are so common: the user enters their payment details, presses
"Buy", and is shown their receipt. So much can happen in between: the payment
details could be expired; the payment processor could have an error; the
product could go out of stock; the payment authorize could succeed
(synchronously) but the capture fails (asynchronously), leading to a
synchronous "everything OK" response but later a failure asynchronous response;
the phone's network connection could drop with a successful payment processed
by the server, but no response coming back within a timeout. On top of all
this, the user might close their app at any time.

These are just some examples, to highlight the importance of not only
[communicating a useful error message][designing for failure] to the user in an
appropriate manner, but also considering all the failure points during the
initial design process.

From here on we'll get much more technical. We will consider the user
authentication example in more depth, and then the payment checkout example for
further refinement of the idea.

[designing for failure]: https://thoughtbot.com/blog/designing-for-failure

## The exception's last stand

In order to show the error message in the view, you have to handle it in the
controller.

Let's talk Rails. In a modern-day Rails app there are a few "just before the
view" points: the controller action, the mailer action, [the GraphQL resolver],
the background job command, the Rack middleware, and more. These are methods
that orchestrate the model/service layer and the rendering layer. And it is
here where the error message must coalesce. Here is the final resting point of
all the chaos that was required to perform the user's action, and now we must
prepare the result as if it were effortless.

[the GraphQL resolver]: https://graphql-ruby.org/

It is here -- and, with some exceptions, here alone -- that you must rescue
exceptions. It is here that you must check the return values of your model
and service methods.

The first example is authentication. You and the design team come up with a
standard sign-in form:

<p><img
src="https://images.thoughtbot.com/handle-failure/DJ79c5SQYeM052p1nujG_68747470733a2f2f696d616765732e74686f75676874626f742e636f6d2f68616e646c652d6661696c7572652f5175444142516c7653556163517255645a694f685f7369676e2d696e2e706e67-1x.png"
srcset="https://images.thoughtbot.com/handle-failure/QuDABQlvSUacQrUdZiOh_sign-in.png 2x"
alt="a sign in screen with email, password, and a button"></p>

After a conversation on error handling, this is the result:

<p><img
src="https://images.thoughtbot.com/handle-failure/vW49fNacRASJX9fr50nW_68747470733a2f2f696d616765732e74686f75676874626f742e636f6d2f68616e646c652d6661696c7572652f6b7155473869744b546b714b4e533847726d62615f7369676e2d696e2d6572726f722e706e67-1x.png"
srcset="https://images.thoughtbot.com/handle-failure/kqUG8itKTkqKNS8Grmba_sign-in-error.png 2x"
alt="a sign in screen with a global message, ambigous about the specifics of the error"></p>

Your controller has two actions: a `new` that renders the login form, and a
`create` that orchestrates the authentication. The `create` fetches a user by
the params, sets the session, and redirects. The `new` form can show an error
from the flash.

```ruby
class SignInsController < ApplicationController
  def new
  end

  def create
    if user = User.authenticated_by(user_params)
      session[:user_id] = user.to_param
      redirect_to dashboard_path
    else
      flash.now[:error] = t(".unknown_or_mismatched_account")
      render :new
    end
  end

  private

  def user_params
    params.require(:user).permit(:email, :password)
  end
end
```

Note that conditional: `if User.authenticated_by(user_params)`. You
don't assume that the email and password match; instead you first check the
return value from `User.authenticated_by`.

The user interface drove the error reporting: to avoid leaking information,
there is one place in the UI for an error message, and all it can say is
"unknown account or incorrect password".

This "just before the view" point looks great, but what lurks below?

## Bubbles

The `User.authenticated_by` method passes work off to the BCrypt library. These
external sources _can_ raise exceptions. Let's look at the code then talk
through the exceptions.

<figure>

<div class="highlight"><pre class="highlight ruby"><code><span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>&#x000A;  <span class="c1">##</span>&#x000A;  <span class="c1"># Return the authenticated user.</span>&#x000A;  <span class="c1">#</span>&#x000A;  <span class="c1"># @param [Hash] args The authentication credentials.</span>&#x000A;  <span class="c1"># @option args [String] email The user-entered email address.</span>&#x000A;  <span class="c1"># @option args [String] password The user-entered password.</span>&#x000A;  <span class="c1"># @return a [User], or +nil+ if no matching user exists.</span>&#x000A;  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">authenticated_by</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>&#x000A;    <span class="n">user</span> <span class="o">=</span> <span class="n">find_by</span><span class="p">(</span><span class="ss">email: </span><span class="n">args</span><span class="p">[</span><span class="ss">:email</span><span class="p">)</span>&#x000A;    <span class="n">enc_passwd</span> <span class="o">=</span> <span class="no">BCrypt</span><span class="o">::</span><span class="no">Password</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="ss">:password</span><span class="p">])</span>&#x000A;&#x000A;    <span class="k">if</span> <span class="n">enc_passwd</span> <span class="o">==</span> <span class="n">user</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">encrypted_password</span>&#x000A;      <span class="n">user</span>&#x000A;    <span class="k">end</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre></div>

<figcaption>The method uses the `ActiveRecord::Base.find_by` method to pick the
user by email address, then the lower-level BCrypt methods to compare the
password. If they match, return the user; otherwise return `nil`.</figcaption>
</figure>

This calls three methods: [`#find_by`] in `ActiveRecord`,
[`BCrypt::Password.create`], and [`BCrypt::Password#==`].

[`#find_by`]: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/finder_methods.rb#L80
[`BCrypt::Password.create`]: https://github.com/codahale/bcrypt-ruby/blob/master/lib/bcrypt/password.rb#L43
[`BCrypt::Password#==`]: https://github.com/codahale/bcrypt-ruby/blob/master/lib/bcrypt/password.rb#L65

The `#find_by` method [returns `nil`] if it cannot find the desired row; this
is in comparison with `#find`, which [raises `ActiveRecord::RecordNotFound`].
The case in which `#find_by` returns `nil` is where the desired email address
is not in our database; this is a user error, and therefore should be reported.
We signal this by returning `nil`.

[returns `nil`]: https://github.com/rails/rails/blob/598dc9f147e6153a461d6707ca0bf6e356e321ad/activerecord/lib/active_record/relation/finder_methods.rb#L521 "that's Enumerable#first"
[raises `ActiveRecord::RecordNotFound`]: https://github.com/rails/rails/blob/598dc9f147e6153a461d6707ca0bf6e356e321ad/activerecord/lib/active_record/relation/finder_methods.rb#L440

The `#find_by` method can raise an exception, however! For example, the `users`
table could be missing, the database connection could have dropped, or any
other database-related, system-level error might occur. These will all [raise an
instance of `PG::Error`]. We choose to rely on the ActionPack `rescue_from`
defaults in our controller, instead of handling the error manually. It is
important to note that the error is handled by Rails ([in the form of
`Exception`]), and that we can rely on it; otherwise, this exception would
crash our app for all users.

[raise an instance of `PG::Error`]: https://github.com/ged/ruby-pg/blob/6853309b64852755daed3e9c24e11de13b4c77a4/ext/pg_result.c#L276
[in the form of `Exception`]: https://github.com/rails/rails/blob/6cbd81c383f17b2790fbff9d06c091bcadb82ece/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L34

Philosophically, the reason we choose not to care about the `PG::Error` when
calling `User.authenticated_by` is because it represents a programmer error.
Nothing in the user flow specifies showing the user an unexpected,
programmer-caused error message. Therefore we can rely on Rails to swallow it
for us (and [our exception tracker] to notify us as appropriate), and otherwise
we can ignore it in our "just before the view" code.

[our exception tracker]: https://thoughtbot.com/blog/airbrake-acquired-by-exceptional

Similar thoughts can be said about `BCrypt::Password.create` and
`BCrypt::Password#==`. The `.create` method always returns an object wrapping
the hashed password, when given an object that responds to `#to_s` (such as a
String). The `#==` comparison method always returns a truthy value when given a
stringable argument. We depend on them performing as advertised for our input.

The `.create` and `#==` methods can raise, though. If you pass an optional
`:cost` argument to `.create`, [it can raise] if you give it an out-of-range
value; a programmer error to be caught by the test-driven development process,
and not exposed to the user. Similarly both can raise if passed [an object that
does not respond to `#to_s`] or if the underlying BCrypt C library [fails to
generate a valid salt]. There's no recovering from this: these are issues that
will block the user entirely, and it is up to us to let these programmer errors
fall through to the last-resort error handling. We do this with intention: we
have weighed the exceptions and how they can appear, and decided that the UI
calls for them to continue to bubble up.

[it can raise]: https://github.com/codahale/bcrypt-ruby/blob/master/lib/bcrypt/password.rb#L45
[an object that does not respond to `#to_s`]: https://github.com/codahale/bcrypt-ruby/blob/2fbe5418b4fcb8662a6984690796d43489fba3dc/lib/bcrypt/engine.rb#L53
[fails to generate a valid salt]: https://github.com/codahale/bcrypt-ruby/blob/2fbe5418b4fcb8662a6984690796d43489fba3dc/lib/bcrypt/engine.rb#L77

This completes the first example. A user can attempt to authenticate and can be
shown failures they can fix, general status errors, and system errors, in a
secure manner.

## Intermezzo

Error handling [starts in the design][], requires the collaboration of the UX
and development team, and often requires more complicated logic.

In [the next section] we'll dive into the API backing a mobile checkout
experience, combining synchronous and asynchronous processing.

[starts in the design]: https://www.bbc.co.uk/gel/guidelines/how-to-write-useful-error-messages
[the next section]: https://thoughtbot.com/blog/check-return-values-api
