How to enable pretty IRB inspection for your Ruby class
When Ruby objects are inspected in any modern IRB, some objects (like ActiveRecord instances) are rendered with neat colors and line breaks.
You will not get that for custom classes by default -- which can be annoying if your inspection contains lots of meaningful information.
Here is what you need to do if you want your objects to be inspected nicely.
Implement a pretty_print
method
As an example, consider the following class.
class MyClass
# ...
def inspect
"#<#{self.class} attr1: #{attr1.inspect}, attr2: #{attr2...
Generating an Entity Relationship Diagram for your Rails application
This card explains how to generate an entity relationship diagram for your Rails application.
We also show how to limit your ERD to a subset of models, e.g. models inside a namespace.
Generating a full ERD
Option A: RubyMine
- Right-click anywhere in your project tree
- In the context menu, find the "Diagrams" menu item at/near the bottom
- Inside, choose "Show diagram" → "Rails Model Dependency Diagram"
- A new tab will open with the diagram inside. You can modify it there, and export it as an image.
Option B: Use rails-e...
Custom error messages in RSpec or Cucumber steps
Sometimes you have a test expectation but actually want a better error message in case of a failure. Here is how to do that.
Background
Consider this test:
expect(User.last).to be_present
In case of an error, it will fail with a not-so-helpful error message:
expected present? to return true, got false (Spec::Expectations::ExpectationNotMetError)
Solution
That can be fixed easily. RSpec expectations allow you to pass an error message like this:
expect(User.last).to be_present, 'Could not find a user!'
...
Heads up: counting may be slow in PostgreSQL
The linked article points out that COUNT
queries might be unexpectedly slow in PostgreSQL.
If you just need to know "are there any records" use any?
. This uses SELECT 1 AS one FROM ... LIMIT 1
under the hood.
If you just need to know "are there no records" use empty?
or none?
. This uses SELECT 1 AS one FROM ... LIMIT 1
under the hood.
In short: Replace foo.count > 0
with foo.any?
or foo.count == 0
with foo.none?
Debugging Capistrano
Capistrano 3 has a doctor
task that will print information about
- Environment: Ruby, Rubygems and Bundler versions
- List of Capistrano gems and whether an update is available
- All config variables and their values
- Capistrano server config
$ bundle exec cap staging doctor
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 ...
Enabling YJIT
YJIT is Ruby's default just-in-time compiler. It is considered production-ready since Ruby 3.2 (source).
To activate YJIT you need two steps:
- Your
ruby
binary needs to be compiled with YJIT support. - You need to enable YJIT.
Getting a Ruby with YJIT support
We usually install Ruby with tools like rbenv
or asdf
. This compiles the ruby
binary from the source code. Support for YJIT
will be automatically added during this compilation...
Running Rubocop as a pre-push hook
Git has the concept of hooks: bash scripts that are invoked at certain points in the Git lifecycle. One handy use is a pre-push hook that runs Rubocop. It will prevent pushing code that Rubocop finds fault with.
Configuring the hook
Git hooks are normally stored locally with a repository. They are not committed.
- Store this snippet in .git/hooks/pre-push:
if [ -f ./.rubocop.yml ]; then
echo 'Running Rubocop ...'
bundle exe...
Updated: Rails: Manually decrypting a session cookie
- Added section for Rails 7
DB enums are ordered
A lesser known fact about PG enums is that they are ordered. This can be really handy when values have an implicit ordering.
Let's imagine a record Issue(criticality: string)
. criticality
should have the following three possible values: critical
, medium
, low
.
Sorting with Issue.all.order(criticality: :desc)
will return the following order:
- The
'medium'
issue - The
'low'
issue - The
'cricital'
issue
This happens, because as the database column backing the the criticality attribute is a string and PG will use a [col...
Geordi 11.2.0 released
- Add support for default branches other than "master" (e.g. "main"). Will read this information from origin.
-
geordi branch
will not fail if it can't determine local branches. - Improved Linear issue menu: now includes the issue id, truncates long issue titles and puts metadata last.
Switching the package manager from yarn to npm
We recently migrated a Rails application from yarn
to npm
. We decided to go this step instead of upgrading to > Yarn 2.0 to reduce the number of dependencies in our project.
Migration
- Remove the
yarn.lock
file - Remove the
node_modules
folder - Run
npm install
- Replace all occurrences of
yarn
withnpm
in your project
Notes
- With
npm
vendored packages with dependencies create their ownnode_modules
folder within the vendor path. We...
How to eager load a single directory with Zeitwerk
Zeitwerk is the new autoloader of Rails. It is mandatory starting with Rails 7.0.
Sometimes, a model needs to know all its descendants. They might be organized in a subdirectory:
# Example
app/models/design.rb
app/models/design/light.rb
app/models/design/dark.rb
...
Now imagine that some external code needs to iterate all design subclasses.
To eager load all designs, use this line:
Rails.autoloaders.main.eager_load_dir("#{Rails.root}/app/models/design")
Make sure that app/models/design.rb
is not required manually be...
jeremyevans/ruby-warning: Add custom processing for warnings
ruby-warning adds custom processing for warnings, including the ability to ignore specific warning messages, ignore warnings in specific files/directories, include backtraces with warnings, treat warnings as errors, deduplicate warnings, and add custom handling for all warnings in specific files/directories.
This tool can precisely silence deprecation warnings.
While you should fix deprecations in your application, ruby-warning is handy to silence those warnings that you cannot fix. E.g. when they originate from libraries that you ca...
CSS: Matching against attributes and their values (or parts of them)
You probably know that you can use CSS selectors to match against elements and their attributes, such as:
a[title] { /* any <a> that has a "title" */ }
a[data-fancy="true"] { /* any <a> that has their "data-fancy" attribute set to "true" */ }
But there is more: You do not need to match against "full" attribute values but can match against parts of them.
They work in all somewhat modern browsers, and IE9 or later.
Match variants
Exact match (CSS2)
[foo="bar"]
(matches `<div foo="b...
JavaScript: Calling a function with a variable number of arguments
This card describes how to pass an array with multiple element to a JavaScript function, so that the first array element becomes the first function argument, the second element becomes the second argument, etc.
Note how this is different from passing the entire array as the first argument. Compare these two different ways of calling fun()
in Ruby:
# Ruby
array = [1, 2, 3]
fun(array) # same as fun([1, 2, 3]) (1 argument)
fun(*array) # same as fun(1, 2, 3) (3 arguments)
Depending on your culture the spreading of array eleme...
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...
Carrierwave: How to remove container directories when deleting a record
When deleting a record in your Rails app, Carrierwave automatically takes care of removing all associated files.
However, the file's container directory will not be removed automatically. If you delete records regularly, this may be an annoyance.
Here is a solution which was adapted from the Carrierwave GitHub wiki and cleans up any empty parent directories it can find.
class ExampleUploader < CarrierWave...
SameSite cookies
TL;DR Most web applications do not require action on this. SameSite=None
(old browser default) will continue to work, and SameSite=Lax
(new Chrome default, gradually rolled out) is an even better default for cookies. Set SameSite=Strict
only for extra security in special cases (see below). If your application is rendered in an iframe (e.g. a video player or some news stream), you need to configure its relevant cookies as SameSite=None
.
The SameSite
cookie attribute targets **c...
How to update the bundler version in a Gemfile.lock
-
Install the latest
bundler
version:gem install bundler Fetching bundler-2.3.5.gem Successfully installed bundler-2.3.5 1 gem installed
-
Update the bundler version in
Gemfile.lock
:bundle update --bundler
-
Confirm it worked:
$ tail -n2 Gemfile.lock BUNDLED WITH 2.3.5
Notes:
-
Bundler should automatically detect the latest installed version. If it does not, you can specify your preferred version like so:
b...
Designing HTML emails
The 90s are calling: they want their tables back. Unfortunately, you need them all for laying out your HTML emails.
Email client HTML rendering is way more scattered than browser HTML. While you might have a pretty good understanding of what features and patterns you can use to support all major browsers, I doubt anyone masters this craft for HTML email clients.
The only way to ensure your email looks good (acceptable, at least) in all mail clients, is to check it. Litmus is your go-to solution for this (see below). W...
Project management best practices: Working with clients in person
When working on a bigger project, the easiest way to improve your work relation with a client or an external product manager, is to make sure you see them in person once in a while.
It makes sense to meet each other when you start working together to establish a relationship and find out what makes them tick.
If you need to discuss a larger package of work, use the opportunity and meet up and discuss it in person.
When you have to discuss something in your daily work, prefer talking to writing, and consider using a webcam.
It's OK to put block elements inside an <a> tag
In general, you should not put a block element inside an inline element. So don't do this:
<span>
<div>text</div>
</span>
The browser will think you wrote invalid HTML by accident, and will sometimes reorder elements silently.
There is one notable exception: It's OK to wrap block elements in a <a>
tag in HTML5 (not 4). The spec says:
The a element may be wrapped around entire paragraphs, lists, tables, and so forth, even entire sections, so long ...
tig: install a more recent version
I noticed that tig 2.5.1
that is provided by Ubuntu 22.04 repositories has inferior bash completion than older versions after a complete rewrite. Newer versions, however, received some fixes. This inspired me to upgrade tig
.
The official debian repositories have more recent versions of tig
than Ubuntu does.
- remove your current version of tig:
sudo apt purge tig
- go to http://deb.debian.org/debian/pool/main/t/tig/ and download the desired version for your machine (e.g. `tig_2.5.5-1_amd6...