How to examine an unknown Ruby object

When debugging your application, you will come across objects created by some gem or framework. You don't have the source code at hand, still need to inspect this object. Here are some tools to do so:

Relevant methods

@object.methods - Object.instance_methods returns a list of methods excluding methods inherited from Object. This makes the methods list drastically more relevant. You can also try subtracting other base classes like ActiveRecord::Base.methods etc.
To further narrow it down you can also just look at public methods...

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

Raising JavaScript errors in Ruby E2E tests (RSpec, Cucumber)

A JavaScript error in an E2E test with Selenium will not cause your test to fail. This may cause you to miss errors in your frontend code.

Using the BrowserConsole helper below you can check your browser's error console from your E2E tests.

The following will raise BrowserConsole::ErrorsPresent if there is an error on the browser console:

BrowserConsole.assert_no_errors!

Ignoring errors

You can ignore errors by their exact message:

BrowserConsole.ignore('Browser is burning')

You can ignore errors with me...

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

Geordi: How to rerun failed features

Geordi's cucumber command has a --rerun option that reruns failing tests the given number of times. Usage:

geordi cucumber path/to/features --rerun=2
geordi cucumber path/to/features -r2

Background and how to rerun manually

Cucumber will save a file tmp/parallel_cucumber_failures.log containing the filenames and line number of the failed scenarios after a full test run. Normally you can say cucumber -p rerun (rerun is a profile defined by default in config/cucumber.yml) to rerun all failed scenarios.

Here are a few al...

Ruby: How to use prepend for cleaner monkey patches

Let's say you have a gem which has the following module:

module SuperClient

  def self.foo
    'Foo'
  end
  
  def bar
    'Bar'
  end

end

For reasons you need to override foo and bar.

Keep in mind: Your code quality is getting worse with with each prepend (other developers are not happy to find many library extensions). Try to avoid it if possible.

  1. Add a lib/ext/super_client.rb to your project (see How to organize monkey patches in Ruby on Rails projects)
  2. Add the extension, which ov...

HTML forms with multiple submit buttons

Most forms have a single submit button that will save the record when pressed.

Sometimes a form needs additional submit buttons like "accept" or "reject". Such buttons usually attempt a state transition while updating the record.

To process a form with multiple buttons, your server-side code will need to know which button was pressed. To do so you can give each submit button a different [formaction] attribute. This will override the ...

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

Project management best practices: The story tracker

In general, the tracker should always be the definitive source of truth of what needs to be done as part of a project. If you identify a task that needs to be done, you should create a story. If you learn something that contradicts an existing story, change it.

The tracker can contain two types of stories: Developer stories, and non-developer stories.

Non-developer stories

Non-developer stories should be clearly marked. They usually belong to the PM (or maybe people from the operations team). Those story can take all forms, could just...

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

How to make changes to a Ruby gem (as a Rails developer)

At makandra, we've built a few gems over the years. Some of these are quite popular: spreewald (> 1M downloads), active_type (> 1M downloads), and geordi (> 200k downloads)

Developing a Ruby gem is different from developing Rails applications, with the biggest difference: there is no Rails. This means:

  • no defined structure (neither for code nor directories)
  • no autoloading of classes, i.e. you need to require all files yourself
  • no active_support niceties

Also, their scope...

Optimizing images for the web

For webpages to load fast it's recommended to optimize images. Ideally an image's file size should be as small as possible while still being of decent quality. This card demonstrates two command line tools for image optimization

Use identify to fetch information about pictures. convert can change size/quality and strip meta information. Both commands are supplied by ImageMagick.

$ identify in.jpg
in.jpg JPEG 294x440 294x440+0+0 8-bit sRGB 92.8KB 0.000u 0:00.000
$ convert in.jpg -resize x220 -strip -set profile sRGB2014.icc -qua...

Devise: Invalidating all sessions for a user

Background information about session storage in Rails

Rails has a default mechanism to store the session in the CookieStore. This is a cookie which holds the entire user session hash in the browser. This cookie is serialized, encoded with base64, and signed.

How Devise handles authentication

Devise uses this CookieStore. To track a users session, a salt is stored in the session ...

Whenever: Don't forget leading zeros for hours!

Whenever is a Ruby gem that provides a nicer syntax for writing and deploying cron jobs.

Leading zeros are important for whenever if you use the 24-hours format!

This schedule.rb:

every 1.day, at: '3:00', roles: [:primary_cron] do
  runner 'Scheduler.delay.do_things'
end

will lead to this crontab entry (crontab -l) with the default configuration:

0 15 * * * /bin/bash -l -c 'cd /var/www/my-project/releases/20180607182518 && bin/rails runner -e production '\''Scheduler.delay.do_things'\'''

Which would run on 3...

Checklist: Rails Authentication

Authentication is a special part of web applications. On the one hand, it usually is a crucial security mechanism restrict access to certain people and roles. On the other hand, most users authenticate only once, so it is very unlikely to spot issues by accident.

So, here comes a quick checklist to help you verifying your authentication solution is all set.

  • This should be default: use HTTPS with HSTS. The HSTS part is important.
  • Use a reliable authentication solution, e.g. Clearance or [Devise...

When you want to format only line breaks, you probably do not want `simple_format`

For outputting a given String in HTML, you mostly want to replace line breaks with <br> or <p> tags.
You can use simple_format, but it has side effects like keeping some HTML.

If you only care about line breaks, you might be better off using a small, specialized helper method:

def format_linebreaks(text)
  safe_text = h(text)
  paragraphs = split_paragraphs(safe_text).map(&:html_safe)

  html = ''.html_safe
  paragraphs.each do |paragraph|
    html << content_tag(:p, paragraph)
  end
  html
end

Full di...

Don't sum up columns with + in a SQL query if NULL-values can be present.

Don't sum up columns with + in a sql-query if NULL-Values can be present.

MySQL and PostgreSQL cannot sum up NULL values with the + value. The sum value will be NULL.

MySQL:

mysql> select 1 + 2 + 3;
+-----------+
| 1 + 2 + 3 |
+-----------+
|         6 |
+-----------+
1 row in set (0,00 sec)

mysql> select 1 + NULL + 3;
+--------------+
| 1 + NULL + 3 |
+--------------+
|         NULL |
+--------------+
1 row in set (0,00 sec)

Postgres:


test_database=# select 1 + 2 + 3;

 ?column? 
----------
        6
(1 row)

t...

Writing strings as Carrierwave uploads

When you have string contents (e.g. a generated binary stream, or data from a remote source) that you want to store as a file using Carrierwave, here is a simple solution.

While you could write your string to a file and pass that file to Carrierwave, why even bother? You already have your string (or stream).
However, a plain StringIO object will not work for Carrierwave's ActiveRecord integration:

>> Attachment.create!(file: StringIO.new(contents))
TypeError: no implicit conversion of nil into String

This is because Carrierwav...

HTML5: disabled vs. readonly form fields

Form fields can be rendered as noneditable by setting the disabled or the readonly attribute. Be aware of the differences:

disabled fields

  • don’t post to the server
  • don’t get focus
  • are skipped while tab navigation
  • available for button, fieldset, input, select, textarea, command, keygen, optgroup, option

Browser specific behavior:

  • IE 11: text inputs that are descendants of a disabled fieldset appear disabled but the user can still interact with them
  • Firefox: selecting text in a disabled text field is no...

netstat: How to show listening ports

Sometimes it's necessary for you to check which ports are in use on your local machine and which process is using it. To list this information you can use the following command (which is pretty easy to memorize for Germans 🌷):

sudo netstat -tulpn
  • t: tcp
  • u: udp
  • l: listening ports
  • p: process
  • n: network

Use sudo to see the name of the process.

Finding open ports in the netstat output

You should look for rows with State: LISTEN.

Rows for local address 127.0.0.1 or...

Decide whether cronjobs should run on one or all servers

Understanding your type of cronjob

Some cronjobs must only run on a single server. E.g. when you run nightly batch operations on the database, it should probably run on a single server. Running it on multiple servers would likely result in deadlocks or corrupt data.

Some cronjobs must always run on all servers. E.g. starting a sidekiq process on reboot.

Configuring whenever

If not configured otherwise, cronjobs defined in whenever's `s...

Carrierwave: Deleting files outside of forms

TL;DR Use user.update!(remove_avatar: true) to delete attachments outside of forms. This will have the same behavior as if you were in a form.


As you know, Carrierwave file attachments work by mounting an Uploader class to an attribute of the model. Though the database field holds the file name as string, calling the attribute will always return the uploader, no matter if a file is attached or not. (Side note: use #present? on the uploader to check if the file exists.)

class User < ApplicationRecord
  mount :avatar, ...

Rails and Postgres: How to test if your index is used as expected

This is a small example on how you can check if your Postgres index can be used by a specific query in you Rails application. For more complex execution plans it might still be a good idea to use the same path of proof.

1. Identify the query your application produces

query = User.order(:last_name, :created_at).to_sql
puts query
# => SELECT "users".* FROM "users" ORDER BY "users"."last_name" ASC, "users"."created_at" ASC

2. Add an index in your migration and migrate

add_index :users, [:last_name, :created_at]...

HTML: Making browsers wrap long words

By default, browsers will not wrap text at syllable boundaries. Text is wrapped at word boundaries only.

This card explains some options to make browsers wrap inside a long word like "Donaudampfschifffahrt".

Option 1: hyphens CSS property (preferred)

Modern browsers can hyphenate natively. Use the hyphens CSS property:

hyphens: auto

There is also hyphens: none (disable hyphenations even at &shy; entities) and hyphens: manual (hy...