Rails: Rescuing exceptions for specific exception types

By default most exceptions in Rails will render a 500 error page and will create a new issue in your error monitoring. There are some built-in rules in Rails that

  • render a different error than 500
  • will rescue the exception and not create an issue in your error monitoring

A good example is ActiveRecord::NotFound: You don't want an exception in your error monitoring when users navigate to e.g. a blog post t...

Using the Truemail gem to validate e-mail addresses

The Truemail gem (not to be confused with truemail.io) allows validating email addresses, e.g. when users enter them into a sign-up form. It runs inside your application and does not depend on an external SaaS service.

Truemail supports different validation "layers":

  1. Regex validation: if the given address is syntactically valid
  2. DNS validation (called MX validation): if the given domain exists and can receive email
  3. SMTP validation: connects to the host received from DNS and starts a test d...

Capybara: Running tests with headless Chrome

Headless Chrome is a way to run the Chrome browser without a visible window.

Configuring Capybara

Configure the Capybara driver like this:

Capybara.register_driver :selenium do |app|
  options = Selenium::WebDriver::Chrome::Options.new
  options.add_argument('--disable-infobars')
  options.add_emulation(device_metrics: { width: 1280, height: 960, touch: false })
  
  unless ENV.key?('NO_HEADLESS')
    options.add_argument('--headless')
    o...

Printing background color of elements

Browsers' printing methods usually don't print background colors. In most cases this is the desired behavior, because you don't want to spend tons of ink printing the background of a web page. But in some cases you want to print the background color of elements, e.g. bars of a chart. For those elements you need to set the following CSS styles:

-webkit-print-color-adjust: exact; /* Chrome and Safari */
color-adjust: exact; /* Firefox */

Another case is printing of white text. When removing background colors, chances are white text n...

How DECIMAL columns deal with numbers exceeding their precision or scale

When storing floating-point numbers such as prices or totals in an SQL database, always use a DECIMAL column. Never use FLOAT or kittens will die.

DECIMAL columns are parametrized with a precision and a scale. These parameters describe which numbers can be stored in that column. E.g. a decimal with a precision of 5 and a scale of 2 can store numbers from -999.99 to 999.99, but not 1000 or 1.234.

This card explains what various databases do when you try to store a number in a DECIMAL field, and that number exceeds that colum...

Storing trees in databases

This card compares patterns to store trees in a relation database like MySQL or PostgreSQL. Implementation examples are for the ActiveRecord ORM used with Ruby on Rails, but the techniques can be implemented in any language or framework.

We will be using this example tree (from the acts_as_nested_set docs):

root
|
+-- Child 1
|   |
|   +-- Child 1.1
|   |
|   +-- Child 1.2
|
+-- ...

Custom RSpec matcher for allowed values (or assignable_values)

In contrast to RSpec's included allow_value matcher, the attached matcher will also work on associations, which makes it ideal for testing assignable_values.

Usage example

describe Unit do

  describe '#building' do
    it 'should only allow buildings that a user has access to' do
      building = build(:building)
      other_building = build(:building)
      unauthorized_building = build(:building)

      power = Power.new(build(:user))

      Power.with_power(power) do
        expect(power).to receive(:buildings).at_least...

Beware when using ActiveSupport time and date calculation methods

The pitfall

Rails Active Support provides some helpful methods for calculating times and dates, like Duration#ago or Duration#from_now. But beware when using those, because they wont give you Dates or Times but ActiveSupport::TimeWithZone instances. As the class name hints, you now have to be awa...

Rails: Overriding view templates under certain conditions only

Rails offers a way to prepend (or append) view paths for the current request. This way, you can make the application use different view templates for just that request.

Example

A use case of this is a different set of view templates that should be used under certain circumstances:

class UsersController < ApplicationController

  before_action :prepare_views
  
  def index
    # ...
  end    
  
  private
  
  def prepare_views
    if <condition>
      prepend_view_path Rails.root.join('app', 'views', 'special')
    end
  end
 ...

Pretty commit messages via geordi

Geordi provides a pretty neat way to generate beautiful commit messages according to your stories in Linear:

geordi commit

Geordi reads from a .geordi.yml file inside your repo and connects to Linear to list started and finished stories with their title. Choosing one of them generates a commit message including id and title from Linear app and a link to the original issue. For example:

[VW-1337] CRUD Users

Issue: https://linear.app/makandra/issue/VW-1337/crud-user...

A quick introduction to CORS

Background

Cross-Site Request Forgery (CSRF) is an attack pattern for websites. A CSRF attack is usually relevant in a browser context, where state is kept for multiple domains (as opposed to independent requests made e.g. with curl). The most common example is authentication via cookies. If a script on https://example.com made requests to https://docs.google.com, the browser would send all cookies for docs.google.com along, effectively given the script access to anythin...

Rails: Composing an ETag from multiple records

Rails offers the fresh_when method to automatically compute an ETag from the given record, array of records or scope of records:

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    fresh_when @user
  end  
  
  
  def index
    @users = User.all.to_a
    fresh_when @users
  end
end

When your view also displays other records (typically associations), those other records should be included in the ETag. You can do so by passing an array of ETaggable objects to fresh_when.

...

Rails: Your index actions probably want strict_loading

By activating strict_loading you force developers to address n+1 queries by preloading all associations used in the index view. Using an association that is not preloaded will raise an ActiveRecord::StrictLoadingViolationError.

I think it's a good default to activate strict_loading in your controllers' #index actions. This way, when a change introduces an n+1 query, you...

Rails: How to test the parsed response body

Testing your responses in Rails allows to parse the body depending on the response MIME type with parsed_body.

get '/posts.json'
response.parsed_body # => [{'id' => 42,  'title' => 'Title'}, ...]

For JSON APIs we often parse the response as symbolized keys with JSON.parse(response.body, symbolize_names: true), which is not supported by parsed_body. For all other cases you might want to drop JSON.parse(response.body) and replace it w...

Best practices: Large data migrations from legacy systems

Migrating data from a legacy into a new system can be a surprisingly large undertaking. We have done this a few times. While there are significant differences from project to project, we do have a list of general suggestions.

Before you start, talk to someone who has done it before, and read the following hints:

Understand the old system

Before any technical considerations, you need to understand the old system as best as possible. If feasible, do not only look at its API, or database, or frontend, but let a user of the old system sho...

Keeping web applications fast

Our applications not only need to be functional, they need to be fast.

But, to quote Donald Knuth,

premature optimization is the root of all evil (or at least most of it) in programming

The reasoning is that you should not waste your time optimizing code where it does not even matter. However, I believe there are some kinds of optimizations you should do right away, because

  • they are either obvious and easy
  • or they are very hard to do optimize later

This is an attempt to list some of those things:

On the server

...

How to exclusively lock file access in ruby

We will achieve this by creating a block accepting method to optionally create and then lock a .lock File of the underlying accessed file.

Why create a .lock file?

  • The main advantage of creating a .lock file is that #flock might block some operations and require the index node of the file to be consistent. Some operations might change that index node.
  • In some cases it might also be convenient to just read/write the lock file first and update the other file afterwards or vice versa, such that breaking of a process does not...

Rails developers: Have better context in Git diffs

Git diffs show the surrounding contexts for diff hunks. It does so by applying regular expressions to find the beginning of a context. When it comes to Ruby, however, it will not find method heads and travel up to the class definition:

@@ -24,7 +24,7 @@ class TicketPdf # <=== Actually expected here: the method definition
     ApplicationController.render(
       "tickets/index.html.haml",
       layout: "tickets",
-      assigns: { tickets: tickets }
+      assigns: { tickets: tickets, event_name: event_name }
     )
   end
 end
```...

Git: Improve your commits by reviewing changes one-by-one

Git commits should be very deliberate, and only contain changes that you really want to be in there. In order to reduce the chance to accidentally commit something you didn't intend, review your changes before committing.

My preferred way of doing this is (only using git)

git add -N . # Add all paths, but not their contents
git add -p

Git will now show you all your changes in small chunks and ask you in an interactive mode whether you really want to add them.

The most helpful commands are

  • y: yes (add the change)
  • ...

Project management best practices: Project team responsibilities

In a project team for a bigger project people have several roles:

  • Developer: at makandra
  • Project lead: at makandra
  • Project manager (PM): at makandra, external, or with the customer. In a smaller project this person is also the project lead.
  • Product owner (PO): with the customer

Developer

  • Development
  • Take responsibility for their stories. This includes always gathering all necessary information from the project lead or the PM, communicate blockers, make sure stories are merged, deployed etc.
  • Tell the project lead, if y...

SASS: Reusing styles from other files

SASS has an @extend keyword to inherit styles.

.alert
  color: red
  
  &.-framed
    border: 1px solid red
    padding: 5px
    
  &.-homepage
    @extend .-framed
    border-width: 5px

When compiling, SASS will simply join the selectors. Note how .-homepage is written where .-framed was defined:

...
.alert.-framed, .alert.-homepage {
  border: 1px solid red;
  padding: 5px;
}
.alert.-homepage {
  border-width: 5px;
}

Warning

Unfortunately, this does...

How to enable template coverage support for simplecov

Since Ruby 3.2.0 you can measure coverage support for eval statements and support has been added for the simplecov gem as well.

This allows to track coverage across ruby templates such as haml, erb, ...

Simply set this within simplecov

SimpleCov.start do
  enable_coverage_for_eval
end

SVGO: SVG Optimizer

SVG files are often much larger than necessary, containing comments, metadata, hidden elements etc.

Optimize them with this tool.

Note that for a properly scaling SVG, you need to keep the viewBox attribute. There's an option --disable=removeViewBox for this.

FactoryBot: Passing attributes to associated records using transient attributes

FactoryBot.define do

  factory :parent do
    transient do
      child_name nil
      child_allowed_to_drive false
    end
    
    child do
      association(:child, name: child_name, allowed_to_drive: child_allowed_to_drive)
    end
  end

  factory :child do
    name 'Child'
    allowed_to_drive false
  end

end

# Usage
daughter = FactoryBot.create(:parent, child_name: 'Lisa').child
daughter.name # => 'Lisa'
daughter.allowed_to_drive? # => false

son = FactoryBot.create(:parent, child_name: 'Benedikt', child_allowed_to_drive: tr...