Verifying doubles in RSpec 3

RSpec 3 has verifying doubles. This breed of mock objects check that any methods being stubbed are present on an instance of a given class. They also check methods aren't called with the wrong number of arguments.

This dual approach allows you to move very quickly and test components in isolation, while
giving you confidence that your doubles are not a complete fiction.

You should always prefer using a verifying double to using an old-school mock...

How to find out which type of Spec you are

When you need to find out in which kind of spec you are during run-time, it's definitely possible. It's a lot easier in RSpec 2+.

For example, consider this global before block where you'd want to run some code for specific specs only:

config.before do
  # stuff
  that_fancy_method
  # more stuff
end

RSpec 2+

If you want to run such a block for a specific type of specs, you can use filters:

config.before do
  # stuff
  # more stuff
end

config.before :type =...

Adding Jasmine JavaScript specs to a Webpack(er) project

The goal is to get Jasmine specs running in a Rails project using Webpacker, with the browser based test runner. Should be easily adaptable to a pure Webpack setup.

Image

Step 1: Install Jasmine

yarn add jasmine-core

Step 2: Add two separate packs

Since we do not want to mix Jasmine into our regular Javascript, we will create two additional packs. The first only contains Jasmine and the test runner. The second will contain our normal application code and the specs themselves.

We cannot...

Choosing the right gems for your project

Adding a gem means you take over the liability towards the external code.

Checklist

Based on "To gem, or not to gem":

  • Gem is really needed (prefer writing your own code for simple requirements without many edge cases)
  • Gem is tested well (coverage and quality)
  • Gem has a good code quality
  • Gem's licence fits to the project requirement
  • Try to avoid gems that do much more than your requireme...

Heads up: pg_restore --clean keeps existing tables

When restoring a PostgreSQL dump using pg_restore, you usually add the --clean flag to remove any existing data from tables.

Note that this only removes data from tables that are part of the dump and will not remove any extra tables. You need to do that yourself.

When is this relevant?

As an example: You want to load a staging dump into your development machine. On your development machine, you have run migrations that introduced more tables which do not yet exist on staging. pg_restore with --clean will loa...

Rspec: around(:all) and around(:each) hook execution order

Background

  • before(:all) runs the block once before all of the examples.
  • before(:each) runs the block once before each of your specs.

Summary

  • around(:suite) does not exist.
  • around(:all) runs after before(:all) and before after(:all).
  • around(:each) runs before before(:each) and after after(:each).

As this is not 100% obvious (and not yet documented) it is written down in this card. In RSpec 3 :each has the alias :example and :all the alias :context.

Example

RSpec.configure do |config|
  ...

Generating and streaming ZIP archives on the fly

When your Rails application offers downloading a bunch of files as ZIP archive, you basically have two options:

  1. Write a ZIP file to disk and send it as a download to the user.
  2. Generate a ZIP archive on the fly while streaming it in chunks to the user.

This card is about option 2, and it is actually fairly easy to set up.

We are using this to generate ZIP archives with lots of files (500k+) on the fly, and it works like a charm.

Why stream downloads?

Offering downloads of large archives can be cumbersome:

  • It takes time to b...

Don't require files in random order

A common pattern in Ruby is to to require all files in a specific diretory, using something like

Dir.glob(Rails.root.join('lib/ext/**/*.rb')).each do |filename|
  require filename
end

However, this causes files to be required in an order determined by the file system. Since load order can be important, this may lead to different behavior on different machines which are hard to debug.

Simply add a .sort:

Dir.glob(Rails.root.join('lib/ext/**/*.rb')).sort.each do |filename|
  require filename
end

Ruby 3

...

Cucumber: Identifying slow steps that drag down your test speed

In most projects I know, Cucumber test suite speed is not an issue. Of course, running 350 features takes its time, but still each test for itself is reasonably fast. There is nothing you can do to fundamentally speed up such a test (of course, you should be using parallel_tests).

However, in projects that go beyond clicking around in a web GUI and checking results, there might be various measures to speed things up. Katapult tests for example could be sped up more than 4 times by re...

Enabling view rendering for controller specs

Views are normally (for good reason) not rendered in controller specs. If you need it to happen, use:

RSpec 1 (Rails 2):

integrate_views

RSpec 2 (Rails 3):

render_views

Note that you can't use that inside it blocks but need to put it in the nesting example group, like this:

    describe '#update' do
      cont...

Capistrano 3: Running a command on all servers

This Capistrano task runs a command on all servers.

bundle exec cap production app:run cmd='zgrep -P "..." RAILS_ROOT/log/production.log'

Code

# lib/capistrano/tasks/app.rake

namespace :app do

  # Use e.g. to grep logs on all servers:
  #   b cap production app:run_cmd cmd='zgrep -P "..." RAILS_ROOT/log/production.log' 
  #
  # * Use RAILS_ROOT as a placeholder for the remote Rails root directory.
  # * Append ` || test $? =1;` to grep calls in order to avoid exit code 1 (= "nothing found")
  # * To be able to process ...

Carrierwave: How to remove container directories when deleting a record

When deleting a record in your Rails app, Carrierwave automatically takes care of removing all associated files.
However, the file's container directory will not be removed automatically. If you delete records regularly, this may be an annoyance.

Here is a solution which was adapted from the Carrierwave GitHub wiki and cleans up any empty parent directories it can find.

class ExampleUploader < CarrierWave...

Heads up: RSpec-Mocks' #stub_const will define intermediate modules that have not been loaded yet

The issue: You are using stub_const to change a constant value for your test.

stub_const "SomeClass::CONST", 'test'

All of a sudden, tests fail with undefined method 'some_method' for #<SomeClass:0x00000000101433a8>.

The reason

When using stub_const before the Class containing the constant has been loaded, a module is automatically created with the name.

Since RSpec does no autoloading, it will create a SomeClass module by itself. This is arguably a good idea.

As a workaround, use stub_const in your Rails specs li...

RSpec: Inferring spec type from file location

RSpec Rails can automatically mix in different behaviors to your tests based on their type tag, for example enabling you to call get and
post in specs with the tag type: :request.

Alternatively you can skip these tags by setting the config config.infer_spec_type_from_file_location! in the spec_helper.rb. This will automatically choose the right type context based on the file location of the test.

For instan...

Why has_many :through associations can return the same record multiple times

An association defined with has_many :through will return the same record multiple times if multiple join models for the same record exist (a n:m relation). To prevent this, you need to add ->{ uniq } as second argument to has_many (below Rails 4 it is a simple option: has_many :xyz, :uniq => true).

Example

Say you have an Invoice with multiple Items. Each Item has a Product:

class Invoice < ActiveRecord::Base
  has_many :items
  has_many :products, :through => :items
end

class Item < ActiveRecord::Base
  ...

How to update the bundler version in a Gemfile.lock

  1. Install the latest bundler version:

    gem install bundler
    Fetching bundler-2.3.5.gem
    Successfully installed bundler-2.3.5
    1 gem installed
    
  2. Update the bundler version in Gemfile.lock:

    bundle update --bundler  
    
  3. Confirm it worked:

    $ tail -n2 Gemfile.lock 
    BUNDLED WITH
      2.3.5
    

Notes:

  • Bundler should automatically detect the latest installed version. If it does not, you can specify your preferred version like so:

    b...
    

Testing ActiveRecord callbacks with RSpec

Our preferred way of testing ActiveRecord is to simply create/update/destroy the record and then check if the expected behavior has happened.

We used to bend over backwards to avoid touching the database for this. For this we used a lot of stubbing and tricks like it_should_run_callbacks.

Today we would rather make a few database queries than have a fragile test full of stubs.

Example

Let's say your User model creates a first Project on cr...

Migration from the Asset Pipeline to Webpacker

This is a short overview of things that are required to upgrade a project from the Asset Pipeline to Webpacker. Expect this upgrade to take a few days even the diff is quite small afterwards.

Preparations

1. Find all libraries that are bundled with the asset pipeline. You can check the application.js and the application.css for require and import statements. The source of a library is most often a gem or a vendor directory.
2. Find an working example for each library in the application and write it down.
3. Find out the ver...

Heads up: Quering array columns only matches equally sorted arrays

Given you have an array column like this:

create_table "users", force: :cascade do |t|
  t.integer "movie_ids", default: [], array: true
end

You might think that the following queries yield the same result:

User.where(movie_ids: [16, 17])
User.where(movie_ids: [17, 16])

Turn's out - they are not! They do care about array ordering more than I do.

To query for identical arrays independent of their order you have to either:

  1. Sort both the query and database content. If you're on Rails 7.1 you can use the new [`normal...

Use DatabaseCleaner with multiple test databases

There is a way to use multiple databases in Rails.
You may have asked yourself how you're able to keep your test databases clean, if you're running multiple databases with full read and write access at the same time. This is especially useful when migrating old/existing databases into a new(er) one.

Your database.yml may look like this:

default: &default
  adapter: postgresql
  encoding: unicode
  username: <%= ENV['DATABASE_USER'] %>
  host: <%= ENV['DATABASE...

When reading model columns during class definition, you must handle a missing/empty database

When doing some meta-programming magic and you want to do something for all attributes of a class, you may need to access connection or some of its methods (e.g. columns) during class definition.

While everything will be fine while you are working on a project that is in active development, the application will fail to boot when the database is missing or has no tables. This means that Raketasks like db:create or db:migrate fail on a freshly cloned project.

The reason is your environment.rb which is loaded for Raketasks and calls...

How to search through logs on staging or production environments

We generally use multiple application servers (at least two) and you have to search on all of them if you don't know which one handled the request you are looking for.

Rails application logs usually live in /var/www/<project-environment-name>/shared/log.
Web server logs usually live in /var/www/<project-environment-name>/log.

Searching through single logs with grep / zgrep

You can use grep in this directory to only search the latest logs or zgrep to also search older (already zipped) logs. zgrep is used just like grep ...

Detecting if a Ruby gem is loaded

Detect if a gem has been activated

A gem is activated if it is either in the current bundle (Gemfile.lock), or if you have manually activated it using Kernel#gem (old-school).

To detect if e.g. activerecord has been activated:

if Gem.loaded_specs.has_key?('activerecord')
  # ActiveRecord was activated
end

Detect if a particular gem version has been activated

To detect if e.g. activerecord ma...

MySQL: Disable query cache for database profiling

If you want to see how long your database queries actually take, you need to disable MySQL's query cache. This can be done globally by logging into a database console, run

SET GLOBAL query_cache_type=OFF;

and restart your rails server.

You can also disable the cache on a per query basis by saying

SELECT SQL_NO_CACHE * FROM ...

You also probably want to disable Rails internal (per-request) cache. For this, wrap your code with a call to ActiveRecord::Base.uncached. For example, as an around_filter:

d...