RSpec: Executing specs by example id (or "nesting index")

There are several ways to run a single spec. I usually copy the spec file path with the line number of the example and pass it to the RSpec binary: bin/rspec spec/models/user_spec.rb:30 (multiple line numbers work as well: :30:36:68). Another is to tag the example with focus: true or to run the example by matching its name.

In this card I'd like to ...

Why preloading associations "randomly" uses joined tables or multiple queries

ActiveRecord gives you the :include option to load records and their associations in a fixed number of queries. This is called preloading or eager loading associations. By preloading associations you can prevent the n+1 query problem that slows down a many index view.

You might have noticed that using :include randomly seems to do one of the following:

  1. Execute one query per involved table with a condit...

What we know about PDFKit

What PDFKit is

  • PDFKit converts a web page to a PDF document. It uses a Webkit engine under the hood.
  • For you as a web developer this means you can keep using the technology you are familar with and don't need to learn LaTeX. All you need is a pretty print-stylesheet.

How to use it from your Rails application

  • You can have PDFKit render a website by simply calling PDFKit.new('http://google.com').to_file('google.pdf'). You can then send the...

How to simulate limited bandwidth in Google Chrome and Firefox

Your development machine is usually on a very good network connection.
To test how your application behaves on a slow network (e.g. mobile), you can simulate limited bandwidth.

Chrome

  • Open the dev tools (Ctrl+Shift+I or F12) and switch to the "Network" tab
  • In the row below the dev tool tabs, there's a throttling dropdown which reads "Online" by default.
  • Inside the dropdown, you will find a few presets and an option to add your own download/upload/latency settings.

Firefox

  • Open the dev tools (Ctrl+Shift+I or F12) and switc...

Rails: Example on how to extract domain independent code from the `app/models` folder to the `lib/` folder

This cards describes an example with a Github Client on how to keep your Rails application more maintainable by extracting domain independent code from the app/models folder to the lib/ folder. The approach is applicable to arbitrary scenarios and not limited to API clients.

Example

Let's say we have a Rails application that synchronizes its users with the Github API:

.
└── app
    └── models
        ├── user
        │   ├── github_client.rb
        │   └── sychronizer.rb
        └── user.rb

In this example the app folder ...

RSpec's hash_including matcher does not support nesting

You can not use the hash_including argument matcher with a nested hash:

describe 'user' do
  let(:user) { {id: 1, name: 'Foo', thread: {id: 1, title: 'Bar'} }

  it do 
    expect(user).to match(
      hash_including(
        id: 1, thread: {id: 1}
      )
    )
  end
end  

The example will fail and returns a not very helpful error message:

expected {:id => 1, :name => "Foo", :thread => {:id => 1, :title => "Bar"}} to...

Rails: Rest API post-mortem analysis

This is a personal post-mortem analysis of a project that was mainly build to provide a REST API to mobile clients.

For the API backend we used the following components:

  • Active Model Serializer (AMS) to serializer our Active Record models to JSON.
  • JSON Schema to test the responses of our server.
  • SwaggerUI to document the API.

It worked

The concept worked really good. Here are two points that were extraordinary compared to normal Rails project with many UI components:

  • Having a Rails application, that has no UI components (only...

How to evaluate CSS media queries in JavaScript

To make CSS rules dependent on the screen size, we use media queries:

@media (max-width: 500px) {
  // rules for screen widths of 500px or smaller
}

Browsers will automatically enable and disable the conditional rules as the screen width changes.

To detect responsive breakpoints from JavaScript, you may use the global matchMedia() function. It is supported in all brow...

Sass: How to get rid of deprecation warnings in dependencies

sass >= 1.35.0 has the option quietDeps and silenceDeprecations to silence deprecation warnings from dependencies.

Below there are a few examples for different build tools how to set the Sass options.

Webpacker

const sassLoaderConfig = environment.loaders.get('sass')
const...

ActiveType::Object: Be careful when overriding the initialize method

Background:

ActiveType::Object inherits from ActiveRecod::Base and is designed to behave like an ActiveRecord Object, just without the database persistence.

Don't remove any of the default behavior of the initialize method!

If you have a class which inherits from ActiveType::Object and you need to override the #initialize method, then you should be really careful:

  • Always pass exactly one attribute. ActiveRecod::Base objects really want to get their arguments processable as keyword arguments. Don't change the syntax, or y...

Use CSS "text-overflow" to truncate long texts

When using Rails to truncate strings, you may end up with strings that are still too long for their container or are not as long as they could be. You can get a prettier result using stylesheets.

The CSS property text-overflow: ellipsis has been around for quite a long time now but since Firefox did not support it for ages, you did not use it. Since Firefox 7 you can!

Note that this only works for single-line texts. If you want to truncate tests across multiple lines, use a JavaScript solution like...

Simple database lock for MySQL

Note: For PostgreSQL you should use advisory locks. For MySQL we still recommend the solution in this card.


If you need to synchronize multiple rails processes, you need some shared resource that can be used as a mutex. One option is to simply use your existing (MySQL) database.

The attached code provides a database-based model level mutex for MySQL. You use it by simply calling

Lock.acquire('string to synchronize on') do
  # non-th...

How to run a small web server (one-liner)

Sometimes you just want to have a small web server that serves files to test something.

Serve the current directory

On Ruby 1.9.2+ you can do the following ("." for current directory). You might need to gem install webrick on modern Rubies.

ruby -run -ehttpd . -p8000

Python 2.x offers a similar way.

python -m SimpleHTTPServer 8000 .

This is the same way with Python 3.x

python -m http.server

In both cases your web server is single-threaded and will block when large files are being downloaded from you.

WEBrick ...

Capybara: Pretending to interact with the document

Browsers blocks abusable JavaScript API calls until the user has interacted with the document. Examples would be opening new tab or start playing video or audio.

E.g. if you attempt to call video.play() in a test, the call will reject with a message like this:

NotAllowedError: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD

Workaround

To pretend document interaction in a test you can create an element, click on it, and remove the element again. This unblocks the entire JavaSc...

How to: Upgrade CarrierWave to 3.x

While upgrading CarrierWave from version 0.11.x to 3.x, we encountered some very nasty fails. Below are the basic changes you need to perform and some behavior you may eventually run into when upgrading your application. This aims to save you some time understanding what happens under the hood to possibly discover problems faster as digging deeply into CarrierWave code is very fun...

Whitelists and blacklists

The following focuses on extension allowlisting, but it is the exact same thing for content type allowlisting with the `content_ty...

Automatic Log Rotation in Rails

Rails log files rotate automatically when they reach approx. 100MB:

$ ls -lh log/
-rw-r--r-- 1 user group  55M Sep 15 09:54 development.log
-rw-r--r-- 1 user group 101M Aug 22 13:45 development.log.0

This behavior is a built-in feature of Ruby's standard Logger class, which Rails uses by default.

To control the maximum file size, set config.log_file_size in yo...

Switch to a recently opened tab with Cucumber

Similar to closing an opened browser window, spreewald now supports the I switch to the new browser tab step.

Info

See the Spreewald README for more cool features.

You can use it to test links that were opened with a link_to(..., :target => '_blank') link or other ways that create new tabs or windows.

Important

This only works with Selenium ...

Bash script to list git commits by Linear ID

As we're switching from PT to Linear, I've updated the existing bash script to work for commits that are referencing Linear IDs.

A core benefit of our convention to prefix commits by their corresponding issue ID is that we can easily detect commits that belong to the same issue. You can either do that manually or use the bash script below. It can either be placed in your .bashrc or a...

Cucumber step to match table rows with Capybara

These steps are now part of Spreewald.

This note describes a Cucumber step that lets you write this:

Then I should see a table with the following rows:
  | Bruce Wayne       | Employee    | 1972 |
  | Harleen Quinzel   | HR          | 1982 |
  | Alfred Pennyworth | Engineering | 1943 |

If there are additional columns or rows in the table that are not explicitely expected, the step won't complain. It does however expect the rows to be ordered as stat...

Creating a Rails application in a single file

Greg Molnar has written a neat article about creating a single-file Rails app.
This is not meant for production use but can be useful to try things out, e.g. when hunting down a bug or embedding a Rails app into the tests of a gem.

What you do is basically:

  1. Put everything (gems, application config, database migrations, models, controllers) into a single .ru file, like app.ru.
  2. Run it via rackup app.ru. (Hint: if your file is called config.ru, you can just run `rac...

OpenAI TTS: How to generate audio samples with more than 4096 characters

OpenAI is currently limiting the Audio generating API endpoint to text bodies with a maximum of 4096 characters.
You can work around that limit by splitting the text into smaller fragments and stitch together the resulting mp3 files with a CLI tool like mp3wrap or ffmpeg.

Example Ruby Implementation

Usage

input_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Mi eget mauris pharetra et ultrices neque."
output_mp3_path = Rails.root.join("tts/ipsum...

RestClient / Net::HTTP: How to communicate with self-signed or misconfigured HTTPS endpoints

Occasionally, you have to talk to APIs via HTTPS that use a custom certificate or a misconfigured certificate chain (like missing an intermediate certificate).

Using RestClient will then raise RestClient::SSLCertificateNotVerified errors, or when using plain Net::HTTP:

OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed

Here is how to fix that in your application.

Important: Do not disable certificate checks for production. The interwebs are full of people say...

RSpec: You can super into parent "let" definitions

RSpec's let allows you to super into "outside" definitions, in parent contexts.

Example:

describe '#save' do
  subject { described_class.new(attributes) }
  let(:attributes) { title: 'Example', user: create(:user) }

  it 'saves' do
    expect(subject.save).to eq(true)
  end

  context 'when trying to set a disallowed title' do
    let(:attributes) { super().merge(title: 'Hello') } # <==

    it 'will not save' do
      expect(subject.save).to eq(false)
    end
  end
end

I suggest you don't make a habit of using this regula...

How to open files from better_errors with RubyMine on Linux

I recently noticed that better_errors allows you to to open files from within your favorite editor. However it was not so easy to get rubymine:// links to work on Gnome/Linux. Here is how it finally worked for me:

Step 1: Add a Desktop launcher

Add this file to ~/.local/share/applications/rubymine.desktop:

[Desktop Entry]
Version=1.0
T...