Documenting your project's Node.js version in .nvmrc
Not all versions of Node.js are compatible with each other. Also npm packages may require a minimum or maximum version of Node.js. We use nvm on our development PCs so we can operate multiple versions of Node.js in parallel.
To make sure that all developers use a compatible version of Node.js, your project should declare the required Node.js in a file called .nvmrc.
When a .nvmrc exists, developers can cd in your project directory and activate the p...
Merging two arbitrary ActiveRecord scopes
(Rails has a method ActiveRecord::Relation#merge that can merge ActiveRecord scopes. However, its behavior has never been clear, and in Rails 7 it still discards conditions on the same column by the last condition. We discourage using #merge!)
The best way to merge ActiveRecord scopes is using a subquery:
scope_a.where(id: scope_b)
It is a little less concise than #merge, but unambiguous.
Example
Assume a model where a deal has many documents:
class Deal < ApplicationRecord
has_many :...
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
requireall files yourself - no
active_supportniceties
Also, their scope...
Caching in Rails < 6.1 may down parts of your application when using public cache control
TL;DR When using Cache-Control on a Rails application, make sure the Vary: Accept header is set.
Proxy caching is a good feature to serve your publicly visible application content faster and reduce load on your servers. It is e.g. available in nginx, but also affects proxies delivered by ISPs.
Unfortunately, there is a little problem in Rails < 6.1 when delivering responses for different MIME-types. Say you have an arbitrary route in your Rails application that is able to respond with regular HTML and JSON. By sending the specific ...
Rails: How to write custom email interceptors
Nowadays it is fairly easy to intercept and modify mails globally before they are sent. All you have to do is register an interceptor class which responds to .delivering_email(message). This card will show you two common use cases.
Subject prefix:
Usually you want to prefix the subject line of emails with the current environment (except production) so you can differentiate between production mails and mails from other environments. Of course a...
Common mistakes when storing file uploads with Rails
1. Saving files to a directory that is not shared between deploys or servers
If you save your uploads to a made up directory like "RAILS_ROOT/uploads", this directory goes away after every deploy (since every release gets a new). Also this directory is not shared between multiple application servers, so your uploads are randomly saved to one local filesystem or another. Fixing this afterwards is a lot of fun.
Only two folders are, by default, shared between our application servers and deployments: "RAILS_ROOT/storage" and `"RAILS...
How to negate scope conditions in Rails
Sometimes you want to find the inverse of an ActiveRecord scope. Depending on what you want to achieve, this is quite easy with Rails 7, and a bit more complicated with Rails 6 and below, or when the inverse scope may contain NULL values. [1]
There are two different ways of "inverting a scope":
As an example, consider the following model.
class User < ApplicationRecord
scope :admins, -> { where(role: ['admin', 'superuser']) }
# ...
end
Mathematical NOT
You know this one from basic set theory. It proces the "complementa...
A reasonable default CSP for Rails projects
Every modern Rails app should have a Content Security Policy enabled.
Very compatible default
The following "default" is a minimal policy that should
- "just work" for almost all applications
- give you most of the benefits of a CSP
In your config/initializers/content_security_policy.rb, set
Rails.application.config.content_security_policy do |policy|
policy.object_src :none
policy.script_src :unsafe_eval, :strict_dynamic, :https # Browsers with support for "'strict-dynamic'" will ignore "https:"
po...
Custom error pages in Rails
Static error pages
To add a few basic styles to the default error pages in Rails, just edit the default templates in public, e.g. public/404.html.
A limitation to these default templates is that they're just static files. You cannot use Haml, Rails helpers or your application layout here. If you need Rails to render your error pages, you need the approach below.
Dynamic error pages
- Register your own app as the applicatio...
Rails: Fixing ETags that never match
Every Rails response has a default ETag header. In theory this would enable caching for multiple requests to the same resource. Unfortunately the default ETags produced by Rails are effectively random, meaning they can never match a future request.
Understanding ETags
When your Rails app responds with ETag headers, future requests to the same URL can be answered with an empty response if the underlying content ha...
Best practices: Writing a Rails script (and how to test it)
A Rails script lives in lib/scripts and is run with bin/rails runner lib/scripts/.... They are a simple tool to perform some one-time actions on your Rails application. A Rails script has a few advantages over pasting some prepared code into a Rails console:
- Version control
- Part of the repository, so you can build on previous scripts for a similar task
- You can have tests (see below)
Although not part of the application, your script is code and should adhere to the common quality standards (e.g. no spaghetti code). However, a scri...
Rails 8 introduces `params.expect`
The new params.expect method in Rails 8 improves parameter filtering, addressing issues with malformed input and enhancing security. It provides a cleaner, more explicit way to enforce the structure and types of incoming parameters.
What changed
-
Replaces
requireandpermit: Combines both methods for concise parameter validation. - Explicit Array Handling: Requires double array syntax to define arrays of hashes, improving clarity.
- Enhanced Validation: Ensures expected parameter structure, rejecting malformed input wi...
Rails: Using custom config files with the config_for method
You can use the config.x configuration in combination with config_for to configure global settings for your Rails 4.2+ application.
Example
In your config/application.rb assign the settings from e.g. config/settings.yml as follows:
module FooApplication
class Application < Rails::Application
config.x.settings = config_for(:settings)
end
end
The config/settings.yml might look as follows:
shared: &shared
email: info@example.com
...
Using partials in Rails views
Rails partials have a lot of "hidden" features and this card describes some non-obvious usages of Rails Partials.
Rendering a basic partial
The most basic way to render a partial:
render partial: 'weather'
This will render a _weather.html.erb file. Notice how all partials need to be prefixed with _.
It's possible to define local variables that are only defined in the partial template.
# _weather.html.erb
<h1>The weather is <%= condition %></h1>
# index.html.erb
render partial: 'weather', locals: { condition: ...
How to create Rails Generators (Rails 3 and above)
General
- Programatically invoke Rails generators
-
Require the generator, instantiate it and invoke it (because generators are
Thor::Groups, you need to invoke them withinvoke_all). Example:require 'generators/wheelie/haml/haml_generator' Generators::HamlGenerator.new('argument').invoke_allOther ways: Rails invokes its generators with
Rails::Generators.invoke ARGV.shift, ARGV. From inside a Rails generator, you may call the [inherited Thor methodinvoke(args=[], options={}, config={})](https://github...
Upgrading Rails 2 from 2.3.8 through 2.3.18 to Rails LTS
This card shows how to upgrade a Rails 2 application from Rails 2.3.8 through every single patch level up to 2.3.18, and then, hopefully, Rails LTS.
2.3.8 to 2.3.9
This release has many minor changes and fixes to prepare your application for Rails 3.
Step-by-step upgrade instructions:
- Upgrade
railsgem - Change your
environment.rbso it saysRAILS_GEM_VERSION = '2.3.9' - Change your ...
How to write complex migrations in Rails
Rails gives you migrations to change your database schema with simple commands like add_column or update.
Unfortunately these commands are simply not expressive enough to handle complex cases.
This card outlines three different techniques you can use to describe nontrivial migrations in Rails / ActiveRecord.
Note that the techniques below should serve you well for tables with many thousand rows. Once your database tables grows to millions of rows, migration performance becomes an iss...
Overview of method delegation in Rails
Method delegation in Rails can help you to keep your code organized and avoid deep call chains (law of demeter) by forwarding calls from one object to another. Rails provides several ways to accomplish this. Below is a concise overview of the most common approaches:
Single-Method delegation with delegate
Use the built-in delegate method from ActiveSupport to forward specific methods:
class User < ApplicationRecord
has_one :profile
delegate :full_name, :age, to: :profile, prefix: true
end
- `delegate: full_name, :age...
Rails: How to get the ordered list of used middlewares
Rails middlewares are small code pieces that wrap requests to the application. The first middleware gets passed the request, invokes the next, and so on. Finally, the application is invoked, builds a response and passes it back to the last middleware. Each middleware now returns the response until the request is answered. Think of it like Russian Dolls, where each middleware is a doll and the application is the innermost item.
You can run rake middleware to get the ordered list of used middlewares in a Rails application:
$> rake midd...
Unpoly + Nested attributes in Rails: A short overview of different approaches
This card describes four variants, that add a more intuitive workflow when working with nested attributes in Rails + Unpoly:
- Without JS
- With HTML template and JS
- With HTML template and JS using dynamic Unpoly templates
- Adding Records via XHR and JS
Example
For the following examples we use a simple data model where a user has zero or more tasks.
Unpoly: Showing the better_errors page when Rails raises an error
When an AJAX request raises an exception on the server, Rails will show a minimal error page with only basic information. Because all Unpoly updates work using AJAX requests, you won't get the more detailled better_errors page with the interactive REPL.
Below is an event listener that automatically repeats the request as a full-page load if your development error shows an error page. This means you get...
Rails: Preloading associations in loaded records
Sometimes you want to fetch associations for an ActiveRecord that you already loaded, e.g. when it has deeply nested associations.
Edge Rider gives your models a static method preload_associations. The method can be used to preload associations for loaded objects like this:
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
@user.preload_associations(threads: { posts: :author }, messages: :sender)
end
end
The attached initializers re...
Your database tables should probably have timestamps
Whenever you create a table from a database migration, remember to add updated_at and created_at timestamps to that table. Without those timestamps, investigating future bug reports will be hell. Always have timestamps.
Adding timestamps to new tables
When you create a table using create_table, you can add timestamps by using the timestamps shortcut:
class CreateEpisode < ActiveRecord::Migration
def change
create_table :episodes do |t|
t.string :name
t.timestam...
PSA: "index: true" in Rails migrations does not work as you'd expect
Several Rails migration methods accept index: true as an option to create an index. In some cases (like #add_column), this option is silently discarded. Know what you are doing, or use #add_index instead.
Example
Consider the following migration.
class CreateExamples < ActiveRecord::Migration
def change
create_table :examples do |t|
t.references :category, index: true
t.boolean :positive, index: true
t.integer :number_of_participants, index: true
end
add_reference :examples, :user, index: tr...