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...
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...
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...
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...
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...
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...
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...
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...
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...
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...
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, ...
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...
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 }
...
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...
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...
RSpec: Inferring spec type from file location
RSpec Rails can automatically mix in different behaviors to your tests based on their type
tag, for example enabling you to call get
and
post
in specs with the tag type: :request
.
Alternatively you can skip these tags by setting the config config.infer_spec_type_from_file_location!
in the spec_helper.rb
. This will automatically choose the right type context based on the file location of the test.
For instan...
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...
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 `...
Ruby: Referencing global variables with the built-in English library
tl;dr
Don't forget
require 'English'
if you use a named global such as$LAST_MATCH_INFO
. Otherwise this could result in an annoying bug.
With Ruby's build-in library English you can reference global variables with an english name. This makes you code easier to read and is also suggested by Rubocop's Style/GlobalVars cop.
Example before:
if 'foo' =~ /foo/
puts $~[1] # => foo
end
Example af...
E-mail deliverability
When your application is open for public sign up and sends out transactional e-mails to a large number of users, e-mail deliverability becomes an issue.
E-mail providers work hard to eliminate spam and have put in place relatively tight checks what kinds of emails they will accept, and from whom. To that end we use tools like mail-tester.com to make our mails as acceptable as possible. Unfortunately, there will always be providers that reject our e-mails for some reason or other, sometimes outside of our control.
For exa...
Limiting GitLab CI runner to specific branches or events
Use rules to include or exclude jobs in pipelines.
Rules are evaluated in order until the first match. When a match is found, the job is either included or excluded from the pipeline, depending on the configuration. The job can also have certain attributes added to it.
rules replaces only/except and they can’t be used together in the same job. If you configure one job to use both keywords, the linter returns a key may not be used with rules error.
GitLab 12.3 introduced rules. You can use them in your .gitlab-ci.yml
in your proj...
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...
How to: Specify size of Selenium browser window
Applications often show or hide elements based on viewport dimensions, or may have components that behave differently (like mobile vs desktop navigation menus).
Since you want your integration tests to behave consistently, you want to set a specific size for your tests' browser windows.
Using WebDriver options / Chrome device metrics
For Google Chrome, the preferred way is setting "device metrics". This allows you to configure dimensions larger than your display and enable/disable touch behavior.
Simply use register_driver
to set up...