Rails: Overriding view templates under certain conditions only

Rails offers a way to prepend (or append) view paths for the current request. This way, you can make the application use different view templates for just that request.

Example

A use case of this is a different set of view templates that should be used under certain circumstances:

class UsersController < ApplicationController

  before_action :prepare_views
  
  def index
    # ...
  end    
  
  private
  
  def prepare_views
    if <condition>
      prepend_view_path Rails.root.join('app', 'views', 'special')
    end
  end
 ...

Advantages of using appname.daho.im:3000 over localhost:3000

Running rails server will start a local server that you can access via http://localhost:3000.

When you are working on multiple web apps, they will likely set cookies with generic names on localhost. This is annoying, since you will sign out your current user whenever you switch to another app.

A better way is to use our own daho.im service. All daho.im subdomains resolve to your local IP (127.0.0.1). That means you can use a different hostname for different apps, and you will stay logged in in each app:

http://foo-ap...

PostgreSQL: Upgrading your user to a superuser

Your default postgres user is named like your linux user. That default user has limited access privileges, which can cause issues such as:

  • DatabaseCleaner needs to disable foreign key constraints before it can wipe the database.
  • Importing a remote dump with geordi
  • Asking Postgres to show the storage path of a database

Doing these things without a superuser will show a Postgres error or (in Ruby) raise PG::InsufficientPrivilege.

To do so, the application's PostgreSQL user must be a superuser. ...

How to control Chromedriver using curl

Here is how to use Chromedriver without libraries like selenium-webdriver. This can be useful for debugging.

The following example visits a web page and reads the a headline's text contents.

  1. Create a session. You will get a JSON response containing lots of information about your Chrome session, including a sessionId. Use that to send any future commands to your chromedriver session.

    $ curl -XPOST http://localhost:9515/session -d '{"desiredCapabilities":{"browserName":"chrome"}}'
    {"sessionId":"your-session-id-here","sta...
    

XHR is not JSON

When a Rails controller action should handle both HTML and JSON responses, do not use request.xhr? to decide that. Use respond_to.

I've too often seen code like this:

def show
  # ...
  if request.xhr?
    render json: @user.as_json
  else
     # renders default HTML view
  end
end

This is just plain wrong. Web browsers often fetch JSON via XHR, but they (should) also send the correct Accept HTTP header to tell the server the data they expect to receive.

If you say request.xhr? as a means for "wants JSON" you are ...

Cucumber: How to avoid VCR errors for unused requests in pending tests

When you have a pending Cucumber step (or feature) that also uses an existing VCR cassette, your pending test may fail at the very end with an error like this:

There are unused HTTP interactions left in the cassette:
  - [get ...] => [200 ...]
  - [get ...] => [200 ...] (VCR::Errors::UnusedHTTPInteractionError)

The error happens because your VCR is configured to complain about cassettes that contain extra requests which your test did not use. This is often a good configuration.
If you do not want to change your whole test suite...

Rails and Postgres: How to test if your index is used as expected

This is a small example on how you can check if your Postgres index can be used by a specific query in you Rails application. For more complex execution plans it might still be a good idea to use the same path of proof.

1. Identify the query your application produces

query = User.order(:last_name, :created_at).to_sql
puts query
# => SELECT "users".* FROM "users" ORDER BY "users"."last_name" ASC, "users"."created_at" ASC

2. Add an index in your migration and migrate

add_index :users, [:last_name, :created_at]...

How to use cookies with curl

When making requests using curl, no cookies are sent or stored by default.
However, you can tell curl to re-use cookies received earlier (or forge your own cookies).

There are 2 command line switches you need to use:

  • -c will write cookies to a given file
  • -b will read cookies from a given file

Example

The remote server sets a "foo" cookie to value "bar". We tell curl to store them to a file at /tmp/cookies using the -c switch.

$ curl -c /tmp/cookies http://httpbin.org/cookies/set?foo=bar

You may look at the file, ...

Jasmine: Mocking API requests in an Angular service spec

Situation: You want to write a spec for a function inside an Angular service. This function at some point makes an API request and acts upon response. Notably, your Angular app employs uiRouter, although it is not used nor actually required for this test.

Working test setup

# Capitalized expressions are intended to be replaced with YOUR values

describe 'SERVICE', ->

  beforeEach ->
    module 'MODULE'

    module ($urlRouterProvider) ->
      # Prevent uiRouter's initialization, i.e. do not sync the current URL
      # with its r...

HTML: Making browsers wrap long words

By default, browsers will not wrap text at syllable boundaries. Text is wrapped at word boundaries only.

This card explains some options to make browsers wrap inside a long word like "Donaudampfschifffahrt".

Option 1: hyphens CSS property (preferred)

Modern browsers can hyphenate natively. Use the hyphens CSS property:

hyphens: auto

There is also hyphens: none (disable hyphenations even at &shy; entities) and hyphens: manual (hy...

chromedriver-helper gem in Gemfile might break you selenium tests (of other projects)

Summary: Don't add chromedriver-helper to the Gemfile

  • the executables might break your tests in projects where chromedriver-helper is not in the Gemfile
  • developers with different chrome versions will have problems using the same chromedriver-helper version

Background

If you install the chromedriver-helper gem, but don't have it in you Gemfile, your selenium tests might fail with:

Selenium::WebDriver::Error::WebDriverError: unable to connect to chromedriver 127.0.0.1:9515

The reason is that chromedriver-helper ov...

Inspecting Angular 1.x UI Router

If your Angular app has some decent complexity, it will not be easy to use UI Router straight away. Here are some hints on how to get around.

Accessing the UI Router $state service from the browser console

$state = angular.element(document.body).injector().get('$state'): Retrieves the Angular injector and asks it for the $state service.

Inspecting the current state

$state.current

Inspecting params of the current state

$state.params

Turning off VCR when stubbing with Webmock

Sometimes when working with VCR you still want to use webmock. Since VCR hooks into webmock and fails when an unknown request is happening, you have to turn it off in order to use webmock like you are used to. Here is how to do this in rspec.

RSpec.configure do |config|
  config.around do | example |
    if example.metadata[:turn_off_vcr]
      VCR.turn_off!
      example.run
      VCR.turn_on!
    else
      example.run
    end
  end
end

Webservice to test and inspect requests

Requestb.in is a webservice that gives you a temporary URL you can use to test request. The page will automatically record and display the latest web request made to it.

Spreewald: Content-Disposition not set when testing a download's filename

Precondition

  • You are not using javascript tests
  • The file is served from a public folder (not via controller)

Problem description

If you deliver files from a public folder it might be that the Content-Disposition header is not set. That's why the following spreewald step might raise an error:

Then I should get a download with filename "..."
expected: /filename="some.pdf"$/
     got: nil (using =~) (RSpec::Expectations::ExpectationNotMetError)

Solution

One solution...

Fixing flaky E2E tests

An end-to-end test (E2E test) is a script that remote-controls a web browser with tools like Selenium WebDriver. This card shows basic techniques for fixing a flaky E2E test suite that sometimes passes and sometimes fails.

Although many examples in this card use Ruby, Cucumber and Selenium, the techniques are applicable to all languages and testing tools.

Why tests are flaky

Your tests probably look like this:

When I click on A
And I click on B
And I click on C
Then I should see effects of C

A test like this works fine...

Be careful to use correct HTTP status codes for maintenance pages

When your public-facing application has a longer downtime for server maintenance or long migrations, it's nice to setup a maintenance page to inform your users.

When delivering the maintenance page, be very careful to send the correct HTTP status code. Sending the wrong status code might get you kicked out of Google, or undo years of SEO work.

Popular footguns

Here are some ways to shoot yourself in the foot during maintenance:

  • If all your routes send a "200 OK" with a HTML body "We're b...

Async control flow in JavaScript: Promises, Microtasks, async/await

Slides for Henning's talk on Sep 21st 2017.


Understanding sync vs. async control flow

Talking to synchronous (or "blocking") API

print('script start')
html = get('/foo')
print(html)
print('script end')

Script outputs 'script start', (long delay), '<html>...</html>', 'script end'.

Talking to asynchronous (or "evented") API

print('script start')
get('foo', done: function(html) {
  print(html)
})
print('script end')

Script outputs 'script start', 'script end', (long ...

How to fix broken font collisions in wkhtmltopdf

If you are using PDFKit / wkhtmltopdf, you might as well want to use custom fonts in your stylesheets. Usually this should not be a problem, but at times they include misleading Meta-information that leads to a strange error in the PDF.

The setup

  • The designer gave you two fonts named something like BrandonText-Regular and BrandonText-Bold. (With flawed Meta-information)
  • You have a HTML string to be rendered by PDFKit
  • For demonstration purposes, this strin...

Sentry: How to get a list of events that is programmatically processable

The linked documentation explains how to get a JSON list of the latest 100 events of a given issue. Simply open this URL while logged in:

https://sentry.io/api/0/issues/<issue id>/events/

How to pair a Bose Quiet Comfort 35 with your Ubuntu computer

You need to disable "Bluetooth low energy", then follow these core steps:

  1. Make sure the headphones are in pairing mode.
  2. Pair with System Settings > Bluetooth. On 16.04 I had to choose "proceed without pairing" and enter the PIN "0000"
  3. Select & test the headphones in System Settings > Sound. Choose High Fidelity Playback (A2DP Sink).

I also had to install a package with sudo apt-get install pulseaudio-module-bluetooth and load it with pactl load-module module-bluetooth-discover. Put the latter command into ~/.bashrc or you'll...

JavaScript bookmarklet to click an element and copy its text contents

Here is some JavaScript code that allows you to click the screen and get the clicked element's text contents (or value, in case of inputs).

The approach is simple: we place an overlay so you don't really click the target element. When you click the overlay, we look up the element underneath it and show its text in a browser dialog. You can then copy it from there.

While moving the mouse, the detected element is highlighted.

Here is the one-liner URL that you can store as a bookmark. Place it in your bookmarks bar and click it to activate....

Ruby: A small summary of what return, break and next means for blocks

Summary

  • Use return to return from a method. return accepts a value that will be the return value of the method call.
  • Use break to quit from a block and from the method that yielded to the block. break accepts a value that supplies the result of the expression it is “breaking” out of.
  • Use next to skip the rest of the current iteration. next accepts an argument that will be the result of that block iteration.

The following method will serve as an example in the details below:

def example
  puts yield
  puts ...

How to use a local gem in your Gemfile

You can use local copies of gems in your Gemfile like this:

gem 'spreewald', path: '~/gems/spreewald'

As soon as you have bundled your project with the local copy of the gem, all code changes in the copy will be available on your project. So you can for example set a debugger or add console output in the gem and use it from your project.
If you checked out the gem with your versioning tool, you can easily reset your changes afterwards or make a pull request for the gem if you improved it.

Don't commit a Gemfile with local path...