ActiveRecord::Relation#merge overwrites existing conditions on the same column
In Ruby on Rails ActiveRecord::Relation#merge
overwrites existing conditions on the same column. This may cause the relation to select more records than expected:
authorized_users = User.where(id: [1, 2])
filtered_users = User.where(id: [2, 3])
authorized_users.merge(filtered_users).to_sql
# => SELECT * FROM users WHERE id IN (2, 3)
The merged relation select the users (2, 3)
, although we are only allowed to see (1, 2)
. The merged result should be (2)
.
This card explores various workarounds to combine two scopes so t...
Rubymine: Configure CTRL + ALT + SHIFT + c to work with "Test Source Roots"
To navigate between test and test subject Rubymine requires you to set the test root sources as Test Sources Root.
In case you are using the keyboard shortcut "CTRL + ALT + SHIFT + c" to copy the reference path + you have set the "Test Sources Root" for your test folders, you might consider setting this keyboard to "Copy From Repository Root". This will return the path `spec/foo_spec....
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...
Rails: Assigning associations via HTML forms
Let's say we have posts with an attribute title
that is mandatory.
Our example feature request is to tag these posts with a limited number of tags. The following chapters explain different approaches in Rails, how you can assign such an association via HTML forms. In most cases you want to use Option 4 with assignable values.
The basic setup for all options looks like this:
config/routes.rb
Rails.application.routes.draw do
root "posts#index"
resources :posts, except: [:show, :destroy]
end
**db/migrate/...
How to exclusively lock file access in ruby
We will achieve this by creating a block accepting method to optionally create and then lock a .lock
File of the underlying accessed file.
Why create a .lock
file?
- The main advantage of creating a
.lock
file is that#flock
might block some operations and require the index node of the file to be consistent. Some operations might change that index node. - In some cases it might also be convenient to just read/write the lock file first and update the other file afterwards or vice versa, such that breaking of a process does not...
Issue Checklist Template
This is a checklist I use to work on issues. For this purpose I extracted several cards related to the makandra process and ported them into a check list and refined that over time a little bit.
This task list is divided by the Gate keeping process in the following steps:
1. Starting a new feature
2. Working on the issue
3. Finishing a feature
4. After Review
Here are some ti...
Rails Partials
Rails partials have a lot of "hidden" features and this card describes some non-obvious usages of Rails Partials.
Rendering a basic partial
The most basic way to render a partial:
render partial: 'partial'
This will render a _partial.html.erb
file. Notice how all partials need to be prefixed with _.
It's possible to define local variables that are only defined in the partial template.
# _weather.html.erb
<h1>The weather is <%= condition %></h1>
# index.html.erb
render partial: 'weather', locals: { condition: ...
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...
git: find the version of a gem that releases a certain commit
Sometimes I ran across a GitHub merge request of a gem where it was not completely obvious in which version the change was released. This might be the case for a bugfix PR that you want to add to your project.
Git can help you to find the next git tag that was set in the branch. This usually has the name of the version in it (as the rake release
task automatically creates a git tag during release).
git name-rev --tags <commit ref>
Note
The more commonly used
git describe
command will return the last tag before a c...
Signed URLs with Ruby on Rails
Using ActiveRecord's #signed_id
and .find_signed
methods you can create URLs that expire after some time. No conditionals or additional database columns required.
Heads up: network requests `Kernel#open` are not mocked with VCR
We usually rely on VCR and WebMock to prevent any real network connection when running our unit tests.
This is not entirely true: They are both limited to a set of HTTP libraries listed below (as of 2022). Direct calls to Kernel#open
or OpenURI#open_uri
are not mocked and will trigger real network requests even in tests. This might bite you e.g. in [older versions of CarrierWave](https://github.com/carrierwaveuploader/carrierwave/blob/0.11-stable/lib/carrierwave/upl...
Bookmarklet: cards Markup Link Bookmarklet
The cards editor has a feature "Cite other card" to create links to other cards in the same deck as mardown links.
If you want to reference a card from a different deck, this bookmarklet might be useful:
javascript:(function () {
const doAlert = () => { alert("Maybe not a makandra card?") };
let cardsPathPattern = /(\/[\w-]+\/\d+)-.+/;
if (window.location.pathname.match(cardsPathPattern)) {
let currentPath = window.location.pathname.match(cardsPathPattern)[1];
let title = document.querySelector('h1.note--title')?.textCon...
A modern approach to SVG icons
You have some SVG files you want to use as icons on your website. How would you embed them?
Common options are:
- Use them with an image:
<img src='path-to-icon.svg'>
- Embed them inline with
<svg>$ICON</svg>
- Embed them using CSS and
background-image: url(path-to-icon.svg)
or evenbackground-image: url(data:$ICON)
. - Build your own icon font.
All of these have drawbacks:
- Image and
background-image
do not allow to recolor the image using CSS. - Inline-
<svg>
are unnecessary work for the server and are...
Using path aliases in esbuild
In esbuild, you usually import other files using relative paths:
import './some-related-module'
import `../../utils/some-utility-module`
import `../../../css/some-css.sass`
This is totally fine if you import closely related files, but a bit clunky when you're trying to import some "global" module, like a utility module. When moving a file, your imports also need to change.
To get around this, esbuild support a mechanism first introduced in TypeScript called "path aliases". It works like this:
First, you create a file called `js...
Chrome DevTools: Treasure (Overview)
tl;dr
The Chrome DevTools are a neat collection of tools for the daily work as a web developer. If you're lucky, you'll maybe find some handy stuff in here.
Analysing
Breakpoints
- [Breakpoints on HTML Elements](https://makandracards.com/makandra/517982-chrome-devtools...
GitLab: Git alias for creating a merge request on push
Git allows you to set push options when pushing a branch to the remote.
You can use this to build an alias that automatically pushes a branch and creates a merge request for it.
Put this in your ~/.gitconfig
in the [alias]
section:
mr = push origin HEAD -o merge_request.create -o merge_request.draft
Now you can do git mr
and a draft merge request will be created.
Target branch is your project's default branch, i.e. main
or master
.
To specify a different target branch, add -o merge_request.target=other-branch
.
[There...
Debug flaky tests with an Unpoly observeDelay
The problem
Unpoly's [up-observe]
, [up-autosubmit]
and [up-validate]
as well as their programmatic variants up.observe()
and up.autosubmit()
are a nightmare for integration tests.
Tests are usually much faster than the configured up.form.config.observeDelay
. Therefore, it may happen that you already entered something into the next field before unpoly updates that field with a server response, discarding your changes.
The steps I wait for active ajax requests to complete
(if configured) and capybara-lockstep can catch some ...
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...
How to fix "Exit with code 1 due to network error: ProtocolUnknownError" with wkhtmltopdf
New versions of wkhtmltopdf dissallow file://
URLs by default. You can allow them by passing --enable-local-file-access
.
If you are using PDFKit, set the option
PDFKit.configure do |config|
config.default_options = {
enable_local_file_access: true,
}
end
This will be necessary in many setups to allow wkhtmltopdf to fetch assets (such as stylesheets) from the filesystem.
Note on security
Allowing this poses some risk when you render user input, since it might be feasible to include data from the local filesyste...
Git commands to discard local changes
Use case
You have uncommited changes (you can always check by using git status
), which you want to discard.
Context
Now there are several options to discard these depending on your exact situation.
The headlines will differentiate the cases whether the files are staged or unstaged.
- Staged and unstaged changes
- [Staged changes](https://makandracards.com/makandra/516559-git-commands-to-discard-local-changes#s...
RubyMine: Find and Replace with Regex (Capture Groups and Backreferences)
tl;dr
In RubyMine you can use find and replace with capture groups
(.*?)
and backreferences$1
(if you have several groups:$[Capture-Group ID]
).
Named captures(?<text>.*)
are also supported.
Examples
Replace double quotes with single quotes
If you want to replace double quotes with single quotes, replacing every "
with a '
is prone to errors. Regular expressions can help you out here.
- Open find and replace
- Activate the regex mode (click on the
.*
icon next to the "find" field). - Fill in f...
SEO: The subtle differences of robots.txt disallow vs meta robots no-index
The robots.txt file and <meta name="robots">
HTML tag can be used to control the behavior of search engine crawlers. Both have different effects.
robots.txt
Marking a URL path as "disallowed" in robots.txt tells crawlers to not access that path.
robots.txt is not a guarantee for exclusion from search engine results.
A "disallowed" URL might be known from an external link, and can still be displayed for a matching search.
Example: even if/admin
is disallowed in robots.txt, `/admin/som...
Prefer using Dir.mktmpdir when dealing with temporary directories in Ruby
Ruby's standard library includes a class for creating temporary directories. Similar to Tempfile it creates a unique directory name.
Note:
- You need to use a block or take care of the cleanup manually
- You can create a prefix and suffix e.g.
Dir.mktmpdir(['foo', 'bar']) => /tmp/foo20220912-14561-3g93n1bar
- You can choose a different base directory than
Dir.tmpdir
e.g. `Dir.mktmpdir('foo', Rails.root.join('tmp')) => /home/user/rails_example/tmp/foo20220912-14...
Ruby and Rails: Debugging a Memory Leak
A memory leak is an unintentional, uncontrolled, and unending increase in memory usage. No matter how small, eventually, a leak will cause your process to run out of memory and crash.
If you have learned about a memory leak, looking at the number of Ruby objects by type can help you track it down:
> pp ObjectSpace.count_objects
{:TOTAL=>77855,
:FREE=>4526,
:T_OBJECT=>373,
:T_CLASS=>708,
:T_MODULE=>44,
:T_FLOAT=>4,
:T_STRING=>65685,
:T_REGEXP=>137,
:T_ARRAY=>984,
:T_HASH=>87,
:T_STRUCT=>12,
:T_BIGNUM=>2,
:T_FILE=>3,
:T_D...