Careful when using Time objects for generating ETags
You can use ETags to allow clients to use cached responses, if your application would send the same contents as before.
Besides what "actually" defines your response's contents, your application probably also considers "global" conditions, like which user is signed in:
class ApplicationController < ActionController::Base
etag { current_user&.id }
etag { current_user&.updated_at }
end
Under the hood, Rails generates an ETag header value like W/"f14ce3710a2a3187802cadc7e0c8ea99"
. In doing so, all objects from that etagge...
How to query GraphQL APIs with Ruby
While most Rails Apps are tied to at least one external REST API, machine-to-machine communication via GraphQL is less commonly seen. In this card, I'd like to give a quick intro on how to query a given GraphQL API - without adding any additional library to your existing app.
Core aspects of GraphQL
Interacting with GraphQL feels a bit like querying a local database. You are submitting queries to fetch data in a given structure (like SELECT in SQL) or mutations to alter the database (similar to POST/PUT/DELETE in REST). You can ...
Ruby: How to collect a Hash from an Array
There are many different methods that allow mapping an Array to a Hash in Ruby.
Array#to_h
with a block (Ruby 2.6+)
You can call an array with a block that is called with each element. The block must return a [key, value]
tuple.
This is useful if both the hash key and value can be derived from each array element:
users = User.all
user_names_by_id = users.to_h { |user| [user.id, user.name] }
{
1 => "Alice",
2 => "Bob"
}
Array#to_h
on an array of key/value tuples (Ruby 2.1+)
Converts an Array ...
Git: Restore
tl;dr
git checkout
is the swiss army of git commands. If you prefer a semantically more meaningful command for restoring tasks, usegit restore
instead.With this command you can ...
- ... do unstaging -
git restore --staged
- ... discard staged changes -
git restore --staged --worktree
- ... discard unstaged changes -
git restore
- ... restore deleted files -
git restore
- ... restore historic versions -
git restore --source
- ... recreate merge conflicts -
git restore --merge
- ... specifiy...
Building web applications: Beyond the happy path
When building a web application, one is tempted to claim it "done" too early. Make sure you check this list.
Different screen sizes and browsers
Desktops, tablets and mobile devices have all different screen resolutions. Does your design work on each of them?
- Choose which browsers to support. Make sure the page looks OK, is usable and working in these browsers.
- Use @media queries to build a responsive design
- If you do not suppo...
Advantages of using appname.daho.im:3000 over localhost:3000
Running rails server
will start a local server that you can access via http://localhost:3000
.
When you are working on multiple web apps, they will likely set cookies with generic names on localhost
. This is annoying, since you will sign out your current user whenever you switch to another app.
A better way is to use our own daho.im service. All daho.im subdomains resolve to your local IP (127.0.0.1). That means you can use a different hostname for different apps, and you will stay logged in in each app:
http://foo-ap...
Fixing authentication in legacy applications
Authentication is hard: there are many edge cases, and most users (including yourself) usually only go the "happy path" once and never see the edge cases. If you have rolled your own authentication, or been using older authentication solutions, or resorted to HTTP Basic Authentication, this card will tell you what to do to make your application safe.
Any application that stores sensitive data in the browser
That is: cookies, e.g. by offering a login.
- Ask the admins to [turn on SSL](https://makandracards.com/makandra/1416-integrate-s...
Make Nokogiri use system libxml2
The nokogiri gem provides different packages for several platforms. Each platform-specific variant ships pre-built binaries of libxml2
, e.g. x86_64-linux
includes binaries for 64bit Linux on Intel/AMD. This significantly speeds up installation of the gem, as Nokogiri no longer needs to compile libxml2
.
However, this also means that for each security issue with libxml2
, Nokogiri maintainers have to update their pre-built binaries and release a new version of the gem. Then, you need to update and ...
How to: Benchmark an Active Record query with a Ruby script
Recently I needed to benchmark an Active Record query for performance measurements. I wrote a small script that runs each query to benchmark 100 times and calculates the 95th percentile.
Note: The script requires sudo permissions to drop RAM cache of PostgreSQL. Due to the number of iterations it was impractical to enter my user password that often. And I temporary edited my /etc/sudoers
to not ask for the sudo password with johndoe ALL=(ALL) NOPASSWD: ALL
.
# Run this script with e.g. `rails ru...
WYSIWYG with Action Text
Rails 6 includes a WYSIWYG editor, Action Text. It works out of the box quite well, but chances are that you want to add some custom functionality. This card contains some tips how to achieve this.
Setup
Basically, follow the guide in the Rails documentation. The automated script may not work with the way webpacker is configured in your project, but it should be easy to fix.
If you don't want the default c...
Minifying object properties in JavaScript files
An introduction to mangling
When you minify ("compress", "optimize") your JavaScript for production, the names of your functions and variables will be renamed for brevity. This process is often called mangling.
E.g. if this is your source code:
function function1() {
function2()
}
After mangling it would look like this:
function a() {
b()
}
Object properties are not mangled by default
Minfiers never mangle properties by default, as this can be an unsafe transformation. This leads to larger file sizes if...
Custom Ruby method Enumerable#count_by (use for quick statistics)
I frequently find myself needing a combination of group_by
, count
and sort
for quick statistics. Here's a method on Enumerable
that combines the three:
module Enumerable
def count_by(&block)
group_by(&block)
.transform_values(&:count)
.sort_by(&:last)
.to_h
end
end
Just paste that snippet into a Rails console and use #count_by
now!
Usage examples
- Number of email addresses by domain:
> User.all.count_by { |user| user.email.sub /^.*@/, '' }
=> { "sina.cn"=>2, ..., "hotmail.co...
Take care of indentation and blank lines when using .erb for plain text emails
Building plain text emails with an .erb template doesn't allow you to indent code like you normally do in HTML mails.
❌ DON'T
<%= 'foo' if bar %>
"\n"
if bar is false
"foo\n"
if bar is true
<%= nil %>
"\n"
<% if true %>
<%= 'foo' %>
<% end %>
" foo"
<%= 'foo' %>
<%= 'bar' %>
"foo\n\nbar\n"
✔️ DO
Write unindented code to get the expected result.
<% if bar %>
<%= 'bar' %>
<% end %>
<%= 'foo' %>
<%= 'bar' %>
- Use [Form Models](https://github.com/makandr...
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...
Center a float horizontally
This card shows you how to center a float horizontally in CSS. Also: find out what techniques are available for your use case and browser requirements in the card linked below.
Note: We have card with all CSS centering options. You probably want to head over there and get an overview over what techniques are available for your use case and browser requirements.
If you cannot use display: inline-block
, centering a float ...
has_one association may silently drop associated record when it is invalid
This is quite an edge case, and appears like a bug in Rails (4.2.6) to me.
Update: This is now documented on Edgeguides Ruby on Rails:
If you set the :validate option to true, then associated objects will be validated whenever you save this object. By default, this is false: associated objects will not be validated when this object is saved.
Setup
# post.rb
class Post < ActiveRecord::Base
has_one :attachment
end
# attachm...
How to use pessimistic row locks with ActiveRecord
When requests arrive at the application servers simultaneously, weird things can happen. Sometimes, this can also happen if a user double-clicks on a button, for example.
This often leads to problems, as two object instances are modified in parallel maybe by different code and one of the requests writes the results to the database.
In case you want to make sure that only one of the requests "wins", i.e. one of the requests is fully executed and completed while the other one at least has to wait for the first request to be completed, you ha...
How the Date Header Affects Cookie Expiration and Caching
tl;dr
When a cookie includes an
Expires
attribute or an HTTP response includes caching headers likeExpires
orCache-Control
, their validity depends on the server'sDate
header if present. Otherwise, the browser uses its local time. This can lead to issues in tests with mocked time or inconsistent cache behavior.
Cookie Expires
depends on the Date
header or browser time
When a cookie includes an Expires
attribute, the browser evaluates the expiration date relative to a reference time:
- If the HTTP response ...
How to turn images into inline attachments in emails
Not all email clients support external images in all situations, e.g. an image within a link. In some cases, a viable workaround is to turn your images into inline attachments.
Note
Rails provides a simple mechanism to achieve this:
This documentation makes it look like you have to care about these attachments in two places. You have to create the attachment in t...
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...
Disable PostgreSQL's Write-Ahead Log to speed up tests
The linked article suggests an interesting way to speed up tests of Rails + Postgres apps:
PostgreSQL allows the creation of “unlogged” tables, which do not record data in the PostgreSQL Write-Ahead Log. This can make the tables faster, but significantly increases the risk of data loss if the database crashes. As a result, this should not be used in production environments. If you would like all created tables to be unlogged in the test environment you can add the following to your...
PSA: Be super careful with complex `eager_load` or `includes` queries
TLDR
Using
.includes
or.eager_load
with 1-n associations is dangerous. Always use.preload
instead.
Consider the following ActiveRecord query:
BlogPost.eager_load(
:comments
:attachments,
).to_a
(Let's assume we only have a couple of blog posts; if you use pagination the queries will be more complicated, but the point still stands.
Looks harmless enough? It is not.
The problem
ActiveRecord will rewrite this into a query using LEFT JOIN
s which looks something like this:
SELECT "blog_posts...
Regular tasks for long-running projects
When projects run for many years, they require special regular maintenance to stay fresh. This kind of maintenance is usually not necessary for small or quick projects, but required to keep long-running projects from getting stale.
You should be able to fit this into a regular development block.
Quarterly
Check which libraries need updating
As time goes by, libraries outdate. Check your software components and decide if any of it needs an update. Your main components (e.g. Ruby, Rails, Unpoly) should always be reasonably up to da...
How to tell ActiveRecord how to preload associations (either JOINs or separate queries)
Remember why preloading associations "randomly" uses joined tables or multiple queries?
If you don't like the cleverness of this behavior, you can explicitely tell ActiveRecord how to preload associations with either JOINs
or separate queries.
This card gives an overview of the different options to preload associations, but
__Whic...