return while rendering partial collections

Ruby's next keyword only works in the context of a loop or enumerator method.

So if you're rendering a collection of objects using Rails render partial: collection, how do you skip to the next item?

Since partials are compiled into methods in a dynamically generated view class, you can simulate next by using an explicit return statement. It will short-circuit the rendering of your partial template and iteration will continue with the next element of the collection.

For example

# app/views/users/_user.haml
- return if user.disabled?
  %li[user]
    rest of your template...

Using default_url_options in RSpec with Rails 5

There's a long-standing bug in the integration of controller testing into RSpec that prevents you from easily setting default_url_options for your controller specs. As far as I can tell, it doesn't get fixed because the RSpec teams considers the problem a bug in Rails, and the Rails team does not care if RSpec breaks.

I'm talking about the issue you run into when you're trying to work with locale settings passed into your application as namespace variables in routes.rb like this:

scope "/:locale" do
    devise_for :users,  #...and so on

Today I learned that the inability to set a default :locale parameter can be maddening. Your specs will fail with ActionView::Template::Error: No route matches errors:

1) Devise::RegistrationsController POST /users should allow registration
     Failure/Error: %p= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token)

     ActionView::Template::Error:
       No route matches {"action":"show","confirmation_token":"pcyw_izS8GchnT-R3EGz","controller":"devise/confirmations"} missing required keys: [:locale]

The reason is that ActionController::TestCase ignores normal settings of default_url_options in ApplicationController or your config/environments/test.rb. No other intuitive attempt at a workaround worked either. Frustratingly, it took me around an hour to debug and come up with a monkeypatch-style workaround. The existing workarounds that I could find online are all broken in Rails 5.

So here it is:

# spec/support/fix_locales.rb
ActionController::TestCase::Behavior.module_eval do
  alias_method :process_old, :process

  def process(action, *args)
    if params = args.first[:params]
      params["locale"] = I18n.default_locale
    end
    process_old(action, *args)
  end
end

Note the assumption that you are passing params in your spec using a symbol key and not the string "params".

FactoryGirl, WebMock, VCR, Fog and CarrierWave

In the interest of fast suite runs (amongst other reasons) you want to make sure that your specs are not dependent on remote servers as they do their thing. One of the more popular ways of achieving this noble aim is by using a gem called WebMock, a library for stubbing and setting expectations on HTTP requests in Ruby.

The first time you use WebMock, code that calls external servers will break.

WebMock::NetConnectNotAllowedError:
       Real HTTP connections are disabled. Unregistered request: GET https://nueprops.s3.amazonaws.com/test...

       You can stub this request with the following snippet:

       stub_request(:get, "https://nueprops.s3.amazonaws.com...

Now maintaining that stub code is often painful, so you probably want to use a gem called VCR to automate the process. VCR works really well. After instrumenting your spec correctly, you run it once to generate a cassette, which is basically a YAML file that captures the HTTP interaction(s) of your spec with the external servers. Subsequent test runs use the cassette file instead of issuing real network calls.

Creation and maintenance of cassettes that mock interaction with JSON-based web services is easy. Services that talk binary? Not so much. And almost every modern Rails project I've ever worked on uses CarrierWave (or Paperclip) to handle uploads to AWS. If you try to use VCR on those requests, you're in for a world of annoyance.

Enter Fog, the cloud-abstraction library that undergirds those uploader's interactions with AWS S3. It has a somewhat poorly documented, yet useful mock mode. Using this mode, I was able to make WebMock stop complaining about CarrierWave trying to upload fixture files to S3.

However, the GET requests generated in my specs were still failing. Given that I'm using the venerable FactoryGirl gem to generate my test data, I was able to eventually move the stub_request calls out of my spec and into a better abstraction level.

factory :standard_star do
  sequence(:name) { |n| "Cat Wrangler #{n}" }
  description "Excellence in project management of ADD people"
  icon { Rack::Test::UploadedFile.new('spec/support/stars/cat-wrangler.jpg') }
  image { Rack::Test::UploadedFile.new('spec/support/stars/cat-wrangler.jpg') }
  after(:create) do |s, e|
    WebMock.stub_request(:get, "https://nueprops.s3.amazonaws.com/test/uploads/standard_star/image/#{s.name.parameterize}/cat-wrangler.jpg").
             to_return(:status => 200, :body => s.image.file.read)

    WebMock.stub_request(:get, "https://nueprops.s3.amazonaws.com/test/uploads/standard_star/icon/#{s.name.parameterize}/cat-wrangler.jpg").
             to_return(:status => 200, :body => s.icon.file.read)

  end
end

When counter_cache on wrong side of association

Absentmindedly put a counter_cache declaration on the has_many instead of where it belongs (pun intended.)

Rails 5 will complain in the most cryptic way it possibly can, which is to raise the following exception

ActiveModel::MissingAttributeError: can't write unknown attribute `true`

If you get that error, now you know how to fix it. Good luck and godspeed.

Rails 5 Attributes API + JSONb Postgres columns

As of when I'm writing this (Jan 2017), support for using ActiveRecord store with Postgres JSONb columns is a bit of shit-show. I'm planning to help fix it as soon as I have some time to spare, but for the moment if you want a better way of supporting these valuable column types in your Rails 5 app, use the new Attributes API. Plus get much improved performance with the Oj gem.

Here's how to make it work. First, define a :jsonb type to replace the native one.

class JsonbType < ActiveModel::Type::Value
  include ActiveModel::Type::Helpers::Mutable

  def type
    :jsonb
  end

  def deserialize(value)
    if value.is_a?(::String)
      Oj.load(value) rescue nil
    else
      value
    end
  end

  def serialize(value)
    if value.nil?
      nil
    else
      Oj.dump(value)
    end
  end

  def accessor
    ActiveRecord::Store::StringKeyedHashAccessor
  end
end

Next, register it in an initializer.

ActiveRecord::Type.register(:jsonb, JsonbType, override: true)

Note that the JsonbType class will need to be somewhere in your loadpath.

Now just declare the attribute at the top of your ActiveRecord model like this:

class User < ApplicationRecord
  attribute :preferences, :jsonb, default: {}

ActiveRecord test objects made easy

If you're testing a ActiveRecord model mixin in your application, you might be tempted to unit test it in the context of one of your app's models. However, that would violate your test isolation and introduce complexities related to the behavior of the model.

Better solution is to make an Active Record class just for your test, and the fact that you can invoke schema definitions on the fly makes it super easy. Here's the top of one of my specs, illustrating the technique.

require 'rails_helper'

ActiveRecord::Schema.define do
  create_table :test_objects, force: true do |t|
    t.jsonb :jobs, null: false, default: {}
  end
end

class TestObject < ApplicationRecord
  include WorkerRegistry
end

RSpec.describe WorkerRegistry do
  let(:test_object) { TestObject.create }

  ...

Adding your own datetime formats to Rails

An aspect of Rails that I adore is how it has a place for nearly everything you need to do. One of those things is to format dates/times using the strftime method. Instead of tucking away custom strftime patterns in constants, you can configure them onto the native Rails formatter, accessed via time.to_s(:format_name)

DateTime formats are shared with Time and stored in the Time::DATE_FORMATS hash. Use your desired format name as the hash key and either a strftime string or Proc instance that takes a time or datetime argument as the value.

# config/initializers/time_formats.rb
Time::DATE_FORMATS[:month_and_year] = '%B %Y'
Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }

Here's one of the formats that I've been using lately, to get my times into a more familiar form.

Time::DATE_FORMATS[:short_time] =
   lambda { |time| time.strftime('%I:%M%p').gsub('AM','am').gsub('PM','pm').gsub(':00','') }

Easily add Toastr flash notices to Rails apps

I learned about Toastr JavaScript library last week and have been delighted to use it instead of more traditional flash messaging.

First of all get the Toastr sources. I opted to link to them on CDNJS:

= stylesheet_link_tag 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.css'
= javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.3/toastr.min.js'

Next I defined some extra flash types in my application_controller.rb file to match Toastr's native notification types and enable use of the built-in styling.

class ApplicationController < ActionController::Base
  add_flash_types :success, :info, :warning, :error
  ...

Finally, add the following block of JavaScript to the bottom of a layout template (or whatever shared partial that contains your JS and CSS includes.

- flash.keys.each do |key|
  - toastr_key = key
  - toastr_key = 'info' if key == 'notice'
  - toastr_key = 'warning' if key == 'alert'
  :javascript
    $(function() {
      toastr["#{toastr_key}"]("#{flash[key]}");
    });

Lines 2 and 3 establish a mapping from conventional Rails notice and alert so that I don't have to hack libraries like Devise which rely on them.

Easy.

Don't roll your own slug code, use FriendlyId

This finding was a pleasant surprise. For years, I've been writing the same kind of boilerplate code to override to_param on my model classes and generate unique slugs. Turns out there's a really well-written library that does that, with some worthwhile additional functionality. Check out FriendlyId for easy slug generation and even the ability to preserve history of slugs after changes, so that it's possible to do 301 redirects with just a couple lines of code.

Use fetch method to access ENV variables in Ruby

It's better for at least a couple of reasons, eloquently stated in this blog post by Michal Orman

...we can set default values or handle - providing a block - gracefully missing keys. Also using fetch with unknown key will raise KeyError that will tell us which exactly key is missing. That is in fact the behavior we are expecting from the app. Without required settings is just not working and complaining about missing setting and not about some random nil references.

Got that? Instead of this:

AWS.config(
  access_key_id:      ENV['S3_ACCESS_KEY'],
  secret_access_key:  ENV['S3_SECRET_KEY'],
  region:             ENV['S3_REGION']
)

Do this:

AWS.config(
  access_key_id:      ENV.fetch('S3_ACCESS_KEY'),
  secret_access_key:  ENV.fetch('S3_SECRET_KEY'),
  region:             ENV.fetch('S3_REGION')
)