Take care of existing users when upgrading Clearance
When upgrading Clearance, pay attention whether the password hashing strategy might have changed. Old clearance versions (< 1.0) used SHA1-encrypted passwords by default. Current versions default to BCrypt.
If you simply upgrade without taking this into account, users will get a BCrypt::Errors::InvalidHash
when trying to sign in. Your tests will not notice this, since they create new users for each scenario.
To fix it, you'll either have to force all users to reset their passwords, or you can allow old users to keep signing in with t...
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...
DOM API for jQuery users
General hints on the DOM
- the root of the DOM is
document
- custom elements inherit from
HTMLElement
. They need a-
(dash) in their name, e.g.<notification-box>
. - event listeners don't have event delegation à la
.on('click', cssSelector, handler)
Comparison
Action | jQuery | DOM API equivalent |
---|---|---|
Find descendant(s) by CSS selector | .find(selector) |
one: `.querySelector(selecto... |
Unpoly: Showing the better_errors page when Rails raises an error
When an AJAX request raises an exception on the server, Rails will show a minimal error page with only basic information. Because all Unpoly updates work using AJAX requests, you won't get the more detailled better_errors page with the interactive REPL.
Below is an event listener that automatically repeats the request as a full-page load if your development error shows an error page. This means you get...
Regular Expressions: Excessive backtracking can get yourself in trouble
Two weeks ago, Cloudflare was struck by a global outage that lasted ~30 minutes. The incident was rooted on a CPU exhaustion caused by a single regular expression containing some catastrophic backtracking:
.*(?:.*=.*)
This is a small reminder do keep using the lazy operator ?
whenever possible and furthermore be aware that regular expressions should not only be unit-tested but also evaluated in terms of performance. See <https://makandracards.com/makandra/494822-regul...
Webpack(er): A primer
webpack is a very powerful asset bundler written in node.js to bundle (ES6) JavaScript modules, stylesheets, images, and other assets for consumption in browsers.
Webpacker is a wrapper around webpack that handles integration with Rails.
This is a short introduction.
Installation
If you haven't already, you need to install node.js and Yarn.
Then, put
gem 'webpacker', '~> 4.x' # check if 4.x is still cu...
Installing Ruby 2.3 or below on Ubuntu 17 and above
From Ubuntu 17, rbenv fails to install Ruby below 2.4 because of a mismatching OpenSSL dependency: it needs libssl1.0-dev
for the installation process, but recent Ubuntus come with libssl-dev
.
From the linked StackOverflow comment:
As far as I know (and tested), Ruby versions < 2.4 requires libssl1.0, while >2.4 libssl1.1+. The two libssl packages conflict with each other, so you can't have both of them, so I had to juggle the libs in order to install the required ruby version. To make things even funnier (or more complicated),...
Git: Apply a diff
git apply
allows you to apply a diff onto your HEAD
. Most often you can achieve the same result with a rebase
& merge
.
Example:
master commit1 - commit3
feature-branch \ commit2 - commit4
git checkout feature-branch
git reset --hard commit3
git diff ..commit4 | git apply
master commit1 - commit3
feature-branch \ Unstaged commit 2 & 4
You can also [create a patch and apply it afterwards](https://makandracards.com/makandra/2521-git-how-to...
RubyMine: Efficiently filtering results in the "Finder" overlay
RubyMine comes with a nice way to grep through your project's files: The finder (ctrl + shift + f
). Don't be discouraged about the notice 100+ matches in n+ files
if your searched keyword is too general or widely used in your project.
RubyMine comes with a few ways to narrow down the resulting list, don't hesitate to apply those filters to speed up your search. Your keybinding might vary based on your personal settings.
File mask (alt + k
)
If you already know the file extension of your ...
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...
Jasmine: using async/await to write nice asynchronous specs
Jasmine has long standing support for writing asynchronous specs. In days gone by we used the done
callback to achieve this, but these days it is possible to write much more readable specs.
Async specs
As a first example, say we want to check that some form disables the submit button while working.
// bad (how we used to do it)
beforeEach(() => {
this.form = setupMyForm()
this.submitButton = findTheSubmitButton()
})
it('disables the submit button while working', (done) => {
expect(this.submitButton.disabled).toBe(false)
...
Adding Jasmine JavaScript specs to a Webpack(er) project
The goal is to get Jasmine specs running in a Rails project using Webpacker, with the browser based test runner. Should be easily adaptable to a pure Webpack setup.
Step 1: Install Jasmine
yarn add jasmine-core
Step 2: Add two separate packs
Since we do not want to mix Jasmine into our regular Javascript, we will create two additional packs. The first only contains Jasmine and the test runner. The second will contain our normal application code and the specs themselves.
We cannot...
How to get a backtrace if rspec (or any other ruby process) hangs with no output
If rspec hangs with no output and you dont get a backtrace neither with --backtrace
nor by just killing it with crtl-c,
you can put the following in your spec/spec_helper.rb
:
puts "rspec pid: #{Process.pid}"
trap 'USR1' do
threads = Thread.list
puts
puts "=" * 80
puts "Received USR1 signal; printing all #{threads.count} thread backtraces."
threads.each do |thr|
description = thr == Thread.main ? "Main thread" : thr.inspect
puts
puts "#{description} backtrace: "
puts thr.backtrace.join("\n")
end
...
Unpoly: Testing values for presence or blankness
In Ruby on Rails, all objects have a useful blank?
method. It returns true for nil
but also for empty strings or empty arrays. There is also a universal method present?
which returns true
for all values that are not blank?
.
In JavaScript you need to roll your own implementation of blank?
and present?
.
If your application uses [Unpoly](...
Deal with certain travis CI failures
Travis changed their default distribution from Ubuntu 14.04 (trusty) to 16.04 (precise). This might break your test setup for new builds.
You can solve this issue by freezing your test distribution in the .travis.yml
to Ubuntu 14.04 until you have the time to solve all the issues you will have in 16.04:
dist: trusty
Error details
Here are few indicators that you ran into this issue:
Connection to the PostgreSQL database does not work anymore
Your travis-ci builds might have started failing on the usual
psql -c...
Documenting your Rails project's Node.js version in .nvmrc
Not all versions of Node.js are compatible with each other. Also npm packages may require a minimum or maximum version of Node.js. We use nvm on our development PCs so we can operate multiple versions of Node.js in parallel.
To make sure that all developers use a compatible version of Node.js, your Rails project should declare the required Node.js in a file called .nvmrc
.
When a .nvmrc
exists, developers can cd
in your project directory and activate...
Selenium may break with ChromeDriver 75+
When you update your ChromeDriver to version 75 or beyond, you might get w3c errors in your tests.
For example, reading the browser console for errors no longer works, and page.driver.browser.manage.logs.get(:browser)
will raise an error like "undefined method `log' for #<Selenium::WebDriver::Remote::W3C::Bridge:0x000055903f307aa8>
".
Add options.add_option('w3c', false)
to your Selenium configuration (e.g. features/support/selenium.rb
) and you should be back to normal:
Capybara.register_driver :selenium do |app|
options ...
cucumber_factory: How to keep using Cucumber 2 Transforms in Cucumber 3
Cucumber up to version 2 had a neat feature called Step Argument Transforms which was dropped in favor of Cucumber 3 ParameterTypes. While I strongly encourage you to drop your legacy Transforms when upgrading to Cucumber 3, it might not always be possible due to their different design.
This is a guide on how to keep the exact same functionality of your old Transforms
while writing them in the style of new `Paramet...
no passenger served applications running error when deploying via capistrano
When deploying with capistrano it's possible you get this "error" message:
*** [err :: example.com] There are no Phusion Passenger-served applications running whose paths begin with '/var/www/example.com'.
*** [err :: example.com]
This is just because there were no running passenger process for this application on the server which could be restarted. It's not a real error. The application process will start if the first request for this app hits the appserver.
The output appears as err
because it's printed to stderr
.
Dep...
Heads up: Capybara 3's text matchers no longer squish whitespace by default
Until Capybara 2, node finders that accept a text
option were able to find nodes based on rendered text, even if it spans over multiple elements in the HTML. Imagine a page that includes this HTML:
<div class='haystack'>
Hi!
<br>
Try to match me.
</div>
Even though the text is separated by a <br>
tag in the HTML, it is matched until Capybara 2 which used to "squish" text prior to the comparison.
# Capyabara 1 or 2
page.find(...
Events triggered by jQuery cannot be observed by native event listeners
jQuery has a function $.fn.trigger()
. You can use it to dispatch an event on a jQuery object:
let $element = $('.foo')
$element.trigger('change')
A caveat is that such an event will be received by jQuery event listeners, but not by native event listeners:
let $element = $('.foo')
$element.on('change', event => console.log('I will be called'))
$element[0].addEventListener('change', event => console.log("I WON'T be called"))
$element.trigger('change')
This is not an issue when your entire app is ...
Webpack: How to split your bundles
To keep JavaScript sources small, it can sometimes make sense to split your webpack bundles. For example, if your website uses some large JavaScript library – say TinyMCE – which is only required on some selected pages, it makes sense to only load that library when necessary.
In modern webpack this is easily doable by using the asynchronous import
function.
Say we have an unpoly compiler that sets up TinyMCE like this (code is somewhat simplified):
// TinyMCE as part of the main bundle!
import tinymce from 'tinymce/tinymce'
// U...
Capybara: Testing file downloads
Download buttons can be difficult to test, especially with Selenium. Depending on browser, user settings and response headers, one of three things can happen:
- The browser shows a "Save as..." dialog. Since it is a modal dialog, we can no longer communicate with the browser through Selenium.
- The browser automatically downloads the file without prompting the user. For the test it looks like nothing has happened.
- The browser shows a binary document in its own window, like a PDF. Capybara/Selenium freaks out because there is no HTML docum...
SameSite cookies
TL;DR Most web applications do not require action on this. SameSite=None
(old browser default) will continue to work, and SameSite=Lax
(new Chrome default, gradually rolled out) is an even better default for cookies. Set SameSite=Strict
only for extra security in special cases (see below). If your application is rendered in an iframe (e.g. a video player or some news stream), you need to configure its relevant cookies as SameSite=None
.
The SameSite
cookie attribute targets **c...