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:

  1. Use them with an image: <img src='path-to-icon.svg'>
  2. Embed them inline with <svg>$ICON</svg>
  3. Embed them using CSS and background-image: url(path-to-icon.svg) or even background-image: url(data:$ICON).
  4. 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, use git 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.

  1. Staged and unstaged changes
  2. [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.

  1. Open find and replace
  2. Activate the regex mode (click on the .* icon next to the "find" field).
  3. 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...