Apply a new callback to existing records

So you added a new callback to your model that (e.g.) caches some data when it is saved. Now you need to run that callback for the 10000 existing records in the production database. You have two options here:

  1. Write a clever migration, possibly by embedding the model into the migration script.
  2. Open the Rails console after deployment and re-save every single record. You should probably add two chores to your issue tracker so you won't forget t...

Collection of Rails development boosting frameworks

Development environment setup

Rails Composer

Basically a comprehensive Rails Template. Prepares your development environment and lets you select web server, template engine, unit and integration testing frameworks and more.

Generate an app in minutes using an application template. With all the options you want!

Code generators

Rails Bricks

A command line wizard. Once you get it running, it creates sleek applications.

RailsBricks enables you to cre...

Automatic Log Rotation in Rails

Rails log files rotate automatically when they reach approx. 100MB:

$ ls -lh log/
-rw-r--r-- 1 user group  55M Sep 15 09:54 development.log
-rw-r--r-- 1 user group 101M Aug 22 13:45 development.log.0

This behavior is a built-in feature of Ruby's standard Logger class, which Rails uses by default.

To control the maximum file size, set config.log_file_size in yo...

Maintaining custom application tasks in Rails

Here are some hints on best practices to maintain your tasks in larger projects.

Rake Tasks vs. Scripts

  • The Rails default is using rake tasks for your application tasks. These live in lib/tasks/*.
  • In case you want to avoid rake for your tasks and just use plain ruby scripts, consider lib/scripts/* as folder.

Keeping tasks slim

For readability and testing it's easier to keep your tasks slim. We suggest to use folders inside the tasks or scripts folder.

Example for a task:

The slim task lib/tasks/gitlab.rb:

Rails: Using normalizes without copying code

Rails 7.1 added the normalizes method which can be used to normalize user input.

It lets you define the fields you want to normalize and how to normalize them. In the example below, the Movie#title attribute is stripped from leading and trailing whitespace automatically:

class Movie < ApplicationRecord
  normalizes :title, with: -> {  _1.strip }
end

Tip

Normalization lambdas are not called for nil values by default. To normalize nil values, pa...

Rails: Destroying vs Deleting

Rails offers several ways to remove records. They differ in whether they instantiate records, fire callbacks (including dependent: associations) and how they manage relation state afterward.

destroy_all

This is the definition of destroy_all:

# ActiveRecord::Relation
def destroy_all
  records.each(&:destroy).tap { reset }
end
  • records evaluates SQL, caches result
  • .each(&:destroy) iterates the cached Array, returns it
  • .tap { reset } calls Relation#reset, clears records and loaded, returns the Array un...

Rails cache connection settings

If you're using a Redis cache in Rails (e.g. :redis_cache_store), it's possible to configure additional parameters for your Redis connection.

Example config for Rails 7.2

config.cache_store = :redis_cache_store, {
  pool: { timeout: 0.5 },
  read_timeout: 0.2, # default 1 second
  write_timeout: 0.2, # default 1 second
  # Attempt two reconnects with some wait time in between
  reconnect_attempts: [1, 5], # default `1` attempt in Redis 5+
  url: REDIS_URL,
  error_handler: ->(method:, returning:, exception:) {
    Sentry.captur...

routing-filter is broken with Rails 7.1

If you are using the routing-filter gem in your Rails 7.1 app for managing URL segments for locales or suffixes, you will notice that the filters do no longer apply, routes are broken and the necessary parameters are no longer extracted. That is because routing-filter patches Rails' find_routes-method to get the current path and apply its defined filters on it. These filters then modify the params that are handed over to your controller action. This way you receive a locale parameter from a ...

Logging multiple lines in Rails without making filtering your logs difficult

Rails' default logger prefixes each log entry with timestamp and tags (like request ID).
For multi-line entries, only the first line is prefixed which can give you a hard time when grepping logs.

Example

Rails.logger.info(<<~TEXT)
  Response from example.com:
  Status: 200
  Body: It works!
TEXT

With that, the following is written to your log file.

I, [2024-10-04T08:12:16.576463 #1917250]  INFO -- : [97e45eae-a220-412d-96ad-e9e148ead71d] Response from example.com:
Status: 200
Body: It works!

If you then run `grep...

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

Rails: Join model table migration template

When creating a database table for a join model without further importance, you can use Rails' create_join_table:

class CreateSchoolsStudents < ActiveRecord::Migration[7.2]
  def change
    create_join_table :schools, :students, column_options: { foreign_key: true } do |t|
      # t.timestamps # Optional
      t.index [:student_id, :school_id], unique: true
    end
  end
end

This will create a table without an id column and without timestamps. It will have school_id and student_id columns with null: false constraints ...

How to disable irb's welcome banner or how to enable it for the Rails console

Version 1.18.0 of irb introduced a welcome banner.
Here is how to disable it, or how to add it to your Rails console.

Welcome banner

Modern versions of irb show a pretty banner which prints

  • IRB version,
  • Ruby version,
  • a random IRB command hint,
  • current directory.

irb Screenshot

How to disable

If you prefer not to see this banner, update your ~/.irbrc (or create, if you don't have one yet) and add:

IRB.conf[:SHOW_BANNER]...

Upgrading a Rails app to Cucumber 3

Upgrade gems

You need to update a lof gems. Make sure you don't have any version constraints in your Gemfile or your bundle update won't do anything!

Upgrade cucumber_priority:

bundle update cucumber_priority

Upgrade spreewald:

bundle update spreewald

Upgrade cucumber_factory:

bundle update cucumber_factory

Upgrade parallel_tests:

bundle update parallel_tests

Even on the latest version, parallel_tests will print some deprecation warnings due to using an older formatter A...

Using PostgreSQL ranges with ActiveRecord

Postgres supports multiple built-in range datatypes:

  • int4range
  • int8range
  • numrange
  • tsrange (range with timestamp without timezone)
  • tstzrange (range with timestamp with timezone)
  • daterange

They represent a start and endpoint of something in a single column. Image you're building a vacation booking feature:

create_table :events do |t|
  t.date :starts_on
  t.date :ends_on
end

This is how you would use a range type in a migration:

create_table :vacations do |t|
  t.daterange :period
end

See [t...

Rails: namespacing models with table_name_prefix instead of table_name

When you want to group rails models of a logical context, namespaces are your friend. However, if you have a lot of classes in the same namespace it might be tedious to specify the table name for each class seperately:

class Accounting::Invoice < ApplicationRecord
  self.table_name = 'accounting_invoices'
  ...
end

class Accounting::Payment < ApplicationRecord
  self.table_name = 'accounting_payments'
  ...
end

A replacement for the self.table_name-assignment is the table_name_prefix in the module definition:

modu...

Rails: Example on how to extract domain independent code from the `app/models` folder to the `lib/` folder

This cards describes an example with a Github Client on how to keep your Rails application more maintainable by extracting domain independent code from the app/models folder to the lib/ folder. The approach is applicable to arbitrary scenarios and not limited to API clients.

Example

Let's say we have a Rails application that synchronizes its users with the Github API:

.
└── app
    └── models
        ├── user
        │   ├── github_client.rb
        │   └── sychronizer.rb
        └── user.rb

In this example the app folder ...

Rails: Using require and permit for attributes

Raising errors for required and permitted attributes makes it easier to find errors in your application during development and in tests. Consider this approach if you want to strengthen the params handling in your application.

Example

# config/application.rb

config.action_controller.action_on_unpermitted_parameters = :raise
def user_params
  params.require(:user).permit(:full_name)
end

Effects

  • This raises an error `Ac...

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

Upgrade Rails: Awareness list

Disclaimer

This card is a collection of guides and things to have in mind when upgrading to a specific version. It is not meant to be complete, so please feel free to contribute!

General workflows

Upgrade to Rails 7

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. [Compose Rails authentication primitives](https://makandracards...

Rails 3: Sending tempfiles for download

When you create a temporary file (e.g. to store a generated Excel sheet) and try to send it to the browser from a controller, it won't work by default. Take this controller action:

class FoosController < ApplicationController
  def download
    file = Tempfile.new('foo')
    file.puts 'foo'
    file.close
    send_file file.path
  end
end

Accessing this controller action will usually raise a 404 not found in the browser and the Apache log will say:

The given path was above the root path: xsendfile: unable to find file: /tm...

Rails: Comparison of assignable_values and Active Record enum types

We are using assignable_values for managing enum values in Rails. Nevertheless Rails is adding more support for enum attributes, allowing to have a closer look at the current feature set in comparison to our still preferred option assignable_values.

Active Record enum attribute interface

By default Rails is mapping enum attributes to integers:
...

Rails: Report CSP Violations to Sentry

You can report CSP violations to Sentry.

Within config/initializers/content_security_policy.rb:

Rails.application.configure do
  config.content_security_policy do |policy|
    # Settings for the policy

    policy.report_uri 'https://ooo4444bbb.ingest.de.sentry.io/api/ooo4444bbb/security/?sentry_key=ooo4444bbb'
  end
end

Replace the actual report_uri with the one from your project settings under https://makandra-eu.sentry.io/settings/projects/<project-name>/security-headers/. Replace <project-name> with the actual name of...

How to keep using secrets.yml after upgrading to Rails 7.2

Rails 5.2 soft-deprecated the storage of secrets in secrets.yml in favor of a new thing, credentials.yml.enc. Rails 7.1 deprecated secrets and Rails 7.2 finally removed it.

In our permissions model, it does not matter much whether secrets or credentials are used. While we'll use credentials in new applications (for conformity), for existing applications it may be appropriate to keep using secrets.yml.

Restoring secrets in Rails 7.2+

Restoring `Rails.applic...