Updated: Breaking changes for boolean attributes in HAML 6
Starting with Unpoly 3.8, most Unpoly attributes can now be enabled with a value "true"
and be disabled with a value "false"
. This pairs well with the default (unpatched) Haml 6 behavior.
capybara-lockstep 2.2.2 released
The capybara-lockstep gem synchronizes Capybara commands with client-side JavaScript and AJAX requests. This greatly improves the stability of an end-to-end ("E2E") test suite, even if that suite has timing issues.
Changes in 2.2.2
- We now only wait for
<script>
elements with a JavaScript type - We only wait for
<iframe>
elements with a[src]
attribute - We no longer wait for
<video>
and<audio>
elements to load their metadata. This did not work consistently, and would sometime...
capybara-lockstep 2.2.1 fixes drivers with { browser: :remote }
Capybara-lockstep < 2.2.1 has a bug that makes it essentially not work when using browser: :remote
in your Capybara driver. Please update to 2.2.1 to get the expected functionality.
The affected setups look like this:
chromedriver_url = ENV['CHROMEDRIVER_URL']
Capybara::Selenium::Driver.new(app,
browser: chromedriver_url ? :remote : :chrome,
options:,
**(chromedriver_url ? { url: chromedriver_url } : {}),
)
Note that older Capybara versions used browser: :chrome, url: chromedriver_url
, will work fine.
Searchkick: async reindexing fails for rails 7 with redis 4
After an upgrade to rails 7 I noticed that async reindexing jobs of Searchkick were failing for Model.reindex(mode: :async, wait: true)
:
/home/a_user/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/searchkick-5.3.1/lib/searchkick/relation_indexer.rb:142:in `block in batch_job': undefined method `call' for nil (NoMethodError)
Searchkick.with_redis { |r| r.call("SADD", batches_key, [batch_id]) }
^^^^^
from /home/a_user/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/searchkick-5.3.1/lib/searchkick.r...
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...
Don't name columns like counter_cache columns in Rails pre v4.2.4
< Rails v4.2.4
ActiveRecord has a feature called counter caching where the containing record in a has_many
relationship caches the number of its children. E.g. when you have House has_many :rooms
, Rails can cache the number of rooms in House#rooms_count
.
Mind that when a model has a column that looks to Rails like a counter-cache column, Rails will apply counter-cache logic to your model, even if you're not using counter caches.
E.g. you have a house with 12...
Ruby: You can nest regular expressions
Ruby lets you re-use existing RegExp
objects by interpolating it into new patterns:
locales_pattern = /de|en|fr|es/i
html_tag_pattern = /<html lang="#{locales_pattern}">/
Any modifiers like /i
or /x
will be preserved within the interpolated region, which is pretty cool. So in the example above only the interpolated locales are case-insensitive, while the pattern around it (/<html .../
) remains case-sensitive.
routing-filter is broken with Rails 7.1
If you are using the routing-filter gem in your Rails 7.1 app for managing URL segments for locales or suffixes, you will notice that the filters do no longer apply, routes are broken and the necessary parameters are no longer extracted. That is because routing-filter patches Rails' find_routes
-method to get the current path and apply its defined filters on it. These filters then modify the params that are handed over to your controller action. This way you receive a locale
parameter from a ...
Active Record: Never use optional with a symbol, lambda or proc
tl;dr
Do not use the option
optional
on association declarations with a symbol, lambda or proc.
Explanation
Association declarations like belongs_to
support the option optional
. This option does not support symbols, lambdas or procs. If you do so, this will always result in optional: true
. So your records can miss a presence validation if optional
is used with a symbol, lambda or proc.
If you set t...
Bash script to list git commits by Linear ID
As we're switching from PT to Linear, I've updated the existing bash script to work for commits that are referencing Linear IDs.
A core benefit of our convention to prefix commits by their corresponding issue ID is that we can easily detect commits that belong to the same issue. You can either do that manually or use the bash script below. It can either be placed in your .bashrc
or a...
Updated: Gatekeeping: Guide for developer
Update our Gatekeeping workflow to refer to our new default workflow states in Linear
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...
Heads up: Sidekiq per default silently fails when retries are exhausted!
For Sidekiq to be able to retry your jobs it has to be able to catch errors that occur while a job is executed.
Per default, Sidekiq will not raise / notify you if the retry count is exhausted. It will only copy the job to the dead queue (see wiki).
If you want to get notified, you have to implement it in your worker explicitly with a sidekiq_retries_exhausted
-block, e.g. like this:
class DownloadWorker
include Sidekiq::Worker
# Import jobs are retried a few time...
RSpec: Using helpers in view specs
If an view spec crashes due to undefined helper methods, you can enable this option:
# config/application.rb
config.action_controller.include_all_helpers = true
If you cannot use this setting, your spec can include individual helper modules like this:
describe 'some view', type: :view do
helper SomeHelper
helper OtherHelper
it 'renders' do
render 'view_that_uses_helpers'
end
end
Alternatively you can also explicitly include *all help...
Rails: Integrating shoelace components
shoelace is a library of web components. Here is a proof of concept how a integration (slightly different as the official docs for Rails) might look like in Rails + webpack + Unpoly. Also see the HN discussion for pro and cons.
Capybara: Quick checking for element presence (without retries or timeout)
Element finding is a central feature of Capybara. Since #find
is normally used to get elements from the current page and interact with them, it's a good thing that some Capybara drivers (e.g. Selenium) will wait an amount of time until the expected element shows up. But if Capybara cannot #find
it at all, you'll get an error.
if page.find('.that-element')
# Do something
else
# Never happens because #find raises
end
In order to simply check whether an element is present, without errors raised, you can use #has_css?
. It...
Nested ActiveRecord transaction pitfalls
When working with custom transactions and use ActiveRecord::Rollback
you may encounter unexpected behaviour if you try to roll back your changes.
tl;dr
When using nested transactions, ActiveRecord::Rollback
might not do what you expect, since it will only roll back the inner, but not the outer transaction.
You can fix this behavior by using transaction(joinable: false)
but this leads to a bunch of different problems.
When you don't need an explicit ActiveRecord::Rollback
, don't worry about any of this and just use a plan `tran...
Capybara: Find an element that contains a string
There is no CSS selector for matching elements that contains a given string ¹. Luckily, Capybara offers the :text
option to go along with your selector:
page.find('div', text: 'Expected content')
You can also pass a regular expression!
page.find('div', text: /Expected contents?/i)
Note that if your CSS selector is as generic as div
, you might get a lot more results than you expect. E.g. a <div class="container">
that surrounds your entire layout will probably also contain that text (in a descendant) and ...
Always convert and strip user-provided images to sRGB
Debugging image color profiles is hard. You can't trust your eyes in this matter, as the image rendering depends on multiple factors. At least the operation system, browser or image viewer software and monitor influence the resulting image colors on your screen.
When we offer our users the possibility to upload images, they will most likely contain tons of EXIF metadata and sometimes exotic color profiles like eciRGB. We want to get rid of the metadata, as it might contain sensitiv...
Ruby: How to determine the absolute path relative to a file
If you want to get the path of a file relative to another, you can use the expand_path
method with either the constant __FILE__
or the method __dir__
. Read this card for more information about __FILE__
and __dir__
.
Example
Structure:
.
├── bin
│ ├── format_changelog
├── CHANGELOG.md
bin/format_changelog
:
#!/usr/bin/env ruby
changelog_path = ? # How to get the path to ../CHANGELOG.md independent of the working dir of the caller
changelog = File.read(changelog_path)
# ... further actions...
Using ngrok for exposing your development server to the internet
Sometimes you need to access a dev server running on localhost from another machine that is not part of the same network. Maybe you want to use your phone to test a web page, but are only in a guest WiFi. In the past, we often used some port forwarding or other techniques to expose the service to the internet.
Enter ngrok, a command line tool that gives you an on-the-fly internet...
makandra cards: A knowledge base on web development, RoR, and DevOps
What is makandra cards?
We are makandra, a team of 60 web developers, DevOps and UI/UX experts from Augsburg, Germany. We have firmly anchored the sharing of knowledge and continuous learning in our company culture. Our makandra cards are our internal best practices and tips for our daily work. They are read worldwide by developers looking for help and tips on web development with Ruby on Rails and DevOps.
15 years ago – in 2009 – we wrote our first card. Since then, over 6000 cards have been created, not o...
Rails: Using require and permit for attributes
Raising errors for required and permitted attributes makes it easier to find errors in your application during development and in tests. Consider this approach if you want to strengthen the params handling in your application.
Example
config.action_controller.action_on_unpermitted_parameters = :raise
def user_params
params.require(:user).permit(:full_name)
end
Effects
- This raises an error `ActionController::Parameter...
Unpoly + Nested attributes in Rails: A short overview of different approaches
This card describes two variants, that add a more intuitive workflow when working with nested attributes in Rails + Unpoly.
Example
For the following examples we use a simple data model where a user has zero or more tasks.
class ExampleMigration < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :full_name
t.timestamps
end
create_table :tasks do |t|
t.string :title
t.references :user
t.timestamps
end
end
end
class Task < ApplicationRecord
...