Rails I18n fallback locales
When you need to create a locale for a language variant (like Austrian for German), you probably don't want to duplicate your entire de.yml
file only to change a few minor exceptions for our Austrian friends.
Luckily, the I18n gem used by Rails has a fallback feature where you can make one locale file fall back to another if no translation is available.
In the example above you would have a config/locales/de_DE.yml
:
de_DE:
# hundreds of translations here
... and another...
Configuring ActionMailer host and protocol for URL generation
When you generate a URL in a mailer view, ActionMailer
will raise an error unless you previously configured it which hostname to use.
There are two options to set the default_url_options
of ActionMailer:
- Hardcoded solution (preferred solution when using Rails with ActiveJob/Sidekiq or Cronjobs)
- Dynamic solution
1. Hardcoded solution
When you are sending mails from outside the request cycle, e.g. ActiveJob/Sidekiq or Cronjobs, y...
Popular mistakes when using nested forms
Here are some popular mistakes when using nested forms:
- You are using
fields_for
instead ofform.fields_for
. - You forgot to use
accepts_nested_attributes
in the containing model. Rails won't complain, but nothing will work. In particular,nested_form.object
will benil
. - The
:reject_if
option lambda in youraccepts_nested_attributes
call is defined incorrectly. Raise the attributes hash given to your:reject_if
lambda to see if it looks like you expect. - If you are nesting forms into nested forms, each model involved ne...
How to use Ubuntu in English, but still show German formats
If you want to have an English Ubuntu UI, but still see dates, money amounts, paper formats, etc. in German formats, you can fine-tune your /etc/default/locale
like this:
LANG="en_US.UTF-8"
LC_CTYPE="de_DE.UTF-8"
LC_NUMERIC="de_DE.UTF-8"
LC_TIME="de_DE.UTF-8"
LC_COLLATE="de_DE.UTF-8"
LC_MONETARY="de_DE.UTF-8"
LC_PAPER="de_DE.UTF-8"
LC_NAME="de_DE.UTF-8"
LC_ADDRESS="de_DE.UTF-8"
LC_TELEPHONE="de_DE.UTF-8"
LC_MEASUREMENT="de_DE.UTF-8"
LC_IDENTIFICATION="de_DE.UTF-8"
Make sure you have both en...
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 :...
Git: Advisory for cherry-picks to production branches
We often have a separate production branch that lags a bit behind the more cutting edge main branch. Sometimes you want to move some, but not all commits from main to production. This can be done with a git cherry-pick
.
However, this may lead to considerable pain later, since git does not understand the commits are actually "the same". Hazards are unnecessary and hard to resolve conflicts as well as incorrect auto-merges.
In order to avoid this, always merge the production branch back to the main after the cherry-pick. Even t...
Use find_in_batches or find_each to deal with many records efficiently
Occasionally you need to do something directly on the server -- like having all records recalculate something that cannot be done in a migration because it takes a long time.
Let's say you do something like this:
Project.all.each(&:recalculate_statistics!)
Even though you may have been successful with this on your development machine or the staging server, keep in mind that production machines often hold a lot more records. Using all
may just work, even with lots of records, but when you iterate over such records and fetch a...
How DECIMAL columns deal with numbers exceeding their precision or scale
When storing floating-point numbers such as prices or totals in an SQL database, always use a DECIMAL
column. Never use FLOAT
or kittens will die.
DECIMAL
columns are parametrized with a precision and a scale. These parameters describe which numbers can be stored in that column. E.g. a decimal with a precision of 5 and a scale of 2 can store numbers from -999.99
to 999.99
, but not 1000
or 1.234
.
This card explains what various databases do when you try to store a number in a DECIMAL
field, and that number exceeds that colum...
Cancelling event propagation
Within an event handler, there are multiple methods to cancel event propagation, each with different semantics.
-
event.preventDefault()
Only prevents the default browser behavior for the click, i.e. going to a different url or submitting a form.
When invoked on a
touchstart
event, this also prevents mouse events likeclick
to be triggered. -
event.stopPropagation()
Prevents the event from bubbling up the DOM.
-
`event.st...
ActiveRecord: Creating many records works faster in a transaction
When you need to insert many records into the same table, performance may become an issue.
What you can do to save time is to open a transaction and save multiple records within that transaction:
transaction do
500.times { Model.create! }
end
Although you will still trigger 500 INSERT
statements, they will complete considerably faster.
When I tried it out with a simple model and 500 iterations, the loop completed in 1.5 seconds vs. 6 seconds without a transaction.
Alternative
Another fast way to insert many ...
Why two Ruby Time objects are not equal, although they appear to be
So you are comparing two Time
objects in an RSpec example, and they are not equal, although they look equal:
expected: Tue May 01 21:59:59 UTC 2007,
got: Tue May 01 21:59:59 UTC 2007 (using ==)
The reason for this is that Time
actually tracks fractions of a second, although #to_s
doesn't say so and even though you probably only care about seconds. This means that two consecutive calls of Time.now
probably return two inequal values.
Consider freezing time in your tests so it is not dependent on the speed of the executi...
Defining class methods with Modularity traits
There are two ways to define a class method from a Modularity trait. Note that the usual caveats regarding class method visibility apply.
Using define_method
The recommended way is to define a method on your module's singleton class:
module SomeTrait
as_trait do
define_singleton_method :foo do
# ...
end
end
end
Using def (has...
Testing if two date ranges overlap in Ruby or Rails
A check if two date or time ranges A and B overlap needs to cover a lot of cases:
- A partially overlaps B
- A surrounds B
- B surrounds A
- A occurs entirely after B
- B occurs entirely after A
This means you actually have to check that:
- neither does A occur entirely after B (meaning
A.start > B.end
) - nor does B occur entirely after A (meaning
B.start > A.end
)
Flipping this, A and B overlap iff A.start <= B.end && B.start <= A.end
The code below shows how to implement this in Ruby on Rails. The example is a class `Interv...
How to define constants with traits
When defining a trait using the Modularity gem, you must take extra steps to define constants to avoid caveats (like when defining subclasses through traits).
tl;dr
In traits, always define constants with explicit
self
.
If your trait defines a constant inside the as_trait
block, it will be bound to the trait module, not the class including the trait.
While this may seem unproblematic at first glance, it becomes a problem when including trai...
Don't mix Array#join and String#html_safe
You cannot use Array#join
on an array of strings where some strings are html_safe
and others are not. The result will be an unsafe string and will thus be escaped when rendered in a view:
unsafe_string = '<span>foo</span>'
safe_string = '<span>bar</span>'.html_safe
[unsafe_string, safe_string].join(' ') # will incorrectly render as '<span>foo</span><span&t;bar</span>'
Bad
The solution is not to call html_safe
on the joined array and if you thought it would be, you [don't understand how XSS prot...
Preloaded associations are filtered by conditions on the same table
When you eagerly load an association list using the .include
option, and at the same time have a .where
on an included table, two things happen:
- Rails tries to load all involved records in a huge single query spanning multiple database tables.
- The preloaded association list is filtered by the
where
condition, even though you only wanted to use thewhere
condition to filter the containing model.
The second case's behavior is mostly unexpected, because pre-loaded associations usually don't care about the circumstances under whi...
Copying validation errors from one attribute to another
When using virtual attributes, the attached trait can be useful to automatically copy errors from one attribute to another.
Here is a typical use case where Paperclip creates a virtual attribute :attachment
, but there are validations on both :attachment
and :attachment_file_name
. If the form has a file picker on :attachment
, you would like to highlight it with errors from any attribute:
class Note < ActiveRecord::Base
has_attached_file :attachment
validates_attachment_presence :a...
Know your Haml comments
There are two distinct ways of commenting Haml markup: HTML and Ruby.
HTML comments
This will create an HTML comment that will be sent to the client (aka browser):
/= link_to 'Example', 'www.example.com'
This produces the following HTML:
<!-- = link_to 'Example', 'www.example.com' -->
Only use this variant if you need the comment to appear in the HTML.
Ruby comments
This will comment code so it will not be sent to the client:
-# = link_to 'foo'
99% of the time you'll be adding notes f...
Load order of the environment
Rails 3, 4, 5, 6
config/application.rb
-
config/environment.rb
before theinitialize!
call (we don't usually edit this file) - The current environment, e.g.
environments/production.rb
- Gems
- Vendored plugins
- All initializers in
config/initializers/*.rb
-
config/environment.rb
after theinitialize!
call (we don't usually edit this file) - Your own code from
app
Rails 2
- Code in
config/preinitializer.rb
(if it exists) -
environment.rb
, code above theRails::Initializer.run
blo...
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...
Use the Git stash without shooting yourself in the foot
The Git stash does not work like a one-slot clipboard and you might shoot yourself in the foot if you pretend otherwise.
In particular git stash apply
does not remove the stashed changes from the stash. That means you will probably apply the wrong stash when you do git stash apply
after a future stashing.
To keep your stash clean, you can use
git stash pop
instead.
Another way to look at it:
git stash pop
is the same as
git stash apply && git stash drop
Notice: In case of a conflict git will not pop th...