The numericality validator does not care about your BigDecimal precision

Looking at the source code of the validates_numericality_of validator, it becomes clear that it converts the attribute in question to either an integer or float:

if configuration[:only_integer]
  unless raw_value.to_s =~ /\A[+-]?\d+\Z/
    record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
    next
  end
  raw_value = raw_value.to_i
else
 begin
    raw_value = Kernel.Float(raw_val...

Preventing users from uploading malicious content

When you allow file uploads in your app, a user might upload content that hurts other users.

Our primary concern here is users uploading .html or .svg files that can run JavaScript and possibly hijack another user's session.

A secondary concern is that malicious users can upload executables (like an .exe or .scr file) and use your server to distribute it. However, modern operating systems usually warn before executing files that were downloaded from t...

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

Jasmine: Preventing unhandled promise rejections from failing your test

You have an async function that rejects:

async function failingFunction() {
  throw new Error("Something went wrong")
}

When you call that function in a test, your test will fail:

it('has a test', function() {
  failingFunction() // this will fail your test
})

The failure message will look like this:

Unhandled promise rejection: Error: Something went wrong

You can fix this by expecting the state of the returned promise:

it('has a test', async function() {
  await expectAsync(failingFunction()).toBeRej...

ActiveRecord::Relation#merge overwrites existing conditions on the same column

In Ruby on Rails ActiveRecord::Relation#merge overwrites existing conditions on the same column. This may cause the relation to select more records than expected:

authorized_users = User.where(id: [1, 2])
filtered_users   = User.where(id: [2, 3])
authorized_users.merge(filtered_users).to_sql
# => SELECT * FROM users WHERE id IN (2, 3)

The merged relation select the users (2, 3), although we are only allowed to see (1, 2). The merged result should be (2).

This card explores various workarounds to combine two scopes so t...

Understanding database Indexes in PostgreSQL

I found the linked article very helpful to refresh my understanding of database indexes. As a small bonus, it includes a few helpful SQL oneliners like these two:

Warning

Do not run random code snippets unless you understand them in detail - especially in production.

How Haml 6 changes attribute rendering, and what to do about it

Haml 6 was a major rewrite with performance in mind. To achieve a performance improvement of 1.7x, some design trade-offs had to be made. The most notable change might be the simplified attribute rendering.

In Haml 5, attribute rendering knew two special cases: an attribute with value true would be rendered without a value, an attribute with a falsy value would not be rendered at all. All other values would just be rendered as attribute values.

According to the Haml maintai...

Simple Form: Rendering errors without an appropriate attribute

Usually you add errors to :base in ActiveRecord, in case no appropriate attribute could be used to add the error.

Simple Form doesn't render errors on :base by default, but here a few options how you can render these on demand. For all the options below we use the following example with a Simple Form Bootstrap configuration:

- @user = Backend::User.new
- @user.errors.add(:base, 'First error')
- @user.errors.add...

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

Beware when using ActiveSupport time and date calculation methods

The pitfall

Rails Active Support provides some helpful methods for calculating times and dates, like Duration#ago or Duration#from_now. But beware when using those, because they wont give you Dates or Times but ActiveSupport::TimeWithZone instances. As the class name hints, you now have to be awa...

Rails: Composing an ETag from multiple records

Rails offers the fresh_when method to automatically compute an ETag from the given record, array of records or scope of records:

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    fresh_when @user
  end  
  
  
  def index
    @users = User.all.to_a
    fresh_when @users
  end
end

When your view also displays other records (typically associations), those other records should be included in the ETag. You can do so by passing an array of ETaggable objects to fresh_when.

...

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

Rails migration: Changing a column type without losing the content

The change_column method for rails migrations support casting with a custom SQL statement. This allows us to change a column type and keep the former content as the new type. This way, we can for example prepare an address number column to hold German address numbers, which can contain letters:

Example (in most cases not a good idea!)

class ChangeAnIntegerColumnToString < ActiveRecord::Migration[6.1]
  def up
    change_column :users, :address_number, 'varchar USING CAST(rating AS varchar)'
  end

  def down
    change_column ...

Issue Checklist Template

This is a checklist I use to work on issues. For this purpose I extracted several cards related to the makandra process and ported them into a check list and refined that over time a little bit.

This task list is divided by the Gate keeping process in the following steps:

1. Starting a new feature
2. Working on the issue
3. Finishing a feature
4. After Review

Here are some ti...

Rails Partials

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: 'partial' 

This will render a _partial.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: ...

Rails: Using database default values for boolean attributes

In the past we validate and set default values for boolean attributes in Rails and not the database itself.

Reasons for this:

  • Older Rails didn't support database defaults when creating new records
  • Application logic is "hidden" in the database

An alternative approach, which currently reflects more the general opinion of the Rails upstream on constraints in the database, is adding default values in the schema of the database itself. We also ...

Carrierwave: Custom file validations inside custom Uploaders

Carrierwave's BaseUploader can have some validations that you can use by overriding a certain method, which's expected name is hard coded. A popular example is extension_allowlist, which returns an array of strings and let's you only upload files that have a filename with an extension that matches an entry in that array. Another useful validation can be size_range, which gives you a little bit of control over how your storage gets polluted.

This is often good enough, but some times you need to validate special cases.

Validations t...

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

git: find the version of a gem that releases a certain commit

Sometimes I ran across a GitHub merge request of a gem where it was not completely obvious in which version the change was released. This might be the case for a bugfix PR that you want to add to your project.

Git can help you to find the next git tag that was set in the branch. This usually has the name of the version in it (as the rake release task automatically creates a git tag during release).

git name-rev --tags <commit ref>

Note

The more commonly used git describe command will return the last tag before a c...

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

Heads up: expect(object).to receive(:method_name) does not execute the original implementation of the method

Let's assume that we have a model Movie that registers a callback function when a new instance of Movie is created (Note: For the purpose of this card it is not important what that callback does or which type of callback it is).

This is how we test whether the callback function (here it is named :my_method) is called when a new movie is created:

expect_any_instance_of(Movie).to receive(:my_method)
create(:movie)  # <-- this is where the method :my_method should be called

You might expect that when calling `create(:mo...

Signed URLs with Ruby on Rails

Using ActiveRecord's #signed_id and .find_signed methods you can create URLs that expire after some time. No conditionals or additional database columns required.

Why Sidekiq Jobs should never be enqueued in an `after_create` or `after_save` callback

When an object is created / updated, various callbacks are executed in this order:

before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit / after_rollback

Thus, each of these callbacks is executed at a specific time in the life cycle of the object. This is important because this point in time determ...

Ruby object equality

TLDR

if you define a equality method for a class you must also implement def hash.

Ruby has a lot of methods that have to do something with equality, like ==, ===, eql?, equal?. This card should help you differentiate between those and give you hints on how to implement your own equality methods in a safe manner.

Differences between the methods

for everyday use: ==

When you compare two objects in ruby, you most often see the use of foo == bar. By default the == operator inherits from Object and is impl...