WYSIWYG with Action Text

Rails 6 includes a WYSIWYG editor, Action Text. It works out of the box quite well, but chances are that you want to add some custom functionality. This card contains some tips how to achieve this.

Setup

Basically, follow the guide in the Rails documentation. The automated script may not work with the way webpacker is configured in your project, but it should be easy to fix.

If you don't want the default c...

GoodJob: Ensure you get notified about background exceptions

GoodJob and ActiveJob rescue exceptions internally, preventing exception_notification from triggering. This can cause silent job failures.To get notified, subscribe to ActiveJob events and configure GoodJob's on_thread_error hook. This lets you manually call your exception notifier for every retry, discard, or internal GoodJob error.

# config/initializers/good_job.rb

# Manually notify on job failures, as they are handled internally by ActiveJob/GoodJob.
ActiveSupport::Noti...

How to enable pretty IRB inspection for your Ruby class

When Ruby objects are inspected in any modern IRB, some objects (like ActiveRecord instances) are rendered with neat colors and line breaks.
You will not get that for custom classes by default -- which can be annoying if your inspection contains lots of meaningful information.

Here is what you need to do if you want your objects to be inspected nicely.

Implement a pretty_print method

As an example, consider the following class.

class MyClass

  # ...

  def inspect
    "#<#{self.class} attr1: #{attr1.inspect}, attr2: #{attr2...

How the Date Header Affects Cookie Expiration and Caching

tl;dr

When a cookie includes an Expires attribute or an HTTP response includes caching headers like Expires or Cache-Control, their validity depends on the server's Date header if present. Otherwise, the browser uses its local time. This can lead to issues in tests with mocked time or inconsistent cache behavior.

Cookie Expires depends on the Date header or browser time

When a cookie includes an Expires attribute, the browser evaluates the expiration date relative to a reference time:

  1. If the HTTP response ...

has_one association may silently drop associated record when it is invalid

This is quite an edge case, and appears like a bug in Rails (4.2.6) to me.

Update: This is now documented on Edgeguides Ruby on Rails:

If you set the :validate option to true, then associated objects will be validated whenever you save this object. By default, this is false: associated objects will not be validated when this object is saved.

Setup

# post.rb
class Post < ActiveRecord::Base
  has_one :attachment
end
# attachm...

Capybara: Preventing server errors from failing your test

When your Rails application server raises error, Capybara will fail your test when it clears the session after the last step. The effect is a test that passes all steps, but fails anyway.

Capybara's behavior will help you to detect and fix errors in your application code. However, sometimes your application will explode with an error outside your control. Two examples:

  • A JavaScript library references a source map in its build, but forgets to package the source map
  • CarrierWave cleans up an upload or cache file after the record was delet...

Center a float horizontally

This card shows you how to center a float horizontally in CSS. Also: find out what techniques are available for your use case and browser requirements in the card linked below.

Note: We have card with all CSS centering options. You probably want to head over there and get an overview over what techniques are available for your use case and browser requirements.


If you cannot use display: inline-block, centering a float ...

Switching the package manager from yarn to npm

We recently migrated a Rails application from yarn to npm. We decided to go this step instead of upgrading to > Yarn 2.0 to reduce the number of dependencies in our project.

Migration

  • Remove the yarn.lock file
  • Remove the node_modules folder
  • Run npm install
  • Replace all occurrences of yarn with npm in your project

Notes

  • With npm vendored packages with dependencies create their own node_modules folder within the vendor path. We...

Making minimal updates to DOM trees using morphdom / idiomorph

When you replace parts of the DOM with new HTML, using .innerHTML = newHtml is usually the simplest and fastest option. It comes at the price of your DOM elements losing state, like input values, scroll position, progress in a video player, or even more complex state for custom elements.

One option to avoid this are libraries like morphdom (as used by Phoenix Liveviews) or idiomorph (as used by Rails' Turbo).

It lets you write

morphdo...

JSON APIs: Default design for common features

When you build a JSON API you need to come up with a style to represent attributes, pagination, errors or including associated objects. Instead of reinventing the wheel, you may reuse successful API designs.

JSON API

JSON:API specifies common features found in most JSON APIs, like pagination, ordering and nested resources. The spec looks very similar to how one would build an API with Rails and uses similar patterns. Even if you don't plan on supporting the whole spec, it can still make sense to know how th...

How to turn images into inline attachments in emails

Not all email clients support external images in all situations, e.g. an image within a link. In some cases, a viable workaround is to turn your images into inline attachments.

Note

Rails provides a simple mechanism to achieve this:

This documentation makes it look like you have to care about these attachments in two places. You have to create the attachment in t...

ActiveRecord: count vs size vs length on associations

TL;DR: You should generally use #size to count associated records.

size

  • Counts already loaded elements
  • If the association is not loaded, falls back to a COUNT query

count

  • If a counter cache is set up, returns the cached value
  • Issues a COUNT query else

Caveats

  • If you trigger a COUNT query for an association of an an unsaved record, Rails will try to load all children where the foreign key IS NULL. This is not what you want. To prevent this behavior, you can use unsaved_record.association.to_a.size.
  • `c...

Disable PostgreSQL's Write-Ahead Log to speed up tests

The linked article suggests an interesting way to speed up tests of Rails + Postgres apps:

PostgreSQL allows the creation of “unlogged” tables, which do not record data in the PostgreSQL Write-Ahead Log. This can make the tables faster, but significantly increases the risk of data loss if the database crashes. As a result, this should not be used in production environments. If you would like all created tables to be unlogged in the test environment you can add the following to your...

Disable SimpleCov if you only run a fraction of your tests

Coverage reports are rarely useful if you run only small parts of your test suite.

Just do not load SimpleCov in this case, and you will see less noise in your test output:

if RSpec.configuration.files_to_run.count > 5
  require "simplecov"
  SimpleCov.start 'rails'
end

See also

How to tell ActiveRecord how to preload associations (either JOINs or separate queries)

Remember why preloading associations "randomly" uses joined tables or multiple queries?

If you don't like the cleverness of this behavior, you can explicitely tell ActiveRecord how to preload associations with either JOINs or separate queries.

This card gives an overview of the different options to preload associations, but

__Whic...

Ensure passing Jasmine specs from your Ruby E2E tests

Jasmine is a great way to unit test your JavaScript components without writing an expensive end-to-end test for every small requirement.

After we integrated Jasmine into a Rails app we often add an E2E test that opens that Jasmine runner and expects all specs to pass. This way we see Jasmine failures in our regular test runs.

RSpec

In a [feature spec](https://web.archive.org/web/20150201092849/http://www.rel...

PSA: Chrome and Firefox do not always clear session cookies on exit

Cookies without an expiration timestamp are called "session cookies". [1] They should only be kept until the end of the browsing session.

However, when Chrome or Firefox are configured to reopen tabs from last time upon start, they will keep session cookies when closing the browser. This even applies to tabs that were closed before shutting down the browser.

This is by design in Chrome and [Firefox](https://bugzilla.mozilla.org/buglist.cgi?bug_id=337551,345830,358042,362212,36...

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...

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 =...

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...

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|
  ...

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

...