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...
Simple Form: Rendering errors without an appropriate attribute
Usually you add errors to :base in ActiveRecord, in case no appropriate attribute could be used to add the error.
Simple Form doesn't render errors on :base
by default, but here a few options how you can render these on demand. For all the options below we use the following example with a Simple Form Bootstrap configuration:
- @user = Backend::User.new
- @user.errors.add(:base, 'First error')
- @user.errors.add...
Using Capybara finder methods with arbitrary matching conditions
Capybara has a variety of finder methods like find_button
to help you look up DOM elements. There are also matchers like have_field
to make expectations during tests.
These methods also have a number of options to influence the lookup. E.g. the :disabled
option lets you control whether Capybara will match disabled fields.
If you have a matching condition that cannot be expressed by the existing Capybara opt...
Project management best practices: Technical debt summary
Maintaining larger projects makes it more difficult to balance refactoring and upgrade tasks according to its actual value. Consider to create and periodically maintain a summary, which helps you and your team in the decision which refactoring task should be taken next.
Template
Here is an template on how you might categorize your tasks:
| Technical debt | Estimated Efforts | Visible customer value| Customer value explained| Developer value|Developer value explained|
|-----------------------------|----------------|----------...
PostgreSQL: WITH Queries (Common Table Expressions)
PostgreSQL's Common Table Expressions (CTEs) can be used to extract sub-queries from bulky SQL statements into a temporary table to be referenced instead.
This is most useful to avoid ugly joins or sub-selects. CTEs can be used for SELECT
, INSERT
, UPDATE
or DELETE
.
Example (from the PostgreSQL docs):
WITH regional_sales AS (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
), top_regions AS (
SELECT region
FROM regional_sales
WHERE total_sales > (SE...
Controlling issue grouping in Sentry
When you use Sentry to monitor exceptions, an important feature is Sentry's error grouping mechanism. It will aggregate similar error "events" into one issue, so you can track and monitor it more easily. Grouping is especially important when you try to silence certain errors.
It is worth understanding how Sentry's grouping mechanism works.
The default grouping mechanism
The exact algorithm has changed over time, and Sentry will keep using the algorithm t...
How not to turn your application into a spam relay
Spammers have started abusing other application to send their spam. This works like this:
- The application has some form that allows to send e-mails to arbitrary users. This can be something like a newsletter sign-up with a double-opt in, a registration confirmation e-mail (or even password reset e-mail), or something similar.
- The e-mail also includes some reflected text. For example, a user may be able to give their name, and the name is used within the e-mail. The spammer will then abuse that text to include his advertisment.
Potentia...
JavaScript: Detecting the end of native smooth scrolling
When you use native smooth scrolling there is no built-in method to detect the end of the scrolling animation. Methods like scrollTo()
don't return a promise. We may eventually get a scrollend
event, but that is still some time away.
Until then I'm using the ...
Rails: Rescuing exceptions for specific exception types
By default most exceptions in Rails will render a 500 error page and will create a new issue in your error monitoring. There are some built-in rules in Rails that
- render a different error than 500
- will rescue the exception and not create an issue in your error monitoring
A good example is ActiveRecord::NotFound
: You don't want an exception in your error monitoring when users navigate to e.g. a blog post t...
Using the Truemail gem to validate e-mail addresses
The Truemail gem (not to be confused with truemail.io) allows validating email addresses, e.g. when users enter them into a sign-up form. It runs inside your application and does not depend on an external SaaS service.
Truemail supports different validation "layers":
- Regex validation: if the given address is syntactically valid
- DNS validation (called MX validation): if the given domain exists and can receive email
- SMTP validation: connects to the host received from DNS and starts a test d...
Capybara: Running tests with headless Chrome
Headless Chrome is a way to run the Chrome browser without a visible window.
Configuring Capybara
Configure the Capybara driver like this:
Capybara.register_driver :selenium do |app|
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--disable-infobars')
options.add_emulation(device_metrics: { width: 1280, height: 960, touch: false })
unless ENV.key?('NO_HEADLESS')
options.add_argument('--headless')
o...
Printing background color of elements
Browsers' printing methods usually don't print background colors. In most cases this is the desired behavior, because you don't want to spend tons of ink printing the background of a web page. But in some cases you want to print the background color of elements, e.g. bars of a chart. For those elements you need to set the following CSS styles:
-webkit-print-color-adjust: exact; /* Chrome and Safari */
color-adjust: exact; /* Firefox */
Another case is printing of white text. When removing background colors, chances are white text n...
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...
Storing trees in databases
This card compares patterns to store trees in a relation database like MySQL or PostgreSQL. Implementation examples are for the ActiveRecord ORM used with Ruby on Rails, but the techniques can be implemented in any language or framework.
We will be using this example tree (from the acts_as_nested_set docs):
root
|
+-- Child 1
| |
| +-- Child 1.1
| |
| +-- Child 1.2
|
+-- ...
Custom RSpec matcher for allowed values (or assignable_values)
In contrast to RSpec's included allow_value
matcher, the attached matcher will also work on associations, which makes it ideal for testing assignable_values
.
Usage example
describe Unit do
describe '#building' do
it 'should only allow buildings that a user has access to' do
building = build(:building)
other_building = build(:building)
unauthorized_building = build(:building)
power = Power.new(build(:user))
Power.with_power(power) do
expect(power).to receive(:buildings).at_least...
Beware when using ActiveSupport time and date calculation methods
The pitfall
Rails Active Support provides some helpful methods for calculating times and dates, like Duration#ago
or Duration#from_now
. But beware when using those, because they wont give you Date
s or Time
s but ActiveSupport::TimeWithZone
instances. As the class name hints, you now have to be awa...
Rails: Overriding view templates under certain conditions only
Rails offers a way to prepend (or append) view paths for the current request. This way, you can make the application use different view templates for just that request.
Example
A use case of this is a different set of view templates that should be used under certain circumstances:
class UsersController < ApplicationController
before_action :prepare_views
def index
# ...
end
private
def prepare_views
if <condition>
prepend_view_path Rails.root.join('app', 'views', 'special')
end
end
...
Pretty commit messages via geordi
Geordi provides a pretty neat way to generate beautiful commit messages according to your stories in Linear:
geordi commit
Geordi reads from a .geordi.yml
file inside your repo and connects to Linear to list started and finished stories with their title. Choosing one of them generates a commit message including id and title from Linear app and a link to the original issue. For example:
[VW-1337] CRUD Users
Issue: https://linear.app/makandra/issue/VW-1337/crud-user...
A quick introduction to CORS
Background
Cross-Site Request Forgery (CSRF) is an attack pattern for websites. A CSRF attack is usually relevant in a browser context, where state is kept for multiple domains (as opposed to independent requests made e.g. with curl
). The most common example is authentication via cookies. If a script on https://example.com
made requests to https://docs.google.com
, the browser would send all cookies for docs.google.com along, effectively given the script access to anythin...
Rails: Composing an ETag from multiple records
Rails offers the fresh_when
method to automatically compute an ETag from the given record, array of records or scope of records:
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
fresh_when @user
end
def index
@users = User.all.to_a
fresh_when @users
end
end
When your view also displays other records (typically associations), those other records should be included in the ETag
. You can do so by passing an array of ETaggable objects to fresh_when
.
...
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: How to test the parsed response body
Testing your responses in Rails allows to parse the body depending on the response MIME type with parsed_body
.
get '/posts.json'
response.parsed_body # => [{'id' => 42, 'title' => 'Title'}, ...]
For JSON APIs we often parse the response as symbolized keys with JSON.parse(response.body, symbolize_names: true)
, which is not supported by parsed_body
. For all other cases you might want to drop JSON.parse(response.body)
and replace it w...
Best practices: Large data migrations from legacy systems
Migrating data from a legacy into a new system can be a surprisingly large undertaking. We have done this a few times. While there are significant differences from project to project, we do have a list of general suggestions.
Before you start, talk to someone who has done it before, and read the following hints:
Understand the old system
Before any technical considerations, you need to understand the old system as best as possible. If feasible, do not only look at its API, or database, or frontend, but let a user of the old system sho...
Keeping web applications fast
Our applications not only need to be functional, they need to be fast.
But, to quote Donald Knuth,
premature optimization is the root of all evil (or at least most of it) in programming
The reasoning is that you should not waste your time optimizing code where it does not even matter. However, I believe there are some kinds of optimizations you should do right away, because
- they are either obvious and easy
- or they are very hard to do optimize later
This is an attempt to list some of those things:
On the server
...