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...
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...
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 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/
...
Testing ActiveJob `limits_concurrency` with Solid Queue
The :test adapter doesn't respect limits_concurrency configuration. Switch to :solid_queue adapter in your test to verify blocking behavior.
Job Configuration
class MembershipJob < ApplicationJob
limits_concurrency(key: ->(membership) { membership })
end
The problem
When using the default test mode for enqueuing jobs, both will be enqueued immediately. However, we actually we want to test that only one of both will be enqueued as the other should have been blocked.
# This doesn't actually test concurren...
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...
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...
Fuzzy scoping in Rails with PostgreSQL
When you want to filter records in a model where a string column roughly matches a given term, you can use PostgreSQL’s trigram similarity search.
Writing a fuzzy query in Rails
User.where("similarity(name, ?) > 0.3", "John")
This finds all users where the name is similar to "John" with a similarity score above 0.3.
You can tune the threshold:
- Closer to 1.0 = stricter match
- Closer to 0.0 = looser match
Ordering by best match
User
.where("similarity(name, ?) > 0.3", "John")
.order(Arel.sql("similarity(n...
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:
- Put everything (gems, application config, database migrations, models, controllers) into a single
.rufile, likeapp.ru. - Run it via
rackup app.ru. (Hint: if your file is calledconfig.ru, you can just run `rac...
How to inspect Rails view cache keys (when using Redis)
When your Rails application is using Redis as its cache store, this is how you can list existing keys:
- Check:
Rails.cacheshould return anActiveSupport::Cache::RedisCacheStore. -
Rails.cache.redis.with(&:keys)lists existing keys. Cached views start with "views/".
Caution! The list of keys may be huge in production.
tel_to Rails helper for linking phone numbers
When putting phone numbers into web pages, you should use tel: links so smartphone users can click those numbers to call someone.
Here is a small helper method that you can use to simplify this:
def tel_to(text)
groups = text.to_s.scan(/(?:^\+)?\d+/)
link_to text, "tel:#{groups.join '-'}"
end
This will allow you to use human-readable numbers and get clean links:
>> tel_to '(01234) 555 6789'
=> <a href="tel:01234-555-6789">(01234) 555 6789</a>
>> tel_to '+1 555 123-456'
=> <a href="tel:+1-555-123-456...
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 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 ...
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...
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>...
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: 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:
...
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 ...
Managing Rails locale files with i18n-tasks
When internationalizing your Rails app, you'll be replacing strings like 'Please enter your name' with t('.name_prompt'). You will be adding keys to your config/locales/*.yml files over and over again. Not to miss any key and place each at the right place is a challenging task.
The gem i18n-tasks has you covered. See its README for a list of things it will do for you.
Note
The
i18n-tasksgem does not understand aliases and will duplicate all referenced data when it writes locales. If yo...
How to query PostgreSQL's json fields from Rails
PostgreSQL offers a really handy field type: json. You can store any JSON there, in any structure.
While its flexibility is great, there is no syntactic sugar in Rails yet. Thus, you need to manually query the database.
Demo
# Given a Task model with 'details' json column
Task.where("details->>'key' = ?", "value") # matches where 'details' contains "key":"value"
Task.where("details->>'{a,b}' = ?", "value") # matches where 'details' contains "a":{"b":"value"}
Task.where("details->'a'->>'b' = ?", "value") # same as above, but vi...
Querying model errors in Rails 4
ActiveModel supplies an errors object that behaves similar to a Hash. It can be used to add errors to a record, as well as to query the record for registered errors. This object is returned when calling <object>.errors:
errors = @user.errors # => #<ActiveModel::Errors ...>
Here are some helpful messages of its API:
[<attribute name>]-
Returns an array of error messages on that attribute. Example:
errors[:name] => ['is missing']
-
add_on_blank(<attribute list>)(similarlyadd_on_empty) -
Registers an error ...
How to accept more than 4k query parameters in Rails
I have a form with a dynamic number of fields. Submitting it worked fine until I tried out a very large version of it. The development log was not very helpful:
Invalid or incomplete POST params
As it turned out, the following exception did not reach the log output
Rack::QueryParser::QueryLimitError
Error Message: total number of query parameters (6313) exceeds limit (4096)
If you ever happen to be in the same position, this is how to increase the limit of allowed query parameters:
# config/initializers/rack_query_parser.rb
...
Rails 4.1+: New locale files require a Spring restart
So you want to organize your I18n using multiple .yml files but your Rails 4.1 application simply won't use any extra files in development? Spring is to blame.
Halt spring by running:
spring stop
The next time you spawn a bin/rails console or similar, your new translations will work.
You will need to repeat the above every time you create a new .yml file or rename existing ones.
:(
Your Rails sandbox console
Just found out about a great feature in Rails that seems to be around since Rails 2. Start a console with the --sandbox (or -s) parameter:
rails console --sandbox
All changes you make to the database will be rolled back on exit.
Warning
Changes beyond the database (deleting files, sending emails, etc) cannot be rolled back!