Checklist for Implementing Design
We have a long-standing checklist for merge requests. However, it hardly matches the intricate requirements for design. This checklist fills the gap.
Before starting implementing, look at all designs: are there components similar to yours? Have they already been implemented? Can you build on this prior art when implementing yours?
Checklist: I confirm my design implementation
- has been tested manually by me
- adheres to the code style of the project (e.g. BEM)
- avoids "magic numbers" (don't say e.g. ...
How to fix parallel_tests with Redis on powerful machines
When you have a powerful machine with many CPU cores, you might run into an error like
ERR DB index is out of range (Redis::CommandError)
This is because Redis defaults to at most 16 databases (0 to 15) and running tests in parallel might exceed that (your tests might run on databases 1..n
or 2..(n+1)
).
You can increase that limit:
-
Get number of CPUs of your machine.
nproc --all
-
Open up Redis configuration file.
sudo vim /etc/redis/redis.conf
-
Find
databases
row and increase it, e.g. set to 32:
...
How to evaluate CSS media queries in JavaScript
To make CSS rules dependent on the screen size, we use media queries:
@media (max-width: 500px) {
// rules for screen widths of 500px or smaller
}
Browsers will automatically enable and disable the conditional rules as the screen width changes.
To detect responsive breakpoints from JavaScript, you may use the global matchMedia()
function. It is supported in all brow...
Jasmine: Testing complex types for equality
Jasmine comes with two matchers that test for equality. The first is toBe
:
expect(first).toBe(second)
toBe
passes when first === second
. Unfortunately this is useless for non-primitive values because JavaScript is a horrible language.
However, Jasmine comes with another matcher toEqual
:
expect(first).toEqual(second)
This matcher behaves as a human would expect for types like the following:
- Arrays
- Objects
- Nested array/object constructs
- Regular expressions...
RSpec: Run a single spec (Example or ExampleGroup)
RSpec allows you to mark a single Example/ExampleGroup so that only this will be run. This is very useful when using a test runner like guard.
Add the following config to spec/spec_helper.rb
:
RSpec.configure do |config|
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run_including :focus => true
config.run_all_when_everything_filtere...
Heads up: Quering array columns only matches equally sorted arrays
Given you have an array column like this:
create_table "users", force: :cascade do |t|
t.integer "movie_ids", default: [], array: true
end
You might think that the following queries yield the same result:
User.where(movie_ids: [16, 17])
User.where(movie_ids: [17, 16])
Turn's out - they are not! They do care about array ordering more than I do.
To query for identical arrays independent of their order you have to either:
- Sort both the query and database content. If you're on Rails 7.1 you can use the new [`normal...
Do not forget mailer previews
When changing code in mailers, updating the corresponding mailer preview can be forgotten very easily.
Mailer previews can be tested like other code as well and I sometimes add the following tests to test suites:
# Make sure to require the previews
Dir[Rails.root.join('spec/mailers/previews/*.rb')].each { |file| require(file) }
ActionMailer::Preview.all.index_with(&:emails).each do |preview, mails|
mails.each do |mail|
describe preview do
specify "##{mail} works" do
expect { preview.call(mail...
Heads Up: Selenium 4 uses a binary to determine the chromedriver
I recently stumbled over a problem that my feature tests broke in CI because of a mismatching chromedriver version.
In this specific project we have a fixed Chromium version in a Debian 12 environment instead of Chrome. The tests however used a recent chrome version instead.
$ chromedriver --version
ChromeDriver 117.0.5938.149 (e3344ddefa12e60436fa28c81cf207c1afb4d0a9-refs/branch-heads/5938@{#1539})
$ chromium --version
Chromium 117.0.5938.149 built on Debian 12.1, running on Debian 12.1
> WARN Selenium [:selenium_manager] The chromed...
RSpec: Scoping custom matchers to example groups
When you find yourself in the situation that you would like to define a custom matcher in your specs, but you do not want to define a global matcher since you need it only for your specific test, there are two ways to do it:
Custom matcher for a single group
If you're only going to include
a matcher once, you can also use the matcher
macro within an example group:
describe "group" do
matcher :be_just_like do |expected|
match {|ac...
How to write modular code
Or: How to avoid and refactor spaghetti code
Please note that I tried to keep the examples small. The effects of the methods in this card are of course much more significant with real / more complex code.
What are the benefits of more modular code?
Code is written once but read often (by your future self and other developers who have to understand it in order to make changes for example). With more modular code you reduce the scope of what has to be understood in order to change something. Also, naming things gives you the opportunity t...
Use <input type="number"> for numeric form fields
Any form fields where users enter numbers should be an <input type="number">
.
Numeric inputs have several benefits over <input type="text">
:
- On mobile or tablet devices, number fields show a special virtual keyboard that shows mostly digit buttons.
- Decimal values will be formatted using the user's language settings.
For example, German users will see1,23
for<input type="number" value="1.23">
. - Values in the JavaScript API or when submitting forms to the server will always use a point as decimal separator (i.e.
"1.23"
eve...
How to push to Git without running CI on GitLab CI, GitHub Actions, or Travis CI
If a project ist configured to spawn CI runners for tests or deployment when pushing to the Repo, a habit of pushing WIP commits regularly may conflict with that.
Here are two solutions that allow you to keep pushing whenever you feel like it.
Special commit message
To skip a CI run, simply add [ci skip]
or [skip ci]
to your commit message. Example:
git commit -m "wip authentication [ci skip]"
Git push options (GitLab)
In addition to that, GitLab CI supports Git push options. Instead of changing your commit message, ...
Bash: How to count and sort requests by IP from the access logs
Example
87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.41 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
87.140.79.42 - - [23/Jan/2024:09:00:46 +0100] "GET /monitoring/pings/ HTTP/1.1" 200 814 "-" "Ruby"
Goal
Count and sort the number of requests for a single IP address.
Bash Command
awk '{ print $1}' test.log | sort...
Ruby: How to use global variables for a conditional debugger
You can share a state in Ruby with global variables. Even if you should avoid them whenever possible, for debugging an application this could be temporary quite handy.
Example:
class User
after_save { byebug if $debug; nil }
def lock
self.locked = true
save
end
end
Rspec.describe User do
let(:user) { create(:user) }
before do
# Many users are created and saved in this hook, but we don't want the debugger to stop for them...
ActiveRecord: When aggregating nested children, always exclude children marked for destruction
When your model is using a callback like before_save
or before_validation
to calculate an aggregated value from its children, it needs to skip those children that are #marked_for_destruction?
. Otherwise you will include children that have been ticked for deletion in a nested form.
Wrong way
class Invoice < ApplicationRecord
has_many :invoice_items
accepts_nested_attributes_for :invoice_items, :allow_destroy => true # the critical code 1/2
before_save :calculate_and_store_amount # the crit...
Heads up: Byebug has problems with zeitwerk
I encountered a unlucky behavior of byebug 11.1.3 (the most recent version at time of writing) when using it with Rails 6 and it's new autoloading component, zeitwerk. There already is a issue for that, so I hope it will be fixed with a future release.
The following test succeeds:
context 'factories' do
let(:test_case) { FactoryBot.create(:test_case) }
it 'are valid' do
expect(test_case).to be_valid
end
end
But when I did the same in byebug the foll...
RSpec 3 allows chaining multiple expectations
When you are using lambdas in RSpec to assert certain changes of a call, you know this syntax:
expect { playlist.destroy }.to change { Playlist.count }.by(-1)
While you can define multiple assertions through multiple specs, you may not want to do so, e.g. for performance or for the sake of mental overhead.
Multiple expectations on the same subject
RSpec allows chaining expectations simply by using and
.
expect { playlist.destroy }
.to change { Playlist.count }.by(-1)
.and not_change { Video.count }
...
Why two Ruby Time objects are not equal, although they appear to be
So you are comparing two Time
objects in an RSpec example, and they are not equal, although they look equal:
expected: Tue May 01 21:59:59 UTC 2007,
got: Tue May 01 21:59:59 UTC 2007 (using ==)
The reason for this is that Time
actually tracks fractions of a second, although #to_s
doesn't say so and even though you probably only care about seconds. This means that two consecutive calls of Time.now
probably return two inequal values.
Consider freezing time in your tests so it is not dependent on the speed of the executi...
JavaScript: Sharing content with the native share dialog
Mobile Chrome and Safari support the "web share API" which allow you to use the native share functionality of an Android or iOS phone. Some desktop OSs like Windows or MacOS also support native share dialogs. See Can I Use for a detailed support matrix.
When clicking a share button using this API, the browser will automatically show all installed applications that support content sharing, such as Whatsapp, Facebook, Twitter, e-mail etc.
The API is extremely simple to use:
if ...
Devise: Don't forget to lock users with soft delete
There are two ways to lock a user in devise.
- Using the lockable module
- Customizing the user account status validation when logging in.
It depends on your requirements which methods works best.
Locking a user on soft delete
We recommend to use option 2 when you want to couple the lock to the m...
Using Rationals to avoid rounding errors in calculations
Ruby has the class Rational which allows you to store exact fractions. Any calculation on these variables will now use fractional calculations internally, until you convert the result to another data type or do a calculation which requires an implicit conversion.
Example use case:
Lets say you want to store the conversion factor from MJ
to kWh
in a variable, which is 1/3.6
. Using BigDecimals for this seems like a good idea, it usually helps with rounding errors over a float, but the...
Rails: Preloading associations in loaded records
Sometimes you want to fetch associations for an ActiveRecord that you already loaded, e.g. when it has deeply nested associations.
Edge Rider gives your models a static method preload_associations
. The method can be used to preload associations for loaded objects like this:
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
@user.preload_associations(threads: { posts: :author }, messages: :sender)
end
end
The attached initializers re...
RSpec: Efficiently rerunning failed examples during development
Note
Don't use reruns as a mean to work around flaky tests. You should always try to fix those instead of rerunning them regularly.
Setup
Configure RSpec to persist the result of your test runs to a file. This is necessary to be able to rerun examples.
Add this to your spec/spec_helper.rb
:
config.example_status_persistence_file_path = 'spec/examples.txt'
Rerun all failed examples using --only-failures
bundle exec rspec --only-failures
(or `...
RSpec: Applying stubs only within a block
When you mocked method calls in RSpec, they are mocked until the end of a spec, or until you explicitly release them.
You can use RSpec::Mocks.with_temporary_scope
to have all mocks applied inside a block to be released when the block ends.
Example:
RSpec::Mocks.with_temporary_scope do
allow(Rails).to receive(:env).and_return('production'.inquiry)
puts Rails.env # prints "production"
end
puts Rails.env # prints "test"
Note that, when overriding pre-existing mocks inside the block, they are not reverted to the previously ...