Rails: Testing the number of database queries
There are a few tools to combat the dreaded n+1 queries. The bullet gem notifies you of missing eager-loading, and also if there is too much eager-loading. strict_loading in Rails 6.1+ forces developers to explicitly load associations on individual records, for a single association, for an entire model, or globally for all models.
But you can also actually **write spe...
When loading Yaml contents in Ruby, use the :freeze argument to deep-freeze everything
Ruby methods which load from a Yaml file, like YAML.safe_load
or YAML.safe_load_file
, support passing freeze: true
to deep-freeze the entire contents from the Yaml file.
This is available by default on Ruby 3.0 and newer. On older Rubies, you can install psych
3.2.0 or newer for :freeze
support.
As an example, consider the following Yaml file:
---
message:
- hello
- universe
foo:
bar:
baz: "example"
We can now load it as usual, but pass freeze: true
.
>> test = YAML.safe_load_file('example.yml', fre...
Maintaining custom application tasks in Rails
Here are some hints on best practices to maintain your tasks in larger projects.
Rake Tasks vs. Scripts
- The Rails default is using rake tasks for your application tasks. These live in
lib/tasks/*
. - In case you want to avoid rake for your tasks and just use plain ruby scripts, consider
lib/scripts/*
as folder.
Keeping tasks slim
For readability and testing it's easier to keep your tasks slim. We suggest to use folders inside the tasks
or scripts
folder.
Example for a task:
The slim task lib/tasks/gitlab.rb
:
Migrating from Elasticsearch to Opensearch: searchkick instructions (without downtime!)
General
A general overview about why and how we migrate can be found under Migrating from Elasticsearch to Opensearch
This card deals with specifics concerning the use of searchkick.
Step 1: Make Opensearch available for Searchkick
In your Gemfile
# Search
gem 'searchkick' # needs to be > 5, to use Opensearch 2
gem 'elasticsearch'
gem 'opensearch-ruby'
in config/initializers/searchkick.rb
(or wherever you have configured your Searchkick settings) add:
SEARCHKICK_CLIENT_T...
OpenAI TTS: How to generate audio samples with more than 4096 characters
OpenAI is currently limiting the Audio generating API endpoint to text bodies with a maximum of 4096 characters.
You can work around that limit by splitting the text into smaller fragments and stitch together the resulting mp3 files with a CLI tool like mp3wrap or ffmpeg.
Example Ruby Implementation
Usage
input_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Mi eget mauris pharetra et ultrices neque."
output_mp3_path = Rails.root.join("tts/ipsum...
Livereload + esbuild
Getting CSS (and JS) live reloading to work in a esbuild / Rails project is a bit of a hassle, but the following seems to work decently well.
We assume that you already use a standard "esbuild in Rails" setup, and have an esbuild watcher running that picks up your source code in app/assets
and compiles to public/assets
; if not change the paths below accordingly.
Basic idea
We will
- use the
guard-livereload
gem as the livereload server (which send updates to the browser), - use the
livereload-js
npm package in the browser to con...
Transfer records to restore database entries (with Marshal)
If you ever need to restore exact records from one database to another, Marshal
might come in handy.
Marshal.dump
is part of the ruby core and available in all ruby versions without the need to install anything. This serializes complete ruby objects including id
, object_id
and all internal state.
Marshal.load
deserializes a string to an object. A deserialized object cannot be saved to database directly as the the dumped object was not marked dirty, thus rails does not see the need to save it, even if the object is not present in...
Ruby: Using `sprintf` to replace a string at fixed named references
The sprintf
method has a reference by name format option:
sprintf("%<foo>d : %<bar>f", { :foo => 1, :bar => 2 }) # => 1 : 2.000000
sprintf("%{foo}f", { :foo => 1 }) # => "1f"
The format identifier %<id>
stands for different data types to be formatted, such as %f
for floats:
sprintf('%f', 1) # => 1.000000
Example:
This is quite useful to replace ...
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...
Zeitwerk: How to collapse folders in Rails
All direct child directories of app
are automatically added to the eager- and autoload paths. They do NOT create a module for namespacing. This is intuitive, since there normally is no module Model
, or module Controller
. If you want to add a new base directory, there's no additional config needed.
Example
app
├── controllers
├── helpers
├── inputs # No config needed
├── mailers
├── models
├── uploaders # No config needed
├── util # No config needed
└── workers # No config needed
Sometimes it's handy to group files wit...
Solving "TypeError (nil can't be coerced into Integer)" in the Rails console / IRB
On the Rails console, assigning an object to a variable can lead to this strange error (without stacktrace):
irb > recipient = Recipient.find(123)
Traceback (most recent call last):
TypeError (nil can't be coerced into Integer)
irb > recipient
#<Recipient ...
The error is only in the output – the assignment is working. It only occurs when using the --nomultiline
option, and thus [only with IRB 1.2.0+ and before Ruby 3](https://github.com/makandra/geordi/blob...
Ruby: `extend` extends the singleton class's inheritance chain
In the discussion of the difference between include
and extend
in Ruby, there is a misconception that extend
would add methods to the singleton class of a ruby object as stated in many posts on this topic. But in fact, it is added to the ancestors chain of the singleton class! Even though it is technically not the same, practically this can be considered the same in most use cases.
Example
This means, that we are able to overwrite these methods or call the parent version with super
depending in which order and in whi...
Debug MiniMagick calls in your Rails app
Most of our applications use CarrierWave for file uploads. CarrierWave has an integrated processing mechanism for different file versions with support for ImageMagick through CarrierWave::MiniMagick
(which requires the mini_magick
gem). In case your processing runs into an error, CarrierWave will just swallow it and rethrow an error with a very generic message like Processing failed. Maybe it is not an image?
which does not help you finding out what the actual problem is. CarrierWave probably does this for security purposes, but does n...
Spreewald, Cucumber: Selector for the nth element
The recommended additional setup of the spreewald gem, a useful set of cucumber steps, includes adding a file for defining custom selectors which can be used as prose within steps:
When I follow "Edit" within the controls section
Where the controls section
can be any arbitrary defined css selector within selectors.rb
Often it can be useful to select the nth element of a specific selector. Luckily, this can ...
How to make sure that manual deploy tasks (scheduled in Pivotal Tracker) are executed on deploy (with Capistrano)
We regularly have tasks that need to be performed around a deploy. Be it to notify operations about changed application behavior, be it to run a little oneline script after the deploy. Most database-related stuff can be handled by migrations, but every once in a while, we have tasks that are much easier to be performed manually.
Writing deploy tasks
Here is how we manage the deploy tasks themselves:
- Deploy tasks are written inside the Pivotal Tracker story description, clearly marked (e.g. with a headline "Deploy task")
- We disting...
Using Ruby's Method objects for inspecting methods
Do you remember finding where a method is defined?
I recently learned from a senior colleague that Method objects are quite useful within a debugging feast to find out the currently defined internals of methods, because they are either called within the current context or because you want to learn something about the API of the current objects.
Why is this useful?
This is especially useful since Ru...
Use rbenv-each to run a command for every installed Ruby version
The linked rbenv plugin rbenv-each is very helpful to keep QoL gems up to date that are not part of the Gemfile.
For example, you can bump the geordi
version for all your rubies with the following command:
rbenv each gem update geordi
Another useful example would be to bulk-update bundler
or rubygems.
Note that rbenv-each
hasn't been updated since 2018, but it is fully functiona...
Don't use log level :debug in your production environments
Catch phrase
You don't want sensitive user data in your logs.
Background
Rails per default filters sensitive data like passwords and tokens and writes [FILTERED]
to the logs. The code which is responsible for enabling that usually lives in filter_parameter_logging.rb
(Rails.application.config.filter_parameters
). Here is an example of a filtered log entry:
Unfiltered:
`User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."token" = $1 LIMIT $2 [["token", "secret-token"], ["LIMIT", 1]]`
After the filter is appl...
Advanced plotting in Ruby with Gnuplot
Besides Plotting graphs in Ruby with Gruff, which comes handy for many uses cases, you sometimes might need configuration for more advanced plots, e.g. for academic concerns. Then using Gnuplot, the first academic open source plotting software, might be a good option.
There are several wrappers for Ruby available and I mainly looked at one of the two most frequently used ones, which are [ruby_gnuplot](https://github.com/rdp/ruby_gnuplot...
Split your parallel tests by execution time and keep execution logs up to date
Both knapsack
and parallel_tests
have the option to split groups by historic execution time. The required logs for this might be outdated since you manually have to update and push them into your repository.
The following card includes an option how you can keep them consistently up to date with no extra effort locally and/or remotely.
How to always split by execution logs
Parallel Tests
The parallel_tests
gem has the option flag `--group...
Lightning Talk: Coverage based Test Case Prioritization in Ruby on Rails
For my computer science bachelor's thesis I programmed and evaluated a CLI Test Case Prioritization (TCP) tool for makandra. It has been written as a Ruby Gem and was tested and evaluated against one Ruby on Rails project. This card will summarize and present the research results, the evaluation and the programmed CLI tool.
The code has been published for educational purposes on GitHub. The german bachelor's thesis has also been included for download at the end.
...
Do not pass params directly into url_for or URL helpers
Rails' url_for
is useful for generating routes from a Hash, but can lead to an open redirect vulnerability.
Your application's generated route methods with a _url
suffix are also affected because [they use url_for
unter the hood](https://github.com/rails/rails...
CarrierWave: Processing images with libvips
When you write your next CarrierWave uploader, consider processing your images with libvips instead of ImageMagick.
Reasons for libvips
There are several upsides to using libvips over ImageMagick:
- libvips is considerably faster and uses less memory.
- ImageMagick has a large attack surface that has repeatedly caused security incidents in the past (compare [ImageMagick CVEs](https://www....
RubyMine: Fixing "Rubocop returned exit code -1. See the logs for details"
When RubyMine reports Rubocop returning "exit code -1", upgrading Rubocop can be the fix:
gem install rubocop
"The logs" can be accessed with Ctrl + Shift + A > Show log in (Files). This opens your file manager at the IDEA log location; the log file is called "idea.log".
If your operating system supports it, right click into the file manager > Open in Terminal. There run tail -f idea.log
to follow the log.