How to upgrade Rails: Workflow advice
When upgrading Rails versions -- especially major versions -- you will run into a lot of unique issues, depending on the exact version, and depending on your app.
However, it is still possible to give some generic advice on how you want to tackle the update in principle.
If you are not really confident about upgrading Rails, have a look at Rails LTS.
How many update steps?
Besides the Rails upgrade itself, you might also want to upgrade your other gems and upgrade your Ruby version.
First decide in how many st...
Rails: Looking up constants by their name string
TL;DR: Rails ships two methods to convert strings to constants, constantize
and safe_constantize
. Neither is safe for untrusted user input. Before you call either method you must validate the input string against an allowlist. The only difference between the two methods is that unresolvable constants raise an error with constantize
, but return nil
with safe_constantize
. If you validate the input string against an allowlist, an error should never happen.
Preventing Dangerous Lookups
Suppose an application uses eit...
How to: Rails cache for individual rspec tests
Rails default config uses the ActiveSupport::Cache::NullStore
and disables controller caching for all environments except production:
config.action_controller.perform_caching = false
config.cache_store = :null_store
If you want to test caching you have at least two possibilities:
- Enable caching for every test (not covered by this card and straightforward)
- Enable caching for individual test
Enable caching for individual test (file cache)
1. Leave the defau...
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 ...
Organize large I18n dictionary files in Ruby on Rails
If you're suffering from a huge de.yml
or similiar file, cry no more. Rails lets you freely organize your dictionary files in config/locales
.
My organization works like this:
-
config/locales/rails.de.yml
modified Rails boilerplate -
config/locales/faker.de.yml
modified Faker boilerplate -
config/locales/models.de.yml
model names, attribute names, assignable_value labels - `config/locales/views.de.y...
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: Using PostgreSQL full-text search without a gem
PostgreSQL can cosplay as a full-text search engine. It doesn't have the features or fidelity of ElasticSearch or Algolia, but it's good enough if you just need to search and rank large volumes of text.
This card will teach you how to index, search and rank your Rails models in a PostgreSQL full-text index. We will do this without using any gems aside from ActiveRecord. While there are gems like pg_search or pg_fulltext, manual integration requires very...
Rails: Keeping structure.sql stable between developers
Why Rails has multiple schema formats
When you run migrations, Rails will write your current database schema into db/schema.rb
. This file allows to reset the database schema without running migrations, by running rails db:schema:load
.
The schema.rb
DSL can serialize most common schema properties like tables, columns or indexes. It cannot serialize more advanced database features, like views, procedures, triggers or custom ditionaries. In these cases you must switch to a SQL based schema format:
# in application.rb
config.a...
Rails: Pluck across associated tables
#pluck
is commonly used as a performant way to retain single database values from an ActiveRecord::Relation
Book.pluck(:title, :price) #=> [["The Hobbit", "8.99"], ["The Alchemist", "7.89"]]
But #pluck
can do more: you can query multiple tables as well!
Book.joins(:author).pluck("books.title, books.price, authors.name") #=> [["The Hobbit", "8.99", "J. R. R. Tolkien"], ["The Alchemist", "7.89", "Paulo Coelho"]]
Note the use of :author
for the joins, and then authors
for the pluck clause. The first corresp...
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:
...
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...
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...
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 normalizenil
values, pa...
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...
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...
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...
Rails asset pipeline: Using ESNext without a transpiler
If your app does not need to support IE11, you can use most ES6 features without a build step. Just deliver your plain JavaScript without transpilation through Babel or TypeScript, and modern browsers will run them natively.
Features supported by all modern browsers include:
- fat arrow functions (
() => { expr }
) -
let
/const
class
-
async
/await
- Promises
- Generators
- Symbols
- Rest arguments (
...args
) - Destructuring
You won't be able to use import
and export
, or use npm modules.
See this [ES6 compatibility mat...
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/
...
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
- Don't use log level :debug in your production environments
- [Rails 7.1: Take care of...
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...
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 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
```...
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...
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...