Mocking Rails, for SPEED

DISCLAIMER: This article is by no means related to the recent quarrel about TDD’s death (be it presumed or actual), nor to this article or these hangouts.

:trollface:

Now that we’re clear let us get to the real title:

How I test Rails stuff without loading Rails and learned to use mkdir

Chapter 1: mkdir1

this chapter is quite brief and straightforward…

Starting from Rails 3 every folder you create under app will be used for autoloading, this means that you can group files by concern2 instead of grouping them by MVC role, or you maybe want to add new “roles”.

For example the app I’m currently working on contains looks like this:

app/
├── admin
├── assets
├── authentication
├── authorization
├── controllers
├── forms
├── helpers
├── journaling
├── mailers
├── models
├── proposals
├── utils
└── views

The journaling folder contains journal.rb (a simple Ruby class) and journal_entry (an ActiveRecord model).

Chapter 2: Rails not required

Talking about that Journal, it doesn’t really need Rails to be used, it’s ok with any JournalEntry-ish object that exposes this API:

class FauxJournalEntryClass < Struct.new(:difference, :user, :model_type, :model_id, :action)
  def save!
    @persisted = true
  end

  def persisted?
    @persisted
  end
end

So it can be used and tested without loading rails, that gives opportunity to have the corresponding spec to give us really fast feedback. Enter a lighter version of our spec_helper:

# spec/spec_helper.rb
RSpec.configure do |config|
  # yada, yada, rspec default config from `rspec --init`
end

along with a speedy spec:

require 'light_spec_helper'
require File.expand_path('../../app/journal/journal', __FILE__)

describe Journal do
  # examples here...
end

and have the normal spec_helper to load the light one:

# spec/spec_helper.rb
require 'light_spec_helper'
# stuff coming from `rails generate rspec:install`...

Chapter 3: Mocking Rails

…without making fun of it

With time i often found myself in the need to add a logger, to check current environment or to know the app’s root from this kind of plain classes.

In a Rails app the obvious choices are Rails.logger, Rails.env and Rails.root. In addition I really can’t stand that File.expand_path and light_spec_helper, they’re just ugly.

My solution was to:

  • have a faux Rails constant that quacks like Rails in all those handy methods
  • add the most probable load paths into the light spec_helper
  • rename the the rails-ready spec_helper to rails_helper and keep the light helper as the default one

Here’s how it looks:

# spec/spec_helper.rb
require 'bundler/setup'
require 'pathname'

unless defined? Rails
  module Rails
    def self.root
      Pathname.new(File.expand_path("#{__dir__}/.."))
    end

    def self.env
      'test'
    end

    def self.logger
      @logger ||= begin
        require 'logger'
        Logger.new(root.join("log/#{env}.log"))
      end
    end

    def self.cache
      nil
    end

    def self.fake?
      true
    end

    def self.implode!
      class << self
        %i[fake? cache logger env root implode!].each do |m|
          remove_method m
        end
      end
    end
  end
end

$:.unshift *Dir[File.expand_path("#{Rails.root}/{app/*,lib}")]

RSpec.configure do |config|
  # yada, yada, rspec default config from `rspec --init`
end

Notice anything of interest?
I bet you already guessed what Rails.implode! does…

Here’s the shiny new rails_helper:

ENV["RAILS_ENV"] ||= 'test'
require 'spec_helper'
Rails.implode! if Rails.respond_to? :implode!

require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join('spec/support/**/*.rb')].each do |f|
  f = Pathname(f).relative_path_from(Rails.root.join('spec'))
  require f
end

ActiveRecord::Migration.maintain_test_schema! # rails 4.1 only

RSpec.configure do |config|
  # stuff coming from `rails generate rspec:install`...
end

Conclusion

The introduction of Spring in Rails 4 has actually made all this stuff useless, running RSpec through it makes it blazingly fast.

Sorry if I wasted your time and I didn’t even make you smile.


  1. I don’t actually use mkdir, I know how to use my editor 
  2. Not those concerns 

Preview emails for any user with Rails 4.1

Last night I was preparing a weekly email to remind my dear TimeSampler users about the work they have done in their previous week.

I started by adding a couple of previews one for the user that has actual stuff done (see an example below):

Weekly email for user with registered bursts

Setup a mailer (you can skip this)

The first thing you need to preview an email is to have a Mailer (yuk!), here’s a simple one:

# app/mailers/user_notifier

class UserNotifier < ActionMailer::Base
  default from: 'weekly-report@timesampler.com', reply_to: 'elia+timesampler-weekly-report@schito.me'
  helper DayHelper

  def weekly_report user
    @user         = user
    @project_days = project_days
    @days         = project_days.group_by(&:day).to_a.sort
    @end_time     = end_time
    @start_time   = start_time

    mail to: user.email, subject: "TimeSampler: Weekly activity report · week #{start_time.cweek}"
  end
end

And its view:

/ app/views/user_notifier
= render 'style'

%h1 TimeSampler: Weekly activity report · week #{@start_time.cweek}

%p
  Hi #{@user.some_name}!

- if @project_days.any?
  %p
    Here's what you've done during the past week (#{link_to "from #{@start_time} to #{@end_time}", journal_path}):
  .days
    - @days.sort.reverse.each do |(day, project_days)|
      = render 'day', day: day, project_days: project_days
- else
  = render 'blank_slate'

= render 'footer'

Setup a previewer

Then we need to add our previewer. Problem is that the default location for the previewers is test/mailers/previews
and as you may have guessed already we have no test dir in this app, instead we’ll put our
ActionMailer::Preview classes inside app/mailer_previews.

To do that let’s add the following line to config/environments/development:

Rails.application.configure do
  # …
  config.action_mailer.preview_path = "#{Rails.root}/app/mailer_previews"
end

Your previewer could be something like this:

class UserNotifierPreview < ActionMailer::Preview
  def weekly_report_empty
    UserNotifier.weekly_report(andrea)
  end

  def weekly_report_full
    UserNotifier.weekly_report(elia)
  end

  # …s
end

And have beautyful previews for your emails. For example, this is how the blankslate TimeSampler mail looks like:

blank slate mail preview

POWER PREVIEWING !!1!

Now what if you want to preview emails for arbitrary users without having to manually update the code?

No problem, luckly it’s Ruby! After a bit of inspection inside actionmailer and railties I found the right hook…

WARNING: rails hackery ahead

Rails asks the previewer if an email_exists? before trying to display it, so we’ll hook into that method and
auto-define a previewer instance method for each user in out system (please don’t try this with more than a hundred users).

Here teh codez:

class UserNotifierPreview < ActionMailer::Preview
  def self.email_exists?(*)
    User.all.each do |user|
      next if user.email.blank?
      email = user.email.gsub(/W/, '_')
      method_name = "weekly_report_#{email}"
      user_id = user.id

      define_method method_name do
        UserNotifier.weekly_report(User.find(user_id))
      end
    end

    super
  end

  # …
end

Of course it’s advisable to be careful and probably reduce the set of users you’re gonna prepare a preview method for.
In my case the development database is quite manageable and hence User.all is fine.

Final Pro-tip

You find cumbersome to dig the sources of the gems in your bundle even
if you setup the $EDITOR var in your bash and are a regular client of bundle open <my-gem>?

Then you can give a try to the Bundler bundle for TextMate2 which will give you an
incredible speed boost while perusing the gems in your current Gemfile.lock.

To get some more info on my TextMate2 setup you can read:

Read next: Pimp my TextMate2 — Ruby edition

opening gems from the current bundle

Pimp my TextMate2 — Ruby edition

Here at Mikamai It’s no secret I’m a happy TextMate user and early TM2 adopter. I’m always there if either an editor war is catching fire or if someone needs help setting up his editor.

Above all I still find that TextMate is the best choice for a Ruby developer, even if SublimeText, emacs and vim seem more fashionable these days. Even if I’m saving the full list of reasons for another post I’ll tell just this one: TextMate relies on Ruby for a big part of its implementation that has always been opensource*.

Of course I’m talking about bundles, if you’re not convinced look at the code used to align assignments (⌥⌘]) from the source bundle (which is responsible for actions common to any programming language).

Just to be clear I would still use SublimeText if I were to program from Linux or (ugh!) Windows.

That said I want to gather here some of the stuff that makes using TextMate2 for Ruby and Rails development so awesome.

* Of course I know about redcar (which seems quite dead, but I didn’t tried it recently) and other TM clones err enhancements like Chocolat

ALERT: shameless self promotion follows

The Bundles and Settings parade

1. Effortless opening of Bundled Gems ⌥⌘O

This I do all the time, opening the source of bundled gems. Please behold and don’t be horrified. Especially in Ruby-land the source of gems is the best source of documentation, and as explained by Glenn Vanderburg) there’s probably a good reason for that. Also the README and specs are included most of the time and reading other’s code is a healthy activity.

Needless to say that the best place to read source code is your editor.

⌥⌘O will present the complete list of gems from your Gemfile.lock, start typing the first letters of a gem and use arrow if you don’t want to touch the mouse (or trackpad).

opening gems from the current bundle

Source: https://github.com/elia/bundler.tmbundle

2. Beautiful Markdown rendering

No README.md reading activity would be on par with a the GFM rendered version without code blocks highlighting.

This bundle almost looks like GFM while typing, press ⌃⌥⌘P (the standard TM key equivalent for preview) to get it rendered to the HTML window.

Redcarpet Markdown Bundle in action

Bonus Install the Scott Web Theme from Preferences → Bundles for a nice looking preview

Source: https://github.com/streeter/markdown-redcarpet.tmbundle

3. Restart Pow! in a single stroke ⌃⌥⌘R

Restarts the current app detecting a tmp/ directory in current project or in a parent dir.

Falls back to /tmp

Source: https://github.com/elia/pow-server.tmbundle

4. Open the terminal in your current project folder

Works with both Terminal and iTerm, just press ⌃⌥⌘T from a project.

Source: https://github.com/elia/avian-missing.tmbundle

5. Trailing whitespace fix, cross-tab completion and more…

Command Description
⌃⎋ Cross tab completion
⌃⌥⌘T Open Project directory in Terminal
⌃⌥⌘L Keep current file as reference

Source: https://github.com/elia/avian-missing.tmbundle

Installing the whole thing

Download the latest version of TextMate2 here: https://api.textmate.org/downloads/release

mkdir -p ~/Library/Application Support/Avian/Bundles
cd ~/Library/Application Support/Avian/Bundles

git clone https://github.com/elia/avian-missing.tmbundle
git clone https://github.com/elia/bundler.tmbundle
git clone https://github.com/streeter/markdown-redcarpet.tmbundle

# Activate the system ruby (if you're using a Ruby version manager):
type rvm &> /dev/null && rvm use system # for RVM
export RBENV_VERSION="system"           # for rbenv

# Install the required gems
sudo gem install redcarpet -v 2.3.0
sudo gem install pygments.rb

# Trailing whitespace
defaults write com.macromates.TextMate.preview environmentVariables -array-add 
    '{ "enabled" = YES; "name" = "TM_STRIP_WHITESPACE_ON_SAVE"; "value" = "true"; }' # enable trailing whitespace removal and EOF fix

echo <<-INI >> ~/.tm_properties
[ "*.y{,a}ml" ]
# Disable trailing whitespace fix for YAML 
# files that can be broken by this feat.
TM_STRIP_WHITESPACE_ON_SAVE = false
INI

The following will set the tabs/file-browser/html-windows to my current taste, I don’t pretend it matches everyone prefs but can still be useful for cherry-picking.

# File browser fixes and general UI fixes
defaults write com.macromates.TextMate.preview fileBrowserStyle SourceList       # lighblue file browser background
defaults write com.macromates.TextMate.preview fileBrowserPlacement left         # keep it on the left
defaults write com.macromates.TextMate.preview tabsAboveDocument -bool YES       # no tabs above the file browser
defaults write com.macromates.TextMate.preview allowExpandingLinks -bool YES     # make symlinks expandable
defaults write com.macromates.TextMate.preview htmlOutputPlacement right         # place the html output to the right
defaults write com.macromates.TextMate.preview disableTabBarCollapsing -bool YES # keep the tab-bar alway visible
defaults write com.macromates.TextMate.preview scrollPastEnd -bool YES           # give me some air after the file ends