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

Detect the current Rails environment from JavaScript or CSS

Detecting if a Javascript is running under Selenium WebDriver is super-painful. It's much easier to detect the current Rails environment instead.

You might be better of checking against the name of the current Rails environment. To do this, store the environment name in a data-environment of your <html>. E.g., in your application layout:

<html data-environment=<%= Rails.env %>>

Now you can say in a pi...

Searchkick: async reindexing fails for rails 7 with redis 4

After an upgrade to rails 7 I noticed that async reindexing jobs of Searchkick were failing for Model.reindex(mode: :async, wait: true):

/home/a_user/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/searchkick-5.3.1/lib/searchkick/relation_indexer.rb:142:in `block in batch_job': undefined method `call' for nil (NoMethodError)

    Searchkick.with_redis { |r| r.call("SADD", batches_key, [batch_id]) }
                                 ^^^^^
from /home/a_user/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/searchkick-5.3.1/lib/searchkick.r...

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

How to organize monkey patches in Ruby on Rails projects

As your Rails project grows, you will accumulate a number of small patches. These will usually fix a bug in a gem, or add a method to core classes.

Instead of putting many files into config/initializers, I recommend to group them by gem in lib/ext:

lib/
  ext/
    factory_girl/
      mixin.rb
    carrierwave/
      change_storage.rb
      fix_cache_ids.rb
      sanitize_filename_characters.rb
    ruby/
      range/
        covers_range.rb
      array/
        dump_to_excel.rb
        xss_aware_join.rb
      enumerable/
    ...

Rails: Flagging all cookies as secure-only to pass a security audit

Why secure-only cookies used to be necessary

Cookies have an optional secure flag. It tells the browser to not send the cookie for a non-https request.

It used to be important to activate the secure flag even on sites that automatically redirect users from http:// to https://. The reason was that most users will only enter a scheme-less domain like makandra.de into their location bar, which will default to `http://m...

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

Fragment Caching in Rails 7.1+ requires Haml 6

Rails slightly changed the fragment cache implementation from Rails 7.0 to Rails 7.1. Unfortunately, this is incompatible with how Haml 5 patches Action View buffers. I tried turning a String buffer into an ActionView::OutputBuffer, but this brought up...

Taming icon fonts for use in Rails views

Icon fonts like Font Awesome are infinitely scalable, look great on high-DPI displays and will give your app a modern look.

However, icon fonts can be very awkward to use compared to raster icons. Elements are given icons by giving them a special class like icon-plus or icon-home:

<span class="icon-plus">Create</span>

The icon font's stylesheet will then recognize this class and insert the icon as the element's :before style.

In practic...

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

Creating a Rails application in a single file

Greg Molnar has written a neat article about creating a single-file Rails app.
This is not meant for production use but can be useful to try things out, e.g. when hunting down a bug or embedding a Rails app into the tests of a gem.

What you do is basically:

  1. Put everything (gems, application config, database migrations, models, controllers) into a single .ru file, like app.ru.
  2. Run it via rackup app.ru. (Hint: if your file is called config.ru, you can just run `rac...

Caveat when using Rails' new "strict locals" feature

In Rails 7.1 it has become possible to annotate partials with the locals they expect:

# partial _user_name.erb
<%# locals: (user:) %>
<%= user.name %>

# view
<%= render 'user_name' %> <%# this raises an ArgumentError %>

Unfortunately, when some other code in that template raises an ArgumentError (for example an error in the User#name method) you will end up with a confusing stacktrace that looks like you have an error in your render call.

If th...

Rails: Assigning associations via HTML forms

Let's say we have posts with an attribute title that is mandatory.

Our example feature request is to tag these posts with a limited number of tags. The following chapters explain different approaches in Rails, how you can assign such an association via HTML forms. In most cases you want to use Option 4 with assignable values.

The basic setup for all options looks like this:

config/routes.rb

Rails.application.routes.draw do
  root "posts#index"
  resources :posts, except: [:show, :destroy]
end

**db/migrate/...

Rails 6.1: where.not changes behaviour from NOR to NAND

Since Rails 6.1, if we use where.not with multiple attributes, it applies logical NAND (NOT(A) OR NOT(B)) instead of NOR (NOT(A) AND NOT(B)). If you do not take care, this change will increase the matched set.

Examples

"Don't send newsletters neither to admins nor to trashed users!" becomes "Don't send newsletters to trashed admins".

User.where.not(role: 'admin', trashed: true)
# Before Rails 6.1, with NOR
=> "SELECT "users".* FROM "users" WHERE "users"."role" != 'admin' AND "users"."trashed" != TRUE"
# Equivale...

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: Passing array values to tag helpers like link_to

From at least Rails 4, the ActionView tag helper turns Array values of HTML options into a single space-separated string.
This means you can pass an array to :class options:

extra_classes = %w[one two]

= link_to 'Dashboard', root_path, class: ['btn', 'btn-primary', *extra_classes]
=> <a href="/" class="btn btn-primary one two">Dashboad</a>

= content_tag 'div', 'Hello World', class: %w[alert alert-info]
=> <div class="alert alert-info">Hello World</div>...

Tagging in Rails 4 using Postgres Arrays

Usage:

class Document < ActiveRecord::Base
  scope :any_tags, -> (tags){ where('tags && ARRAY[?]', tags) }
  scope :all_tags, -> (tags){ where('tags @> ARRAY[?]', tags) }
end

Document.create(title: "PostgreSQL", tags: ["pg","rails"])

Document.any_tags('pg')
Document.all_tags(['pg', 'rails'])

Migration:

class CreateDocuments < ActiveRecord::Migration
  def change
    create_table :documents do |t|
      t.string :title
      t.string :tags, array: true, default: []
      t.timestamps
    end
    add_index  :documents, :ta...

request_store: Per-request global storage for your Rails app

Ever needed to use a global variable in Rails? Ugh, that's the worst. If you need global state, you've probably reached for Thread.current.

When you're using Thread.current, you must make sure you're cleaning up after yourself. Else, values stored in one request may be available to the next (depending on your server). request_store wipes all data when a request ends and makes per-request global storage a no-brainer. Internally, it's using Thread.current with a Hash in a simple middleware.

Example: Remembering all currently a...

Rails: Encrypting your database information using Active Record Encryption

Since Rails 7 you are able to encrypt database information with Active Record. Using Active Record Encryption will store an attribute as string in the database. And uses JSON for serializing the encrypted attribute.

Example:

  • p: Payload
  • h: Headers
  • iv: Initialization Vector
  • at: Authentication Tag
{ "p": "n7J0/ol+a7DRMeaE", "h": { "iv": "DXZMDWUKfp3bg/Yu", "at": "X1/YjMHbHD4talgF9dt61A=="} }

Note this before encrypting attributes with Active Record:
...

Rails SQL Injection Examples

This page lists many query methods and options in ActiveRecord which do not sanitize raw SQL arguments and are not intended to be called with unsafe user input. Careless use of these methods can open up code to SQL Injection exploits. The examples here do not include SQL injection from known CVEs and are not vulnerabilites themselves, only potential misuses of the methods.

Please use this list as a guide of what not to do.

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

Rails 7 adds #caching? and #uncacheable!

Rails' fragment caching caches subtrees of an HTML document tree. While constructing that tree though, it can be really hard to keep track of whether some code is run in a caching context. Fortunately, Rails 7 brings two helpers that simplify this.

Note that these helpers are all about Rails' fragment caching and not about downstream caching (i.e. Cache-Control).

uncacheable!

Invoke this helper in a partial or another helper that should never be cached. Used outside of fragment caches, the helper does just nothing. But should it ...

esbuild: Make your Rails application show build errors

Building application assets with esbuild is the new way to do it, and it's great, especially in combination with Sprockets (or Propshaft on Rails 7).
You might be missing some convenience features, though.

Here we cover one specific issue:
Once you have started your development Rails server and esbuild with the --watch option (if you used jsbundling-rails to set up, you probably use bin/dev), esbuild will recompile your assets upon change, but build errors will only be printed to the terminal. Your application won't complain about them ...