How to debug file system access in a Rails application
It might sometimes be useful to check whether your Rails application accesses the file system unnecessarily, for example if your file system access is slow because it goes over the network.
The culprit might be a library like carrierwave that checks file existence or modification times, whereas your application could determine all this from your database.
Introducing strace
One option it to use strace for this, which logs all system calls performed by a process.
To do this, start your rails server using something like
DISA...
Capybara: Most okayest helper to download and inspect files
Testing file download links in an end-to-end test can be painful, especially with Selenium.
The attached download_helpers.rb provides a download_link method for your Capybara tests. It returns a hash describing the download's response:
details = download_link('Download report')
details[:disposition] # => 'attachment' or 'inline'
details[:filename] # => 'report.txt'
details[:text] # => file content as string
details[:content_type] # => 'text/plain'
Features
Compared to [other approaches](...
RSpec: How to turn off partial double verification temporarily
While verifying doubles in RSpec is a good default, it is limited in the amount of methods it actually is able to verify.
The background is that RSpec can't verify dynamically defined methods, which is a known issue for the usage of helper_method and also the reason why [RSpec >= 3.6](http://rspec.info/blog/2017/05/rspec-3-6-has-been-rel...
Project maintenance: four levels of code quality
Code quality can be measured in four levels:
- (Working code)
- Reliable code (minimum)
- Readable code (ok for short-lived code)
- Changeable code (standard level)
The code quality of a project directly impacts its maintainability.
Generally you should aim for level 3. If the code will stay for less than a few months, it may stay at level 2. Never go below level 1.
0. Working code
You have implemented that feature and it works. Congrats! You have reached level zero, which means three levels of code quality lie ahead.
First, m...
ActiveRecord: Query Attributes
tl;dr
You can useattribute?as shorthanded version ofattribute.present?, except for numeric attributes and associations.
Technical Details
attribute? is generated for all attributes and not only for boolean attributes.
These methods are using #query_attribute under the hood. For more details you can see ActiveRecord::AttributeMethods::Query.
In most circumstances query_attribute is working like attribute.present?. If your attribute is responding to :zero? then you have to be aware that `query_attri...
How to get information about a gem (via CLI or at runtime from Ruby)
When you need information about a gem (like version(s) or install path(s)), you can use the gem binary from the command line, or the Gem API inside a ruby process at runtime.
gem binary (in a terminal)
You can get some information about a gem by running gem info <gem name> in your terminal.
Example:
$ gem info irb
*** LOCAL GEMS ***
irb (1.4.1, 1.3.5)
Author: Keiju ISHITSUKA
Homepage: https://github.com/ruby/irb
Licenses: Ruby, BSD-2-Clause
Installed at (1.4.1): /home/arne/.rbenv/versions/3.0.3/lib/ruby/g...
Version 5 of the Ruby Redis gem removes Redis.current
Redis.current will be removed without replacement in redis-rb 5.0.
Version 4.6.0 adds deprecation warnings for Redis.current and Redis.current=:
`Redis.current=` is deprecated and will be removed in 5.0.
If your application still uses Redis.current, you can only fix it by no longer using it. Here is how.
Redis.new when you need it
You can easily instantiate a Redis client when you need it.
There is probably already a constant like REDIS_URL that you use to configure Sidekiq or similar. So just use that one.
``...
How to access Chrome Devtools when running JavaScript tests via CLI
While we are used to run our JavaScript tests on a test page within our Browser, it's also possible to run them on the command line with NodeJS. I think that's actually the most common way to run JS tests.
Given a Vue project that uses Jest (via vue-cli-service) with the following package.json:
{
"scripts": {
"test": "vue-cli-service test:unit --testMatch='**/tests/**/*.test.js' --watch"
},
}
This allows us to run J...
Terminator setup for Procfile-based applications for more comfortable debugging
We use foreman to start all necessary processes for an application, which are declared in a Procfile. This is very convenient, but the outputs of all processes get merged together. Especially while debugging you might not want other processes to flood your screen with their log messages.
The following setup allows you to start Terminator in a split view with the Rails server running in the left pane and all remaining processes running via foreman in the right pane. It was heavily inspired by [this card](https://makandracards.com/makandr...
esbuild: Make your Rails application show build errors
Building application assets with esbuild is the new way to do it, and it's great, especially in combination with Sprockets (or Propshaft on Rails 7).
You might be missing some convenience features, though.
Here we cover one specific issue:
Once you have started your development Rails server and esbuild with the --watch option (if you used jsbundling-rails to set up, you probably use bin/dev), esbuild will recompile your assets upon change, but build errors will only be printed to the terminal. Your application won't complain about them ...
Carrierwave: How to attach files in tests
Attaching files to a field that is handled by Carrierwave uploaders (or maybe any other attachment solution for Rails) in tests allows different approaches. Here is a short summary of the most common methods.
You might also be interested in this card if you see the following error in your test environment:
CarrierWave::FormNotMultipart:
You tried to assign a String or a Pathname to an uploader, for security reasons, this is not allowed.
If this is a file upload, please check that your upload form is multipart encoded.
Factor...
Capybara: Working with invisible elements
When Capybara locates elements in the DOM, by default it allows only accessing visible elements -- when you are using a driver that supports it (e.g. Selenium, not the default Rack::Test driver).
Consider the following HTML:
<div class="test1">One<div>
<div class="test2">Two</div>
With some CSS:
.test1 { display: block }
.test2 { display: none }
We will be using Capybara's find below, but this applies to any Capybara finder methods.
Default: visible: :visible
As described above, by default Capybara finds ...
Caching in Rails < 6.1 may down parts of your application when using public cache control
TL;DR When using Cache-Control on a Rails application, make sure the Vary: Accept header is set.
Proxy caching is a good feature to serve your publicly visible application content faster and reduce load on your servers. It is e.g. available in nginx, but also affects proxies delivered by ISPs.
Unfortunately, there is a little problem in Rails < 6.1 when delivering responses for different MIME-types. Say you have an arbitrary route in your Rails application that is able to respond with regular HTML and JSON. By sending the specific ...
New gem: Rack::SteadyETag
Rack::SteadyETag is a Rack middleware that generates the same default ETag for responses that only differ in CSRF tokens or CSP nonces.
By default Rails uses Rack::ETag to generate ETag headers by hashing the response body. In theory this would enable caching for multiple requests to the same resourc...
The TCF 2.0 (Tranparency and Consent Framework) standard, and what you should know about it
The Interactive Advertising Bureau (IAB) is a European marketing association which has introduced a standard how advertising can be served to users in line with the General Data Protection Regulation (GDPR). This standard is called the TCF 2.0 (Transparency and Consent Framework). If you want to integrate any kind of advertising into a website, chances are the advertising network will require your website to implement that standard. This is a very brief overview of what this means:
The basic idea in the TCF 2.0 ...
Finding ancestors with Capybara
Modern versions of Capybara include a finder method #ancestor which allows you to find a parental element using CSS or XPath.
If you previously did something like this:
field.find(:xpath, './ancestor::div[contains(@class, "form-group")]')
..and prefer CSS, you may rewrite it:
field.ancestor('div.form-group')
Both versions will return the outermost matching element. Use the #order option find the closest parent:
field.ancestor('div.form-group', order: :reverse)
You can use graphs in gitlab comments!
Since GitLab 10.3. you can use Mermaid in GitLab comments: Gitlab Doc.
With Mermaid you can create diagrams based on text / code.
Sometimes a picture says more than thousand words and can help you express your thoughts more clearly.
Some Examples from the Mermaid documentation:
Flowcharts
graph TD;
A-->B;
A-->C...
Using feature flags to stabilize flaky E2E tests
A flaky test is a test that is often green, but sometimes red. It may only fail on some PCs, or only when the entire test suite is run.
There are many causes for flaky tests. This card focuses on a specific class of feature with heavy side effects, mostly on on the UI. Features like the following can amplify your flakiness issues by unexpectedly changing elements, causing excessive requests or other timing issues:
- Lazy loading images
- Autocomplete in search f...
A Guide To CSS Debugging
Debugging in CSS means figuring out what might be the problem when you have unexpected layout results. We’ll look at a few categories bugs often fit into, see how we can evaluate the situation, and explore techniques that help prevent these bugs.
Semantic HTML
Besides their default styling properties, HTML elements have a semantic meaning. For example, an h1 tag is usually styled with a larger font and bold, while it denotes "the single most important headline in its context".
While CSS enables us to style almost any HTML element like anything that is needed, choosing HTML elements corresponding to the meaning of their content has a few advantages:
- HTML becomes a little clearer
- Edge cases have already been considered and implemented:
- Keyboard support (tabbing, arrow keys)
- State...
RSpec matcher to compare two HTML fragments
The RSpec matcher tests if two HTML fragments are equivalent. Equivalency means:
- Whitespace is ignored
- Types of attribute quotes are irrelevant
- Attribute order is irrelevant
- Comments are ignored
You use it like this:
html = ...
expect(html).to match_html(<<~HTML)
<p>
Expected content
</p>
HTML
You may override options from CompareXML by passing keyword arguments after the HTML string:
html = ...
expect(html).to match_html(<<~HTML, ignore_text_nodes: true)
...
Unpoly 2: Don't try to download files through AJAX requests
Rails has the handy controller method send_file which lets us download files easily. We can decide whether the file should be downloaded (disposition: 'attachment') or shown in the browser (disposition: 'inline'). The default is disposition: 'attachment'.
Downloading files will not work when you are calling the controller action from an AJAX request. The browser will try to render the file and insert it in the DOM, which is never what you want.
Unpoly 2
Unpoly (sin...
You should probably load your JavaScript with <script defer>
It is generally discouraged to load your JavaScript by a <script src> tag in the <head>:
<head>
<script src="app.js"></script>
</head>
The reason is that a <script src> tag will pause the DOM parser until the script has loaded and executed. This will delay the browser's first contentful paint.
A much better default is to load your scripts with a <script src defer> tag:
<head>
<script src="app.js" defer></script>
</head>
A deferred script has many useful properties:
- I...
Unobtrusive JavaScript helper to progressively enhance HTML
The attached compiler() function below applies JavaScript behavior to matching HTML elements as they enter the DOM.
This works like an Unpoly compiler for apps that don't use Unpoly, Custom Elements or any other mechanism that pairs JavaScript with HTML elements.
The compiler() function is also a lightweight replacement for our legacy [$.unobtrusive()](https://makandracards.com/makandra/4-unobtrusiv...