Databases don't order rows unless you tell them so

There is no such thing as a "default order" of rows in database tables.

For instance, when you paginate a result set: When using LIMIT, it is important to use an ORDER BY clause that constrains the result rows into a unique order. Otherwise you will get an unpredictable subset of the query's rows. You might be asking for the tenth through twentieth rows, but tenth through twentieth in what ordering? The ordering is unknown, unless you specified ORDER BY.

In Rails, if you use Record.first or Record.last, it will default to orderin...

A different testing approach with Minitest and Fixtures

Slow test suites are a major pain point in projects, often due to RSpec and FactoryBot. Although minitest and fixtures are sometimes viewed as outdated, they can greatly improve test speed.

We adopted a project using minitest and fixtures, and while it required some initial refactoring and establishing good practices, the faster test suite was well worth it! Stick with me to explore how these tools might actually be a good practice.

So, why is this setup faster? Partially, it's because minitest is more lightweight than RSpec, which...

Configuring Git with .gitconfig

Basic configuration

Please keep this config simple. It should be a starting point for new developers learning Git.

[user]
  name = Your Name
  email = your.name@domain.com

[branch]
  sort = -committerdate
[color]
   ui = auto
[color "branch"]
  current = yellow reverse
  local = yellow
  remote = green
[color "diff"]
  whitespace = white reverse
  meta = blue reverse
  frag = blue reverse
  old = red
  new = green
[color "status"]
  added = green
  changed = yellow
  untracked = cyan
[interactive]
  singlekey = true # Do not requir...

How to discard a surrounding Bundler environment

tl;dr: Ruby's Bundler environment is passed on to system calls, which may not be what you may want as it changes gem and binary lookup. Use Bundler.with_original_env to restore the environment's state before Bundler was launched. Do this whenever you want to execute shell commands inside other bundles.

Example outline

Consider this setup:

my_project/Gemfile     # says: gem 'rails', '~> 3.0.0'
my_project/foo/Gemfile # says: gem 'rails', '~> 3.2.0'

And, just to confirm this, these are the installed Rails versions for each ...

Why you can't use timezone codes like "PST" or "BST" for Time objects

Rails' ActiveSupport::TimeWithZone objects have both a timezone code and offset, e.g. Thu, 28 Mar 2019 16:00:00 CET +01:00. Ruby's stdlib TZInfo also has time zones, but with different identifiers.
Unfortunately, not all timezone codes can be used to parse strings or to move time objects into another time zone.

Some timezone codes like CET are supported by ActiveSupport extensions like String#in_time_zone, while many codes will actually not work:

>> '2019-03-01 12:00'.in_time_zone('PST')
ArgumentError (Invalid Timezone: PST)
...

In MySQL, a zero number equals any string

In MySQL comparing zero to a string 0 = "any string" is always true!

So when you want to compare a string with a value of an integer column, you have to cast your integer value into a string like follows:

SELECT * from posts WHERE CAST(posts.comments_count AS CHAR) = '200' 

Of course this is usually not what you want to use for selecting your data as this might cause some expensive database operations. No indexes can be used and a full table scan will always be triggered.

If possible, cast the compared value in your application to...

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

RSpec: Leverage the power of Capybara Finders and Matchers for view specs

View specs are a powerful tool to test several rendering paths by their cases instead of using a more costing feature spec. This is especially useful because they become quite convenient when used with Capybara::Node::Finders and Capybara::RSpecMatchers. This allows to wirte view unit specs as you can isolate specific part...

Capistrano: Speeding up asset compile during deploy

Remember How to skip Sprockets asset compile during Capistrano deployment and Automatically skipping asset compilation when assets have not changed? Turns out there is an even better way to speed up Capistrano deployments with asset compilation – and it's even simpler.

Adding the asset cache directory to symlinked directories

Popular asset managers for Rails are Sprockets and Webpacker. Both keep a cache of already compiled files that we're going to leverage for deployments now.

  • Sprockets cache...

Case sensitivity in PostgreSQL

PostgreSQL, unlike MySQL, treats strings as case sensitive in all circumstances.

This includes

  • comparison with = and LIKE
  • collision detection in unique indexes

Usually this is fine, but some strings (like emails and usernames) should typically be treated as case insensitive.

There are a few workarounds available:

  • use the citext extension (not recommended)
  • use ILIKE instead of LIKE
  • use Postgres' lower() function
  • add an index on lower(email)

Probably th...

Using Passenger Standalone for development

For our production servers we use Passenger as a Ruby application server. While it is possible to use Passenger for development as an Apache module, the installation process is not for the faint of heart.

Luckily Passenger also comes as a standalone binary which requires zero configuration.

You can Passenger Standalone as a replacement for Webrick or Thin if you'd like to:

  • Use SSL certificates locally
  • Get performance behavior that is closer to ...

Local development with SSL and Puma

Sometimes the need arises for SSL in local development. We have guides for different webservers, this one is for puma.

  1. make sure mkcert is installed

  2. create an SSL certificate for localhost with mkcert:

$ mkcert-v1.4.4-linux-amd64 localhost
Created a new local CA 💥
...
  1. use the certificate in the Puma config config/puma.rb:
localhost_key = "#{File.join('localhos...

Capistrano: exclude custom bundle groups for production deploy

Capistrano is by default configured to exclude the gems of the groups development and test when deploying to the stages production and staging. Whenever you create custom groups in your Gemfile, make sure to exclude these, if they should not be deployed to the servers. The gems of these groups might not be loaded by rails, however, the deployment process will take longer as the gems will be downloaded and installed to the server.

e.g. to exclude the groups cucumber and deploy, add the following to `config/deploy/production.rb...

Using the ActiveSupport::BroadcastLogger

The ActiveSupport::BroadcastLogger allows you to log to multiple sinks. You know this behavior from from the rails server command, that both logs to standard out and the log/development.log file.

Here is an example from the ActiveSupport::BroadcastLogger API:

stdout_logger = ActiveSupport::Logger.new(STDOUT)
file_logger = ActiveSupport::Logger.new("development.log")
broadcast = ActiveSupport::BroadcastLogger.new(stdout_logger, file_logger)

broadcast.i...

How to reload a belongs_to association

To reload a single-item association in Rails 5+, call #reload_<association>:

post.reload_author

In older Railses you can say

post.author(true)

Generate a strong secret from the shell

A good tool to generate strong passwords and secrets is "apg". You can get it with

sudo apt-get install apg

To create a strong secret for sessions, hashed Paperclip paths, etc. say

apg -m128 -a1 -E\'\"

Arguments explained:

  • The -m parameter defines the secret length
  • -a1 makes apg choose from all 7-bit ASCII characters instead of just the alphabet
  • -E\'\" excludes quote characters so you can easily paste the secret into a Ru...

How to: Use git bisect to find bugs and regressions

Git allows you to do a binary search across commits to hunt down the commit that introduced a bug.

Given you are currently on your branch's HEAD that is not working as expected, an example workflow could be:

git bisect start # Start bisecting
git bisect bad # Tag the revision you are currently on (HEAD) as bad. You could also pass a commit's SHA1 like below:
git bisect good abcdef12345678 # Give the SHA1 of any commit that was working as it should
# shorthand:
git bisect start <bad ref> <good ref>

Git will fetch a comm...

New gem: Rack::SteadyETag

Rack::SteadyETag is a Rack middleware that generates the same default ETag for responses that only differ in CSRF tokens or CSP nonces.

By default Rails uses Rack::ETag to generate ETag headers by hashing the response body. In theory this would enable caching for multiple requests to the same resourc...

Postgres: DISTINCT ON lets you select only one record per ordered attribute(s) for each group

  • To retrieve only unique combinations of the selected attributes: You can omit rows, where all selected columns are equal with the DISTINCT statement.
  • To retrieve the group wise maximum of certain columns: You can keep only one record for each group with the DISTINCT ON statement, to omit equal rows within each specified group.

Use case

You have a query where you want only one record for a set of specifically ordered attributes.

How to use?

Let's say we look at the example how to query only the latest post for each user:
...

How to send HTTP requests using cURL

  • Reading a URL via GET:

    curl http://example.com/
    
  • Defining any HTTP method (like POST or PUT):

    curl http://example.com/users/1 -XPUT
    
  • Sending data with a request:

    curl http://example.com/users -d"first_name=Bruce&last_name=Wayne"
    

    If you use -d and do not set an HTTP request method it automatically defaults to POST.

  • Performing basic authentication:

    curl http://user:password@example.com/users/1
    
  • All together now:

    curl http://user:password@example.com/users/1 -XPUT -d"screen_name=batman"
    

...

Terminator setup for Procfile-based applications for more comfortable debugging

We use foreman to start all necessary processes for an application, which are declared in a Procfile. This is very convenient, but the outputs of all processes get merged together. Especially while debugging you might not want other processes to flood your screen with their log messages.

The following setup allows you to start Terminator in a split view with the Rails server running in the left pane and all remaining processes running via foreman in the right pane. It was heavily inspired by [this card](https://makandracards.com/makandr...

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

Inspecting the page content in a Cucumber session

When you need to see the content of a page (i.e. not all the HTML but the relevant text body)

  • you can do pp (html_content)
    • pp will format the html String human readable pretty printed
  • where html_content can be replaced by one of the following commands:

Rails

body or response.body

Capybara:

  • page.driver.html.content
  • page.body

Webrat:

Nokogiri::HTML(response.body).content

The returned strings can be cleaned up by calling .gsub(/^\s*$/, '').squeeze("\n") on them.\
Although this may be useful for d...

How to silence Puma for your feature tests

When RSpecs runs the first feature spec, you may see log output like this:

Capybara starting Puma...
* Version 6.5.0, codename: Sky's Version
* Min threads: 0, max threads: 4
* Listening on http://127.0.0.1:39949

You can disable this behavior by tweaking Capybara's Puma server in your spec/support/capybara.rb:

Capybara.server = :puma, { Silent: true }

Note

You don't need to configure this if you're using system tests with modern versions of Rails. They do [exactly the same](https://github.com/rails/rails/blob/ma...