<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>DIE ANTWORT Techblog</title>
  <subtitle>What’s this all about? (Techstuff: Ruby, Rails, JavaScript and more)</subtitle>
  <id>https://die-antwort.eu/techblog</id>
  <link href="https://die-antwort.eu/techblog"/>
  <link href="https://die-antwort.eu/techblog/feed.xml" rel="self"/>
  <updated>2018-08-22T00:00:00+02:00</updated>
  <author>
    <name>Büro DIE ANTWORT</name>
  </author>
  <entry xml:lang="en">
    <title>Avoid InvalidCrossOriginRequest Exceptions when Having a Catch-all Route</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2018-08-avoid-invalid-cross-origin-request-with-catch-all-route/"/>
    <id>https://die-antwort.eu/techblog/2018-08-avoid-invalid-cross-origin-request-with-catch-all-route/</id>
    <published>2018-08-22T00:00:00+02:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;We recently deployed a CMS-like Rails application, and immediately got hit by a lot of &lt;code&gt;ActionController::InvalidCrossOriginRequest&lt;/code&gt; exceptions, all containing the following description:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security warning: an embedded &amp;lt;script&amp;gt; tag on another site requested protected JavaScript. If you know what you&amp;rsquo;re doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These exceptions got triggered by various bots and crawlers requesting javascript URLs like &lt;code&gt;/wp-includes/js/wpdialog.js&lt;/code&gt;. These URLs sure don’t exist in our app, so why didn’t Rails just respond with a “404 Not Found” status?&lt;/p&gt;

&lt;p&gt;Turns out the root cause for this was that our app has a catch-all route like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# routes.rb&lt;/span&gt;

&lt;span class="c1"&gt;# … (various more specific routes) …&lt;/span&gt;
&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s2"&gt;"/(*page_path)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"pages#show"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;page_path: &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This route is accompanied by a controller action that renders a matching page, or a 404 error page if no page was found:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PagesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="vi"&gt;@page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_first_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:page_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:page_path&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@page&lt;/span&gt;
      &lt;span class="n"&gt;render_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;file: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/public/404.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;layout: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In principle this works fine: If you request a non-existing URL, the 404 error page gets rendered. &lt;em&gt;However&lt;/em&gt;, if the URL ends in &lt;code&gt;.js&lt;/code&gt;, an &lt;code&gt;InvalidCrossOriginRequest&lt;/code&gt; exception is raised instead.&lt;/p&gt;

&lt;h2&gt;A Closer Look Into Rails’ Request Forgery Protection&lt;/h2&gt;

&lt;p&gt;Looking into &lt;code&gt;ActionController::RequestForgeryProtection&lt;/code&gt; &lt;a href="https://github.com/rails/rails/blob/5-2-stable/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L257-L264"&gt;(source)&lt;/a&gt; reveals that for this exception to be raised, three conditions have to be met:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request forgery protection must be active (&lt;code&gt;protect_from_forgery&lt;/code&gt;, &lt;a href="http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html#method-i-protect_from_forgery"&gt;docs&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;The request must have been a non-XHR GET request.&lt;/li&gt;
&lt;li&gt;The response must have a content-type of &lt;code&gt;text/javascript&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we can’t really do anything about the first two points (disabling request forgery protection would be a no-go), but what about the third? Why does our response even have a content-type of &lt;code&gt;text/javascript&lt;/code&gt; when we’re rendering a perfectly normal HTML template?&lt;/p&gt;

&lt;p&gt;Turns out that if the response’s content-type is not explicitly set when rendering the template. Rails infers it based on the &lt;em&gt;requested format&lt;/em&gt;. And for URLs ending &lt;code&gt;.js&lt;/code&gt;, the requested format is &lt;code&gt;text/javascript&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This led to the obvious and easy fix:&lt;/p&gt;

&lt;h2&gt;The Fix&lt;/h2&gt;

&lt;p&gt;Simply add an explicit content-type to the action rendering the error template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PagesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;CmsController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="c1"&gt;# (unchanged)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@page&lt;/span&gt;
      &lt;span class="c1"&gt;# (unchanged)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;# Note the addition of `content_type: 'text/html'`:&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;file: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/public/404.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;layout: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;content_type: &lt;/span&gt;&lt;span class="s1"&gt;'text/html'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And voilà: Requesting non-existent JavaScript URLs no longer results in any exceptions, but just renders the 404 error page as expected.&lt;/p&gt;

&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    Your Rails app needs to be upgraded or extended? We’ve been working with Rails since the release of Rails 0.9.3 in 2005.
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Talk to us!&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Playing Audio over an USB Audio Interface on a Raspberry Pi from the Command-line</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2017-12-raspberry-pi-usb-audio-interface-command-line/"/>
    <id>https://die-antwort.eu/techblog/2017-12-raspberry-pi-usb-audio-interface-command-line/</id>
    <published>2017-12-14T00:00:00+01:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;The Raspberry Pi powering our status screen is also connected to our office doorbell: Everytime someone rings the bell, the Pi plays a short audio file.&lt;/p&gt;

&lt;p&gt;The Pi features an onboard audio output, but its quality is &lt;a href="https://raspberrypi.stackexchange.com/questions/3626/how-to-get-better-audio-quality-from-audio-jack-output"&gt;rather poor&lt;/a&gt;. So we decided to use an external USB audio interface instead. But how do we make the Pi use the external interface instead of its own audio output?&lt;/p&gt;

&lt;p&gt;You’ll find a lot of &lt;a href="https://www.woltersadvertising.com/how-to-set-default-raspberry-pi-sound-output-to-usb-device-on-raspbian-jessie-and-retropie/"&gt;posts&lt;/a&gt; &lt;a href="https://computers.tutsplus.com/articles/using-a-usb-audio-device-with-a-raspberry-pi--mac-55876"&gt;about&lt;/a&gt; &lt;a href="https://www.raspberrypi.org/forums/viewtopic.php?t=124016"&gt;this&lt;/a&gt;, most being targeted at older versions of Raspian and/or incomplete in other ways.&lt;/p&gt;

&lt;p&gt;Also, all these tutorials talk about changing the Pi’s default audio output. This may be necessary in some circumstances, but often it’s not: Insteads simply specify the desired output each time a command to play some audio is run, and boom – we don’t have to care about the defaults at all.&lt;/p&gt;

&lt;p&gt;Now let&amp;rsquo;s see how this works (note that this guide is for the current – November 2017 – version of &lt;a href="https://www.raspberrypi.org/downloads/raspbian/"&gt;Raspian Stretch Lite&lt;/a&gt;, but it should also work with Raspian Stretch Desktop).&lt;/p&gt;

&lt;h2&gt;Find the Correct “PCM Device”&lt;/h2&gt;

&lt;p&gt;The “PCM device” is the argument we’ll need to provide to &lt;code&gt;aplay&lt;/code&gt;, telling it which audio interface to use. How do we find it? Let’s have a look at the full list of devices &lt;code&gt;aplay&lt;/code&gt; knows about:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aplay &lt;span class="nt"&gt;-L&lt;/span&gt;

null
    Discard all samples &lt;span class="o"&gt;(&lt;/span&gt;playback&lt;span class="o"&gt;)&lt;/span&gt; or generate zero samples &lt;span class="o"&gt;(&lt;/span&gt;capture&lt;span class="o"&gt;)&lt;/span&gt;
default:CARD&lt;span class="o"&gt;=&lt;/span&gt;ALSA
    bcm2835 ALSA, bcm2835 ALSA
    Default Audio Device
sysdefault:CARD&lt;span class="o"&gt;=&lt;/span&gt;ALSA
    bcm2835 ALSA, bcm2835 ALSA
    Default Audio Device
dmix:CARD&lt;span class="o"&gt;=&lt;/span&gt;ALSA,DEV&lt;span class="o"&gt;=&lt;/span&gt;0
    bcm2835 ALSA, bcm2835 ALSA
    Direct sample mixing device
&lt;span class="o"&gt;[&lt;/span&gt;…]
default:CARD&lt;span class="o"&gt;=&lt;/span&gt;Device
    USB Audio Device, USB Audio
    Default Audio Device
sysdefault:CARD&lt;span class="o"&gt;=&lt;/span&gt;Device
    USB Audio Device, USB Audio
    Default Audio Device
front:CARD&lt;span class="o"&gt;=&lt;/span&gt;Device,DEV&lt;span class="o"&gt;=&lt;/span&gt;0
    USB Audio Device, USB Audio
    Front speakers
&lt;span class="o"&gt;[&lt;/span&gt;…]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One of the entries should look like &lt;code&gt;default:CARD=Device&lt;/code&gt; with an explanation like “USB Audio Device, Default Audio Device” next to is – that’s the one you’re looking for. Remember its name.&lt;/p&gt;

&lt;h2&gt;Play Sound from the Command-line&lt;/h2&gt;

&lt;p&gt;Once the correct PCM device is known, use &lt;code&gt;aplay&lt;/code&gt; to play a wave file over this interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aplay &lt;span class="nt"&gt;-D&lt;/span&gt; default:CARD&lt;span class="o"&gt;=&lt;/span&gt;Device doorbell.wav
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Adjusting the Volume from the Command-line&lt;/h2&gt;

&lt;p&gt;For adjusting the volume we’ll use &lt;code&gt;amixer&lt;/code&gt;. Unfortunately, you can’t use the same PCM device here – instead you have to provide the “card number”. This will most probably be “1” (“0” being the Pi’s internal audio device), but just try it out and check the device description:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amixer &lt;span class="nt"&gt;-c&lt;/span&gt; 1 info

Card hw:1 &lt;span class="s1"&gt;'Device'&lt;/span&gt;/&lt;span class="s1"&gt;'C-Media Electronics Inc. USB Audio Device at usb-3f980000.usb-1.5, full speed'&lt;/span&gt;
  Mixer name    : &lt;span class="s1"&gt;'USB Mixer'&lt;/span&gt;
  Components    : &lt;span class="s1"&gt;'USB0d8c:0014'&lt;/span&gt;
  Controls      : 9
  Simple ctrls  : 3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If the device description does not look like the audio interface you want to use, try &lt;code&gt;-c 2&lt;/code&gt; instead (and so on).&lt;/p&gt;

&lt;p&gt;Now we need to find out the correct “control” (most soundcards have multiple controls for things like “playback volume”, “microphone volume” etc.):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amixer &lt;span class="nt"&gt;-c&lt;/span&gt; 1 controls

&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3,iface&lt;span class="o"&gt;=&lt;/span&gt;MIXER,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Mic Playback Switch'&lt;/span&gt;
&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4,iface&lt;span class="o"&gt;=&lt;/span&gt;MIXER,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Mic Playback Volume'&lt;/span&gt;
&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7,iface&lt;span class="o"&gt;=&lt;/span&gt;MIXER,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Mic Capture Switch'&lt;/span&gt;
&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8,iface&lt;span class="o"&gt;=&lt;/span&gt;MIXER,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Mic Capture Volume'&lt;/span&gt;
&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9,iface&lt;span class="o"&gt;=&lt;/span&gt;MIXER,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Auto Gain Control'&lt;/span&gt;
&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5,iface&lt;span class="o"&gt;=&lt;/span&gt;MIXER,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Speaker Playback Switch'&lt;/span&gt;
&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6,iface&lt;span class="o"&gt;=&lt;/span&gt;MIXER,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Speaker Playback Volume'&lt;/span&gt;
&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2,iface&lt;span class="o"&gt;=&lt;/span&gt;PCM,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Capture Channel Map'&lt;/span&gt;
&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1,iface&lt;span class="o"&gt;=&lt;/span&gt;PCM,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Playback Channel Map'&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re interested in the “Speaker Playback Volume” control. Note that it’s named &lt;code&gt;numid=6&lt;/code&gt;. Let’s have a look at its current value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amixer &lt;span class="nt"&gt;-c&lt;/span&gt; 1 cget &lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6

&lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6,iface&lt;span class="o"&gt;=&lt;/span&gt;MIXER,name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Speaker Playback Volume'&lt;/span&gt;
  &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;INTEGER,access&lt;span class="o"&gt;=&lt;/span&gt;rw---R--,values&lt;span class="o"&gt;=&lt;/span&gt;2,min&lt;span class="o"&gt;=&lt;/span&gt;0,max&lt;span class="o"&gt;=&lt;/span&gt;37,step&lt;span class="o"&gt;=&lt;/span&gt;0
  : &lt;span class="nv"&gt;values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;16,16
  | dBminmaxmute-min&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-37&lt;/span&gt;.00dB,max&lt;span class="o"&gt;=&lt;/span&gt;0.00dB
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Parsing this we understand that this control has two integer values with an allowed range of 0 to 37. Currently both values are set to 16. Apparently this control lets us set the volume for both stereo channels separately, and currently the volume is set to about 50%.&lt;/p&gt;

&lt;p&gt;Now we know everything needed to construct a single command to set the volume to 100%:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amixer &lt;span class="nt"&gt;-c&lt;/span&gt; 1 cset &lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6 37,37
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This tells &lt;code&gt;amixer&lt;/code&gt; to set the volume control (&lt;code&gt;numid=6&lt;/code&gt;) of the second sound card (&lt;code&gt;-c 1&lt;/code&gt;) to the values &lt;code&gt;37, 37&lt;/code&gt; (100%).&lt;/p&gt;

&lt;h2&gt;TL;DR: Just Turn up the Volume and Play me a Sound!&lt;/h2&gt;

&lt;p&gt;Simply combine both commands from the previous sections:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amixer &lt;span class="nt"&gt;-c&lt;/span&gt; 1 cset &lt;span class="nv"&gt;numid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6 37,37&lt;span class="p"&gt;;&lt;/span&gt; aplay &lt;span class="nt"&gt;-D&lt;/span&gt; default:CARD&lt;span class="o"&gt;=&lt;/span&gt;Device doorbell.wav
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And this is exactly what we use in our &lt;a href="https://github.com/die-antwort/da-klingel/blob/c8329d0/config.example.yml#L20"&gt;doorbell application&lt;/a&gt;.&lt;/p&gt;

&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    Thinking about an IoT project, needing a prototype? We can do both, hardware and software.
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Let’s talk!&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Setup a Raspberry Pi to run a Web Browser in Kiosk Mode</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2017-12-setup-raspberry-pi-for-kiosk-mode/"/>
    <id>https://die-antwort.eu/techblog/2017-12-setup-raspberry-pi-for-kiosk-mode/</id>
    <published>2017-12-06T00:00:00+01:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;This guide provides a very lightweight setup for a Raspberry Pi in kiosk mode: Instead of stripping down a full desktop environment like &lt;a href="https://www.raspberrypi.org/blog/introducing-pixel/"&gt;PIXEL&lt;/a&gt; or &lt;a href="http://lxde.org"&gt;LXDE&lt;/a&gt;, we start without any GUI at all and install only the bare minimum needed to display a web browser in full screen.&lt;/p&gt;

&lt;h2&gt;Start With a Fresh Install of Raspian Lite&lt;/h2&gt;

&lt;p&gt;Download and install &lt;a href="https://www.raspberrypi.org/downloads/raspbian/"&gt;Raspbian &lt;strong&gt;Lite&lt;/strong&gt;&lt;/a&gt;. In contrast to Raspian Desktop, Raspian Lite has no desktop environment preinstalled and is generally much lighter and smaller in size.&lt;/p&gt;

&lt;p&gt;This guide is based on the November 2017 version of Raspian (&lt;em&gt;Raspbian Stretch Lite&lt;/em&gt;), but it should work for other versions, too.&lt;/p&gt;

&lt;p&gt;Boot up the Raspberry Pi, login as user &lt;code&gt;pi&lt;/code&gt; with password &lt;code&gt;raspberry&lt;/code&gt;, then start &lt;code&gt;sudo raspi-config&lt;/code&gt; to apply some initial customizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Localisation Options:&lt;/strong&gt; Select your preferred locale (we simply keep the default &lt;code&gt;en_GB.UTF-8&lt;/code&gt;), timezone, and keyboard layout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change User Password:&lt;/strong&gt; This is &lt;strong&gt;important&lt;/strong&gt; – keeping the default password means your Pi will get owned faster than you can say “botnet” as soon as you connect it to the internet. (Make sure to have selected the correct keyboard layout before typing in the new password, though.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network Options:&lt;/strong&gt; Configure WiFi as needed. Alternatively, you also &lt;a href="https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md"&gt;configure WiFi manually&lt;/a&gt;  using &lt;code&gt;wpa_passphrase&lt;/code&gt; if you don’t want your WiFi password stored on the Pi in clear text.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boot Options:&lt;/strong&gt; Select “Desktop / CLI” and then “Console Autologin”. We’ll come back to this later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interfacing Options:&lt;/strong&gt; Enable SSH access if needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advanced Options:&lt;/strong&gt; Disable “Overscan” if the Pi’s output does not fill your screen completely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now reboot the Pi. If everything was done correctly you should end up in a terminal session without having to enter your password.&lt;/p&gt;

&lt;p&gt;To conclude the initial setup, update all preinstalled packages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get upgrade
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Minimum Environment for GUI Applications&lt;/h2&gt;

&lt;p&gt;Usually the graphical environment for GNU/Linux consists of four parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;X server (usually &lt;a href="https://www.x.org/wiki/"&gt;X.Org&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Window manager (&lt;a href="http://openbox.org"&gt;Openbox&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Xfce#Xfwm"&gt;XFWM&lt;/a&gt;, …)&lt;/li&gt;
&lt;li&gt;Desktop environment (&lt;a href="https://www.raspberrypi.org/blog/introducing-pixel/"&gt;PIXEL&lt;/a&gt;, &lt;a href="http://lxde.org/"&gt;LXDE&lt;/a&gt;, &lt;a href="https://mate-desktop.org/"&gt;MATE&lt;/a&gt;, …)&lt;/li&gt;
&lt;li&gt;Login manager (for example &lt;a href="https://www.freedesktop.org/wiki/Software/LightDM/"&gt;LightDM&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, we only want to run a single application (the web browser) in full screen – so we don’t need a desktop environment. And we already have autologin enabled (and no other users will ever use the Pi) – so we don’t need a login manager either.&lt;/p&gt;

&lt;p&gt;The bare minimum we need are X server and window manager. Let’s install just that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; xserver-xorg x11-xserver-utils xinit openbox
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Web Browser&lt;/h2&gt;

&lt;p&gt;We’ll use &lt;a href="https://www.chromium.org/Home"&gt;Chromium&lt;/a&gt; because it provides a nice kiosk mode:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; chromium-browser
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Openbox Configuration&lt;/h2&gt;

&lt;p&gt;Now with everything in place, we can configure Openbox. Edit &lt;code&gt;/etc/xdg/openbox/autostart&lt;/code&gt; and replace its content with the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c"&gt;# Disable any form of screen saver / screen blanking / power management
&lt;/span&gt;&lt;span class="n"&gt;xset&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;
&lt;span class="n"&gt;xset&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;noblank&lt;/span&gt;
&lt;span class="n"&gt;xset&lt;/span&gt; -&lt;span class="n"&gt;dpms&lt;/span&gt;

&lt;span class="c"&gt;# Allow quitting the X server with CTRL-ATL-Backspace
&lt;/span&gt;&lt;span class="n"&gt;setxkbmap&lt;/span&gt; -&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;terminate&lt;/span&gt;:&lt;span class="n"&gt;ctrl_alt_bksp&lt;/span&gt;

&lt;span class="c"&gt;# Start Chromium in kiosk mode
&lt;/span&gt;&lt;span class="n"&gt;sed&lt;/span&gt; -&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="s1"&gt;'s/"exited_cleanly":false/"exited_cleanly":true/'&lt;/span&gt; ~/.&lt;span class="n"&gt;config&lt;/span&gt;/&lt;span class="n"&gt;chromium&lt;/span&gt;/&lt;span class="s1"&gt;'Local State'&lt;/span&gt;
&lt;span class="n"&gt;sed&lt;/span&gt; -&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="s1"&gt;'s/"exited_cleanly":false/"exited_cleanly":true/; s/"exit_type":"[^"]\+"/"exit_type":"Normal"/'&lt;/span&gt; ~/.&lt;span class="n"&gt;config&lt;/span&gt;/&lt;span class="n"&gt;chromium&lt;/span&gt;/&lt;span class="n"&gt;Default&lt;/span&gt;/&lt;span class="n"&gt;Preferences&lt;/span&gt;
&lt;span class="n"&gt;chromium&lt;/span&gt;-&lt;span class="n"&gt;browser&lt;/span&gt; --&lt;span class="n"&gt;disable&lt;/span&gt;-&lt;span class="n"&gt;infobars&lt;/span&gt; --&lt;span class="n"&gt;kiosk&lt;/span&gt; &lt;span class="s1"&gt;'http://your-url-here'&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;First we disable screen blanking and power management (we don’t want our screen to go blank or even turn off completely after some time).&lt;/p&gt;

&lt;p&gt;Then we allow to quit the X server by pressing &lt;kbd&gt;Ctrl&lt;/kbd&gt;-&lt;kbd&gt;Alt&lt;/kbd&gt;-&lt;kbd&gt;Backspace&lt;/kbd&gt;. (Because we didn’t install a desktop environment there won’t be a “Log out” button or the like.)&lt;/p&gt;

&lt;p&gt;Finally we tell Openbox to start Chromium in kiosk mode. This turns out to be a bit intricate because Chromium loves to show various tool bubbles for session restore etc. The simplest way to avoid all of these seems to be tricking Chromium into thinking it exited cleanly last time it was run (see &lt;a href="https://superuser.com/a/1206120"&gt;this answer on Super User&lt;/a&gt; for details).&lt;/p&gt;

&lt;p&gt;That’s it! Time to give it a try:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;startx &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-nocursor&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After a few seconds Chromium should appear showing the URL you specified. Oh, and as you might have guessed: The &lt;code&gt;-nocursor&lt;/code&gt; option tells X to not display any mouse cursor at all.&lt;/p&gt;

&lt;p&gt;Press &lt;kbd&gt;Ctrl&lt;/kbd&gt;-&lt;kbd&gt;Alt&lt;/kbd&gt;-&lt;kbd&gt;Backspace&lt;/kbd&gt; to quite the X server, bringing you back into the text console.&lt;/p&gt;

&lt;h2&gt;Start X automatically on boot&lt;/h2&gt;

&lt;p&gt;Now there’s only one thing left: The X server should start automatically on boot.&lt;/p&gt;

&lt;p&gt;Because we already configured the Pi to autologin the &lt;code&gt;pi&lt;/code&gt; user, we can use its &lt;code&gt;.bash_profile&lt;/code&gt; for starting X. Simply append the following line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nv"&gt;$DISPLAY&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$XDG_VTNR&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 1 &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; startx &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-nocursor&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The condition makes sure that X is only started on the first console (and if it isn’t already running). Because autologin uses the first console, this has the desired effect of automatically starting the X server (and thus the window manager and thus Chromium) on boot. And you can still use any of the other consoles for logging in manually.&lt;/p&gt;

&lt;p&gt;Reboot your pi to test if everything works as expected.&lt;/p&gt;

&lt;h2&gt;Usage tips&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If Chromium (or the X server) crashes, press &lt;kbd&gt;Ctrl&lt;/kbd&gt;-&lt;kbd&gt;Alt&lt;/kbd&gt;-&lt;kbd&gt;Backspace&lt;/kbd&gt; to kill the X server and restart it with &lt;code&gt;startx -- -nocursor&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you need a terminal session, you can switch to one of the other consoles by pressing &lt;kbd&gt;Ctrl&lt;/kbd&gt;-&lt;kbd&gt;Alt&lt;/kbd&gt;-&lt;kbd&gt;F2&lt;/kbd&gt; (or any other function key). Pressing &lt;kbd&gt;Ctrl&lt;/kbd&gt;-&lt;kbd&gt;Alt&lt;/kbd&gt;-&lt;kbd&gt;F1&lt;/kbd&gt; brings you back to the first console where Chromium is running.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    We do not only develop web applications, we also care about server setup, operations, and monitoring.
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Want to know more?&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>What we Discovered when we Deployed CSP</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2017-03-what-we-discovered-when-we-deployed-csp/"/>
    <id>https://die-antwort.eu/techblog/2017-03-what-we-discovered-when-we-deployed-csp/</id>
    <published>2017-03-24T00:00:00+01:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;We recently started using CSP (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"&gt;Content Security Policy&lt;/a&gt;) on some of our websites. The primary goal was to make the websites more secure, but along the way we also learned a lot about browser extensions making requests to adservers and other kinds of adware and malware.&lt;/p&gt;

&lt;h2&gt;CSP in a Nutshell&lt;/h2&gt;

&lt;p&gt;CSP works like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The website’s admin configures a whitelist of third party websites and resources that are legitimately used when visiting the website. This includes external CDNs, analytics services and such.&lt;/li&gt;
&lt;li&gt;Requests to any other host will be blocked by the browser. This mitigates attacks like &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting"&gt;XSS (Cross Site Scripting)&lt;/a&gt; or data injection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we deployed CSP on our sites we meticulously crafted the whitelists to include all legitimate resources. But we still get quite a lot of CSP violation reports – reports about requests to non-whitelisted hosts that have been blocked by the browser.&lt;/p&gt;

&lt;p&gt;These reports could be caused by attempted XSS or data injection attacks. After all, blocking these kind of attacks is exactly the purpose of CSP. However, we investigated our application logfiles and data and confirmed that no attacks had been attempted.&lt;/p&gt;

&lt;p&gt;This leaves us with the second cause for CSP violation reports: Browser extensions.&lt;/p&gt;

&lt;h2&gt;Browser Extensions causing CSP Violations&lt;/h2&gt;

&lt;p&gt;Here’s a breakdown of the top blocked domains according to the CSP violation reports we get.&lt;/p&gt;

&lt;figure class="chart-container"&gt;
  &lt;figcaption&gt;Top Blocked Domains According to our CSP Violation Reports&lt;/figcaption&gt;
  &lt;div class="chart" id="chart"&gt;&lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;All of these requests are caused by browser extensions, adware, or malware.&lt;/p&gt;

&lt;h2&gt;Analysis of Blocked Domains&lt;/h2&gt;

&lt;h3&gt;Innocuous&lt;/h3&gt;

&lt;p&gt;Some blocked requests are innocuous, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fonts.googleapis.com&lt;/code&gt; hosts Google Webfonts, these requests seem to originate from browser extensions like &lt;a href="https://chrome.google.com/webstore/detail/font-changer-with-google/jgjhhoglgjdklldfgoffdiaceffijeke"&gt;Font Changer with Google Web Fonts™&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxcdn.bootstrap.cdn&lt;/code&gt; is the &lt;a href="https://www.bootstrapcdn.com/"&gt;CDN for the Boostrap UI framework&lt;/a&gt;. This is likely to be caused by browser extensions relying on Bootstrap for their UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Adware&lt;/h3&gt;

&lt;p&gt;Other blocked hosts are part of ad delivery networks. These requests seem to be caused by adware or browser extensions behaving like adware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;secure.surfbuyermac.com&lt;/code&gt; relates to the &lt;a href="https://www.pcrisk.com/removal-guides/10643-surfbuyer-adware-mac"&gt;“SurfBuyer” adware&lt;/a&gt;, also known as “MyShopBot”, “My WebEnhancer”, “My ShopMate” and various other names. Once installed it “enables placement of third party graphical content on any site […] thereby significantly diminishing the web browsing experience.”&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lancheck.net&lt;/code&gt; and &lt;code&gt;cdncash.com&lt;/code&gt; are adservers used by &lt;a href="https://www.google.at/search?q=site:addons.mozilla.org+lancheck.net"&gt;several browser extensions&lt;/a&gt; like “&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/screengrab-fix-version/"&gt;Screengrab&lt;/a&gt;” or “&lt;a href="https://addons.mozilla.org/en-US/thunderbird/addon/s3google-translator/"&gt;S3.Google Translator&lt;/a&gt;”. These extensions also inject unrelated ads into the webpages you’re visiting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Malware&lt;/h3&gt;

&lt;p&gt;There’s a third group of blocked hosts that seem related to (or at least used by) malware like viruses, trojans, ransomware, scamware and such. For example, searching VirusTotal for &lt;a href="https://virustotal.com/en/domain/s.pmddby.com/information/"&gt;s.pmddby.com&lt;/a&gt; or &lt;a href="https://virustotal.com/en/domain/eluxer.net/information/"&gt;eluxer.net&lt;/a&gt; shows up a lot of malware files embedding these domains, including viruses like &lt;a href="https://www.microsoft.com/security/portal/threat/encyclopedia/Entry.aspx?Name=Virus:Win32/Sality"&gt;Win32/Sality&lt;/a&gt; and &lt;a href="https://www.microsoft.com/security/portal/threat/encyclopedia/Entry.aspx?Name=Virus:Win32/Dynamer!ac"&gt;Win32/Dynamer!ac&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Of course these domains are not &lt;em&gt;exclusively&lt;/em&gt; used by malware, so (blocked) connections to these hosts does not necessarily imply that your computer is infected.&lt;/p&gt;

&lt;h3&gt;Further Reading&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.nicolas-hoffmann.net/source/"&gt;Nicolas Hoffmann&lt;/a&gt; is putting together a comprehensive list of WTFs in CSP notifications on GitHub: &lt;a href="https://github.com/nico3333fr/CSP-useful/tree/master/csp-wtf"&gt;github.com/nico3333fr/CSP-useful/tree/master/csp-wtf&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Conclusio: Watch Your Browser Extensions!&lt;/h2&gt;

&lt;p&gt;Browser extensions are powerful and convenient tools. But they also might annoy you by displaying (even more) ads and popups, and some have even been found to &lt;a href="https://www.theregister.co.uk/2016/11/07/browsers_ban_web_of_trust_addon_after_biz_is_caught_selling_its_users_browsing_histories/"&gt;track and sell your complete browsing history&lt;/a&gt;. Be picky about the extensions you install in the first place, and remove any extensions you don’t really need.&lt;/p&gt;

&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    We take security seriously in everything we do. Need advice?
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Talk to us!&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;script src="/assets/javascripts/vendor/d3.v3.min-da3dcdda.js"&gt;&lt;/script&gt;&lt;script src="/assets/javascripts/techblog-articles/2017-03-csp-230fb816.js"&gt;&lt;/script&gt;

&lt;p&gt;&lt;link href="/assets/stylesheets/techblog-articles/2017-03-csp-e82b5000.css" rel="stylesheet" /&gt;&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Create an HTML archive of your Flowdock flows</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2017-03-create-an-html-archive-of-your-flowdock-flows/"/>
    <id>https://die-antwort.eu/techblog/2017-03-create-an-html-archive-of-your-flowdock-flows/</id>
    <published>2017-03-13T00:00:00+01:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;We’ve been using &lt;a href="https://www.flowdock.com"&gt;Flowdock&lt;/a&gt; for internal chat and organization since early 2010. However, during the last year(s) we noticed our usage patterns gradually shifting to other tools and services, and so we recently decided to cancel our Flowdock account. Of course we needed to somehow archive the content of our flows (that’s what chat channels are called in Flowdock) before everything was going to be deleted.&lt;/p&gt;

&lt;p&gt;As expected, Flowdock lets you export all your data, but this leaves you with large JSON files which aren’t particularly useful if you need to quickly find and re-read this one discussion from three years ago.&lt;/p&gt;

&lt;p&gt;So we decided to write a small Ruby script that takes the &lt;code&gt;messages.json&lt;/code&gt; file from a Flowdock export and converts it into a simple static HTML document. This file can be viewed in any browser, is easily searchable (by using &lt;code&gt;Ctrl-F&lt;/code&gt; / &lt;code&gt;Cmd-F&lt;/code&gt;) and even looks a bit like the original flow on Flowdock. Files and images that were posted in the flow are preserved, too (they are part of the archive you get when exporting a flow).&lt;/p&gt;

&lt;div class="media with-border"&gt;
  &lt;figure&gt;&lt;a href="/techblog/2017-03-flowdock-archive/screenshot-1f86acb1.png" data-lightbox="true" data-lightbox-group="gallery" data-lightbox-caption-allow-html="false"&gt;
    &lt;img src="/techblog/2017-03-flowdock-archive/screenshot.640gt.png" alt="The generated document looks similar to the original flow, including images." /&gt;&lt;/a&gt;&lt;figcaption&gt;The generated document looks similar to the original flow, including images.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;

&lt;h2&gt;Usage&lt;/h2&gt;

&lt;p&gt;The script outputs HTML to &lt;code&gt;stdout&lt;/code&gt; (and warnings, if any, to &lt;code&gt;stderr&lt;/code&gt;). Use it like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;flowdock-archive-to-html messages.json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; messages.html
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Including original usernames in the output&lt;/h2&gt;

&lt;p&gt;One gotcha of an exported flow is that it includes userids only, no usernames. By default, the HTML document produced by the script will therefore contain usernames like “User 123456”.&lt;/p&gt;

&lt;p&gt;If you prefer to have the original usernames instead you need to find out the names belonging to these ids. You can use the Flowdock API to &lt;a href="https://www.flowdock.com/api/users"&gt;list all users&lt;/a&gt;. Or, if you haven’t deleted your flows on Flowdock yet, you can also search for distinctive messages in both the generated HTML document and on Flowdock to cross-reference userids and usernames. Then simply add these mappings to the &lt;code&gt;@usernames&lt;/code&gt; hash at the beginning of the script.&lt;/p&gt;

&lt;p&gt;Pro tip: Run the script once to get a list of all userids in your flow (it will output a warning for each userid which is not in &lt;code&gt;@usernames&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;The complete script&lt;/h2&gt;

&lt;p&gt;The script is also available as a gist: &lt;a href="https://gist.github.com/noniq/8f40a8dccc02ac4062b3530561bdd507"&gt;gist.github.com/noniq/8f40a8dccc02ac4062b3530561bdd507&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;#!/usr/bin/env ruby&lt;/span&gt;

&lt;span class="c1"&gt;# Convert an exported Flowdock flow into a static HTML document.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Usage:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;#    flowdock-archive-to-html messages.json &amp;gt; messages.html&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# The script assumes that there is a subdirectory `files` containing all files referenced in the exported flow. (This is exactly the&lt;/span&gt;
&lt;span class="c1"&gt;# directory structure you get if you unzip an archive downloaded from Flowdock.)&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Only flow events of the types “message”, “comment”, and “file” are handled and thus included in the output. To include&lt;/span&gt;
&lt;span class="c1"&gt;# other events like “user-edit” or “mail”, add the appropriate formatting code to the huge `case` statement at the end of&lt;/span&gt;
&lt;span class="c1"&gt;# this file.&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bundler/inline"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"erb"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"json"&lt;/span&gt;

&lt;span class="n"&gt;gemfile&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"https://rubygems.org"&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"redcarpet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.3.0"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Add “userid =&amp;gt; username” mappings for all users you want to identify by name. Missing users will show up as “User 123456”.&lt;/span&gt;
&lt;span class="vi"&gt;@usernames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Flowdock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="no"&gt;HTML_HEADER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="sh"&gt;
  &amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
      &amp;lt;meta charset="utf-8"&amp;gt;
      &amp;lt;style type="text/css"&amp;gt;
      html {
        font-family: sans-serif;
        font-size: 14px;
        line-height: 1.5;
      }
      .message {
        display: flex;
        flex-direction: row;
        margin: 0 0 1em;
        padding: 0 0 1em;
        border-bottom: 1px solid #f4f4f4;
      }
      .message p {
        margin: 0;
        overflow-wrap: break-word;
      }
      .message pre {
        background: #eee;
        padding: 0.5em 1em;
        max-width: 100%;
        overflow: scroll;
      }
      .message img {
        max-width: 75%;
        max-height: 50vh;
      }
      .message blockquote {
        background: #f6f6f9;
        padding: 0.5em 1em;
        border-left: 3px solid #9898B0;
        margin: 0 0 1em;
        color: #2E2E75;
      }
      .date {
        flex: 0 14em;
        color: #999;
        font-size: 80%;
        text-align: right;
      }
      .user {
        flex: 0 5em;
        color: #999;
        font-weight: bold;
        text-align: right;
        margin-right: 1em;
      }
      .content {
        min-width: 0; /* To make `max-width: 100%` work in contained elements, see http://stackoverflow.com/a/31972181/566850 */
        flex: 1;
      }
      &amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;

&lt;span class="no"&gt;HTML_FOOTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="sh"&gt;
    &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;

&lt;span class="vi"&gt;@markdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redcarpet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Redcarpet&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Render&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;escape_html: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;hard_wrap: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;autolink: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;no_intra_emphasis: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;space_after_headers: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;h&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;ERB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;h&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sent'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@usernames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;fallback_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"User &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="vg"&gt;$stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: No username mapping for userid &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - using '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;fallback_username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' instead."&lt;/span&gt;
    &lt;span class="vi"&gt;@usernames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fallback_username&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="sh"&gt;
    &amp;lt;div class='message'&amp;gt;
      &amp;lt;div class='user'&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class='content'&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class='date'&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%H:%M – %b. %d, %Y'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;  HTML&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;HTML_HEADER&lt;/span&gt;
&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"messages.json"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"comment"&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"comment"&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt; \1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;render_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"file"&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"files/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;%r{&lt;/span&gt;&lt;span class="se"&gt;\A&lt;/span&gt;&lt;span class="sr"&gt;/files/&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+/}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;link_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="s2"&gt;"&amp;lt;img src='&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'&amp;gt;"&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"file_name"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;render_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;a href='&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;link_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/a&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"user-edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"mail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"open-invitation-enable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"discussion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"activity"&lt;/span&gt;
    &lt;span class="c1"&gt;# ignore&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="vg"&gt;$stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: Unknown event type in JSON data: `&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'event'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`"&lt;/span&gt;
    &lt;span class="vg"&gt;$stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;HTML_FOOTER&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    Need to transform, convert, or otherwise transmogrify data? Chances are that this can be done (better) programmatically.
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Talk to us!&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Transferring a Rails App to a new Server</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2017-02-transferring-a-rails-app-to-a-new-server/"/>
    <id>https://die-antwort.eu/techblog/2017-02-transferring-a-rails-app-to-a-new-server/</id>
    <published>2017-02-22T00:00:00+01:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;Sometimes a web app has to be transferred to a new server. Here’s the process we use (for Rails apps, but should work for other kinds of webapps, too).&lt;/p&gt;

&lt;p&gt;This guide makes the following assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app is served by &lt;a href="http://nginx.org/"&gt;nginx&lt;/a&gt; + &lt;a href="https://www.phusionpassenger.com/"&gt;Passenger&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The database is running on the same server (no replication setup).&lt;/li&gt;
&lt;li&gt;No zero downtime requirement (but the downtime should be kept minimal).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why do we need a downtime? Well, at some point we have to transfer all data (database contents, user generated files, …) from the old server to the new one. It is crucial that data is not being changed during or after this process.&lt;/p&gt;

&lt;p&gt;After transferring the data, the app’s DNS records will be updated to point to the new server. However, since DNS changes take some time to propagate, and we want to keep the downtime as short as possible, we’ll configure the old server to act as a reverse proxy for the app on the new server at this stage. This way your users can instantly access the app on the new server using the familiar URL, without waiting for their cached DNS entries to update.&lt;/p&gt;

&lt;p&gt;Let’s look into the details:&lt;/p&gt;

&lt;h2&gt;Preparation&lt;/h2&gt;

&lt;p&gt;Starting point: The new server is ready and accessible via &lt;code&gt;new.yourapp.com&lt;/code&gt;, but the app has not yet been deployed to it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add HTTP-Auth to the new server (or limit the access your IP address) – you don’t want anyone else to access the app on the new server yet.&lt;/li&gt;
&lt;li&gt;Deploy the app to the new server. Check if it works correctly if accessed via &lt;code&gt;new.yourapp.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make sure that the app on the new server works correctly even if accessed via its original URL &lt;code&gt;www.yourapp.com&lt;/code&gt;. You can do this eg. by &lt;a href="https://support.rackspace.com/how-to/modify-your-hosts-file/"&gt;temporarily modifying your hosts file&lt;/a&gt;. Don&amp;rsquo;t forget to undo these changes afterwards!&lt;/li&gt;
&lt;li&gt;Optional: Lower the TTL of the DNS record for &lt;code&gt;www.yourapp.com&lt;/code&gt; to something like 3600. This speeds up the propagation of the DNS update later. (But since we’re using a revere proxy setup, this is not strictly necessary.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Migration Steps&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Bring the application on the old server into maintenance mode (see below for details).&lt;/li&gt;
&lt;li&gt;Stop all background jobs. If the application has any cronjobs, deactivate them, too.&lt;/li&gt;
&lt;li&gt;Create a database dump on the old server and import it on the new server. Also, transfer all uploads and other user generated content to the new server.&lt;/li&gt;
&lt;li&gt;Install cronjobs on the new server (if any).&lt;/li&gt;
&lt;li&gt;Check that the app works as expected on the new server (no missing data or files etc).&lt;/li&gt;
&lt;li&gt;Remove HTTP-Auth (or IP address based access restrictions) from the app on the new server.&lt;/li&gt;
&lt;li&gt;Activate the reverse proxy on the old server (details below).&lt;/li&gt;
&lt;li&gt;Update the DNS record of &lt;code&gt;www.yourapp.com&lt;/code&gt; to point to new server (reset the TTL if you have lowered it previously).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! By activating the reverse proxy you made the app available again – only that it is now being served from the new server, even if users are still connecting to the old server because their cached DNS entries haven’t been updated yet.&lt;/p&gt;

&lt;h2&gt;Cleaning Up&lt;/h2&gt;

&lt;p&gt;You now have to wait long enough for the DNS change to propagate (this will depend on the DNS record’s TTL). When nobody is accessing the app via the reverse proxy on the old server anymore (check the nginx logfiles to be sure), you can take down the old server. Afterwards, you can also remove the DNS record for &lt;code&gt;new.yourapp.com&lt;/code&gt; (and the according entry in the nginx configuration on the new server).&lt;/p&gt;

&lt;h2&gt;Details, Details, Details …&lt;/h2&gt;

&lt;h3&gt;Activating Maintenance Mode&lt;/h3&gt;

&lt;p&gt;Maintenance mode ensures that no activity takes place that causes data (in the database or filesystem) to be changed. Think of it as “read-only mode” for your app.&lt;/p&gt;

&lt;p&gt;There are several ways to achieve this. One common way is to configure nginx to return a &lt;strong&gt;“503 Service Unavailable”&lt;/strong&gt; response code for every request. This way, requests won’t even hit the Rails application.&lt;/p&gt;

&lt;p&gt;Like with 404s, you can use a custom designed error page for these responses. Just be sure to have a file called &lt;code&gt;503.html&lt;/code&gt; in your document root (in case of a Rails app, that’s usually the &lt;code&gt;public/&lt;/code&gt; folder):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
  &lt;span class="n"&gt;server_name&lt;/span&gt;  &lt;span class="n"&gt;www&lt;/span&gt;.&lt;span class="n"&gt;yourapp&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;;

  &lt;span class="n"&gt;location&lt;/span&gt; / {
    &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;503&lt;/span&gt;;
  }

  &lt;span class="n"&gt;error_page&lt;/span&gt; &lt;span class="m"&gt;503&lt;/span&gt; @&lt;span class="n"&gt;offline&lt;/span&gt;;
  &lt;span class="n"&gt;location&lt;/span&gt; @&lt;span class="n"&gt;offline&lt;/span&gt; {
    &lt;span class="n"&gt;rewrite&lt;/span&gt; ^(.*)$ /&lt;span class="m"&gt;503&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;break&lt;/span&gt;;
  }

  &lt;span class="c"&gt;# … additional stuff snipped
&lt;/span&gt;}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To allow yourself to still access the application even in maintenance mode, simply wrap the  &lt;code&gt;return 503;&lt;/code&gt; in a conditional:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c"&gt;# Replace 1.2.3.4 with the IP of the computer you’re currently working on
&lt;/span&gt;&lt;span class="n"&gt;if&lt;/span&gt; ($&lt;span class="n"&gt;remote_addr&lt;/span&gt; != &lt;span class="s2"&gt;"1.2.3.4"&lt;/span&gt;) {
  &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;503&lt;/span&gt;;
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Final notice: Remember that there may be other processes causing data to change, like background jobs or cronjobs. Make sure to disable them as well!&lt;/p&gt;

&lt;h3&gt;Setting up a Reverse Proxy&lt;/h3&gt;

&lt;p&gt;The reverse proxy allows your users to use the app on the new server even while they are technically still connecting to the old server. The old server just forwards all requests to the new server, fetches the responses and then presents the response to the user. The user can not even tell that the response has not been generated by the old server itself. (Of course the forwarding adds some milliseconds to the total response time, but this effect is usually negligible.)&lt;/p&gt;

&lt;p&gt;To configure the old server as reverse proxy for the app on the new server, add the following lines to your nginx configuration (if you’ve already added a location block for maintenance mode, replace it now – there can only be one block for each location like &lt;code&gt;/&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
  &lt;span class="n"&gt;server_name&lt;/span&gt;  &lt;span class="n"&gt;www&lt;/span&gt;.&lt;span class="n"&gt;yourapp&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;;

  &lt;span class="n"&gt;location&lt;/span&gt; / {
    &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;new&lt;/span&gt;.&lt;span class="n"&gt;yourapp&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;;
    &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; &lt;span class="n"&gt;www&lt;/span&gt;.&lt;span class="n"&gt;yourapp&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;;
    &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Real&lt;/span&gt;-&lt;span class="n"&gt;IP&lt;/span&gt; $&lt;span class="n"&gt;remote_addr&lt;/span&gt;;
    &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;For&lt;/span&gt; $&lt;span class="n"&gt;proxy_add_x_forwarded_for&lt;/span&gt;;
    &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Forwarded&lt;/span&gt;-&lt;span class="n"&gt;Proto&lt;/span&gt; $&lt;span class="n"&gt;scheme&lt;/span&gt;;
  }

  &lt;span class="c"&gt;# … additional stuff snipped
&lt;/span&gt;}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of all these directives, only &lt;code&gt;proxy_pass&lt;/code&gt; is strictly required. However, the other directives are helpful, too: For example they ensure that the Rails logs contain the IP addresses of your user, not the IP of the old server (which is where the requests to the new server are coming from, technically). See the docs for &lt;a href="http://api.rubyonrails.org/classes/ActionDispatch/RemoteIp.html"&gt;ActionDispatch::RemoteIp&lt;/a&gt; for details.&lt;/p&gt;

&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    Need help transferring your Rails app to a new server?&lt;br&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Talk to us!&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Using CarrierWave with ActiveModel</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2016-09-using-carrierwave-with-active-model/"/>
    <id>https://die-antwort.eu/techblog/2016-09-using-carrierwave-with-active-model/</id>
    <published>2016-09-28T00:00:00+02:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;Recently I worked on a Rails application with some kind of contact form: Data gets entered, validated, and sent by mail to a predefined recipient. No need for persistence, so a simple &lt;a href="http://api.rubyonrails.org/classes/ActiveModel/Model.html"&gt;ActiveModel&lt;/a&gt; class is used:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# app/models/person.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:age&lt;/span&gt;
  &lt;span class="n"&gt;validates_presence_of&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The task was to extend the form with an upload field (the uploaded document would then be added as attachment to the mail). The upload should persist across form redisplays, so using a plain file input was not an option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/carrierwaveuploader/carrierwave"&gt;CarrierWave&lt;/a&gt; is a great solution for handling file uploads, and it also supports form redisplays. Normally it’s used with ActiveRecord or Mongoid models – but can we also make it work with a simple ActiveModel class?&lt;/p&gt;

&lt;p&gt;Turns out this is quite easy: We simply extend the model class with &lt;code&gt;CarrierWave::Mount&lt;/code&gt; and voilà, we now can use &lt;code&gt;mount_uploader&lt;/code&gt; as usual:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# app/models/person.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;

  &lt;span class="c1"&gt;# … (existing code snipped) …&lt;/span&gt;

  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;CarrierWave&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mount&lt;/span&gt;
  &lt;span class="n"&gt;mount_uploader&lt;/span&gt; &lt;span class="ss"&gt;:profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AttachmentUploader&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/uploaders/attachment_uploader.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AttachmentUploader&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;CarrierWave&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Uploader&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="ss"&gt;:file&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the appropriate changes to the form (don’t forget the &lt;a href="http://www.rubydoc.info/gems/carrierwave/frames#Making_uploads_work_across_form_redisplays"&gt;hidden “cache” field for form redisplays&lt;/a&gt; and &lt;code&gt;multipart: true&lt;/code&gt;), everything already works as expected.&lt;/p&gt;

&lt;h2&gt;Adding validations to the upload&lt;/h2&gt;

&lt;p&gt;Now, what if we want to allow only certain kinds of files? CarrierWave also &lt;a href="http://www.rubydoc.info/gems/carrierwave/CarrierWave/Uploader/ExtensionWhitelist#extension_white_list-instance_method"&gt;supports this&lt;/a&gt;, but the validations won’t work out of the box with ActiveModel. We have to manually require the validations module and explicitly activate the validations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# app/models/person.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'carrierwave/validations/active_model'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;

  &lt;span class="c1"&gt;# … (existing code snipped) …&lt;/span&gt;

  &lt;span class="n"&gt;validates_with&lt;/span&gt; &lt;span class="no"&gt;CarrierWave&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validations&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IntegrityValidator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;attributes: &lt;/span&gt;&lt;span class="sx"&gt;%i(profile)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/uploaders/attachment_uploader.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AttachmentUploader&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;CarrierWave&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Uploader&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

  &lt;span class="c1"&gt;# … (existing code snipped) …&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extension_white_list&lt;/span&gt;
    &lt;span class="sx"&gt;%w(pdf jpg)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that’s it! We now have an ActiveModel model supporting file uploads, including validations and form redisplay!&lt;/p&gt;

&lt;h2&gt;Complete code&lt;/h2&gt;

&lt;p&gt;Here’s the complete code for our model and uploader classes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# app/models/person.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'carrierwave/validations/active_model'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;CarrierWave&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mount&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:age&lt;/span&gt;
  &lt;span class="n"&gt;mount_uploader&lt;/span&gt; &lt;span class="ss"&gt;:profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AttachmentUploader&lt;/span&gt;
  &lt;span class="n"&gt;validates_presence_of&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
  &lt;span class="n"&gt;validates_with&lt;/span&gt; &lt;span class="no"&gt;CarrierWave&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validations&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IntegrityValidator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;attributes: &lt;/span&gt;&lt;span class="sx"&gt;%i(profile)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/uploaders/attachment_uploader.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AttachmentUploader&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;CarrierWave&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Uploader&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="ss"&gt;:file&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extension_white_list&lt;/span&gt;
    &lt;span class="sx"&gt;%w(pdf jpg)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    We offer Ruby on Rails consulting based on over 11 years of experience, going back to Rails 0.9.3 in 2005.
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Want to know more?&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Git Tricks for Maintaining a Long-Lived Fork</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2016-08-git-tricks-for-maintaining-a-long-lived-fork/"/>
    <id>https://die-antwort.eu/techblog/2016-08-git-tricks-for-maintaining-a-long-lived-fork/</id>
    <published>2016-08-05T00:00:00+02:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;Sometimes you have to maintain a long-lived fork of an external Git repository. For me, this happens for example with various TextMate bundles where I want to keep my local modifications, but still want to get all the features and bug fixes that are being implemented upstream, too.&lt;/p&gt;

&lt;p&gt;So you frequently merge upstream into your fork, and over time it gets harder and harder to keep track of the exact changes you made to the fork. Fortunately, there are some Git tricks to make things easier.&lt;/p&gt;

&lt;h2&gt;Listing Commits That Exist in the Fork Only&lt;/h2&gt;

&lt;p&gt;First of all, you can use &lt;code&gt;git log upstream/master..master&lt;/code&gt; to limit the log output to commits that exist in the fork only. (Technically, Git interprets a revision range in the form of &lt;code&gt;A..B&lt;/code&gt; as “all the commits that are reachable from B, but not from A”):&lt;/p&gt;

&lt;style type="text/css"&gt;
  span.s1 {color: #afad24}
  span.s2 {color: #ff40ff}
  span.s3 {color: #ccc}
  span.s4 {color: #5330e1}
  span.s5 {color: #34bd26}
&lt;/style&gt;

&lt;div class="terminal"&gt;&lt;pre&gt;&lt;code&gt;$ git log upstream/master..master
&lt;span class="s1"&gt;dc7741c&lt;/span&gt;&lt;span class="s2"&gt;&lt;b&gt; (HEAD -&amp;gt; master)&lt;/b&gt;&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(5 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;da81927&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(9 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;097c858&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(10 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;ff2ba96&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(11 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;1181de3&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(1 year, 2 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;20f285d&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(1 year, 3 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;6040ebf&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(1 year, 9 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;de1ac2b&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(1 year, 9 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;983c9f1&lt;/span&gt;&lt;span class="s3"&gt; Reset “encoding” snippet to upstream. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(1 year, 11 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;fbefd7f&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(1 year, 11 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;45799ef&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 1 month ago)&lt;/span&gt;
&lt;span class="s1"&gt;94f0ec8&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 2 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;9a05d0a&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 4 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;85f15be&lt;/span&gt;&lt;span class="s3"&gt; Merge remote-tracking branch 'upstream/master' &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;85f831d&lt;/span&gt;&lt;span class="s3"&gt; Use ^⌘E for “Execute Line / Selection as Ruby”. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;651b49c&lt;/span&gt;&lt;span class="s3"&gt; Make “Documentation for Word (APIDock)” visible again. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;63b14af&lt;/span&gt;&lt;span class="s3"&gt; Use ⇧^H for “Documentation for Word”. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;fc6ae6d&lt;/span&gt;&lt;span class="s3"&gt; Simplify 'encoding: utf-8' snippet, use 'enc' as trigger. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(3 years, 8 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;b246537&lt;/span&gt;&lt;span class="s3"&gt; Simplify do...end snippet. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(3 years, 8 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;79f0b06&lt;/span&gt;&lt;span class="s3"&gt; Fix invalid key binding that disables dead keys. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(3 years, 8 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;82fb660&lt;/span&gt;&lt;span class="s3"&gt; Make Ctrl-" toggle only between single and double quotes. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(4 years ago)&lt;/span&gt;
&lt;span class="s1"&gt;8352d50&lt;/span&gt;&lt;span class="s3"&gt; Remove Ctrl-Shift-w keybinding (should call 'Wrap Selection' even in Ruby mode) &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(4 years, 1 month ago)&lt;/span&gt;
&lt;span class="s1"&gt;6ddab40&lt;/span&gt;&lt;span class="s3"&gt; Make help command use APIDock. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(4 years, 2 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;1de19d1&lt;/span&gt;&lt;span class="s3"&gt; Add rules for literal symbol syntax (%i and %I). &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 8 months ago)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This is already much better, but still sprinkled with a lot of merge commits. Let’s try again with &lt;code&gt;--no-merges&lt;/code&gt;:&lt;/p&gt;

&lt;div class="terminal"&gt;&lt;pre&gt;&lt;code&gt;$ git log upstream/master..master --no-merges
&lt;span class="s1"&gt;983c9f1&lt;/span&gt;&lt;span class="s3"&gt; Reset “encoding” snippet to upstream. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(1 year, 11 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;85f831d&lt;/span&gt;&lt;span class="s3"&gt; Use ^⌘E for “Execute Line / Selection as Ruby”. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;651b49c&lt;/span&gt;&lt;span class="s3"&gt; Make “Documentation for Word (APIDock)” visible again. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;63b14af&lt;/span&gt;&lt;span class="s3"&gt; Use ⇧^H for “Documentation for Word”. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;fc6ae6d&lt;/span&gt;&lt;span class="s3"&gt; Simplify 'encoding: utf-8' snippet, use 'enc' as trigger. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(3 years, 8 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;b246537&lt;/span&gt;&lt;span class="s3"&gt; Simplify do...end snippet. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(3 years, 8 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;79f0b06&lt;/span&gt;&lt;span class="s3"&gt; Fix invalid key binding that disables dead keys. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(3 years, 8 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;82fb660&lt;/span&gt;&lt;span class="s3"&gt; Make Ctrl-" toggle only between single and double quotes. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(4 years ago)&lt;/span&gt;
&lt;span class="s1"&gt;8352d50&lt;/span&gt;&lt;span class="s3"&gt; Remove Ctrl-Shift-w keybinding (should call 'Wrap Selection' even in Ruby mode) &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(4 years, 1 month ago)&lt;/span&gt;
&lt;span class="s1"&gt;6ddab40&lt;/span&gt;&lt;span class="s3"&gt; Make help command use APIDock. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(4 years, 2 months ago)&lt;/span&gt;
&lt;span class="s1"&gt;1de19d1&lt;/span&gt;&lt;span class="s3"&gt; Add rules for literal symbol syntax (%i and %I). &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 8 months ago)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Excellent, now we have a succinct list of all the changes that have been made in the fork. Time for the next step.&lt;/p&gt;

&lt;h2&gt;Getting rid of Obsolete Changes&lt;/h2&gt;

&lt;p&gt;If your fork has existed for some time it is quite possible that some of its changes have become obsolete: Maybe in the meantime similar changes have been made upstream, or early changes were superseded by later changes in the fork. To keep track of the differences between fork and upstream it would be nice to somehow fix this up and retain only changes that are still relevant. Similar to an &lt;a href="https://git-scm.com/docs/git-rebase"&gt;interactive rebase&lt;/a&gt;, but without destroying the history.&lt;/p&gt;

&lt;p&gt;Turns out this is possible, too! The process consists of two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, you do a special merge to make the fork identical to upstream. At this point it seems like all of your changes have been lost, but they still are part of the fork’s history.&lt;/li&gt;
&lt;li&gt;Now you &lt;a href="https://git-scm.com/docs/git-cherry-pick"&gt;cherry pick&lt;/a&gt; those changes (only) that are still relevant, reapplying them to the fork.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First about this “special merge”: Git supports supports different &lt;a href="https://git-scm.com/docs/git-merge#_merge_strategies"&gt;merge strategies&lt;/a&gt;, one of them being &lt;code&gt;ours&lt;/code&gt;. Here’s how the manpage describes this strategy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[…] the resulting tree of the merge is always that of the current branch head, effectively ignoring all changes from all other branches. It is meant to be used to supersede old development history of side branches.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One caveat: Because this merge strategy ignores “all changes from all other branches”, we can’t use it on our master branch directly (unfortunately there is no strategy names &lt;code&gt;theirs&lt;/code&gt;). Instead, we will create a branch tracking &lt;code&gt;upstream/master&lt;/code&gt;, merge our master branch into it, and then fast-forward merge this branch back into master:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c"&gt;# Create a branch from upstream/master&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; reset-to-upstream upstream/master

&lt;span class="c"&gt;# Merge master into this branch, effectively ignoring all changes from master&lt;/span&gt;
git merge &lt;span class="nt"&gt;--strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ours master

&lt;span class="c"&gt;# Switch back to master and merge the temporary branch (will be a fast-forward merge)&lt;/span&gt;
git checkout master
git merge reset-to-upstream
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At this point, master and upstream/master are identical (&lt;code&gt;git diff upstream/master&lt;/code&gt; should be empty). Now let’s reapply the commits that are still relevant:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c"&gt;# Show the list of commits that exist only in the fork only. Pipe output to&lt;/span&gt;
&lt;span class="c"&gt;# cat to make sure output is stil available for copy-paste after the command&lt;/span&gt;
&lt;span class="c"&gt;# has exited.&lt;/span&gt;
git log upstream/master..master &lt;span class="nt"&gt;--no-merges&lt;/span&gt; | &lt;span class="nb"&gt;cat&lt;/span&gt;

&lt;span class="c"&gt;# Now just copy paste the commit hashes of the commits you want to keep:&lt;/span&gt;
git cherry-pick &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; …
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In my case I chose to reapply a single commit (&lt;code&gt;36a1b92&lt;/code&gt;), so the history now looks like this:&lt;/p&gt;

&lt;div class="terminal"&gt;&lt;pre&gt;&lt;code&gt;$ git log --graph --date-order
&lt;span&gt;* &lt;/span&gt;&lt;span class="s1"&gt;36a1b92&lt;/span&gt;&lt;span class="s2"&gt; (HEAD -&amp;gt; master, origin/master, origin/HEAD)&lt;/span&gt;&lt;span&gt; Remove ⇧^H from other commands. &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;span&gt;* &lt;span class="Apple-converted-space"&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span class="s1"&gt;e4d7137&lt;/span&gt;&lt;span class="s2"&gt; (reset-to-upstream)&lt;/span&gt;&lt;span&gt; Merge branch 'master' into reset-to-upstream &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(2 days ago)&lt;/span&gt;
&lt;span class="s5"&gt;|&lt;/span&gt;&lt;span class="s1"&gt;\&lt;/span&gt;&lt;span&gt; &lt;span class="Apple-converted-space"&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;* &lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class="s1"&gt;d5ed27f&lt;/span&gt;&lt;span class="s2"&gt; (upstream/master)&lt;/span&gt;&lt;span&gt; Add snippet to insert `${}` in template strings &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Michael Sheets&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(5 days ago)&lt;/span&gt;
&lt;span&gt;* &lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class="s1"&gt;91bb821&lt;/span&gt;&lt;span&gt; Change documentation tags to keyword.other.documentation &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Michael Sheets&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(6 days ago)&lt;/span&gt;
&lt;span&gt;* &lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class="s1"&gt;ce865b6&lt;/span&gt;&lt;span&gt; Do not allow documentation comments to start with `/***` &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Michael Sheets&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(6 days ago)&lt;/span&gt;
&lt;span&gt;* &lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span class="s1"&gt;f3426a8&lt;/span&gt;&lt;span&gt; Move interpolation and escapes into local repository &lt;/span&gt;&lt;span class="s4"&gt;&amp;lt;Michael Sheets&amp;gt; &lt;/span&gt;&lt;span class="s5"&gt;(6 days ago)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Technically, you could now delete the &lt;code&gt;reset-to-upstream&lt;/code&gt; branch. But it may be a good idea to keep it. Read on to see why.&lt;/p&gt;

&lt;h2&gt;Listing Commits Since the Last Cleanup&lt;/h2&gt;

&lt;p&gt;If you want to see all the commits in your fork &lt;em&gt;since the last cleanup&lt;/em&gt; only, you need to specify one more additional option to &lt;code&gt;git log&lt;/code&gt;:&lt;/p&gt;

&lt;div class="terminal"&gt;&lt;pre&gt;&lt;code&gt;$ git log upstream/master..master ^reset-to-upstream --no-merges
&lt;span class="s1"&gt;36a1b92&lt;/span&gt; &lt;span class="s2"&gt;(HEAD -&gt; master, origin/master, origin/HEAD)&lt;/span&gt; Remove ⇧^H from other commands. &lt;span class="s4"&gt;&amp;lt;Stefan Daschek&amp;gt;&lt;/span&gt; &lt;span class="s5"&gt;(2 years, 5 months ago)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Notice the &lt;code&gt;^reset-to-upstream&lt;/code&gt; option: This tells Git to also exclude all commits from the &lt;code&gt;reset-to-upstream&lt;/code&gt; branch, leaving us with the commits we reapplied after the merge only.&lt;/p&gt;

&lt;h2&gt;Repeating the Process in the Future&lt;/h2&gt;

&lt;p&gt;Chances are you want to repeat the cleanup process at some point in the future. This turns out quite simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c"&gt;# Checkout the reset branch and bring it up to date with upstream&lt;/span&gt;
git checkout reset-to-upstream
git merge upstream/master

&lt;span class="c"&gt;# Now just repeat the same process as described above&lt;/span&gt;
git merge &lt;span class="nt"&gt;--strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ours master
git checkout master
git merge reset-to-upstream
git log upstream/master..master ^reset-to-upstream &lt;span class="nt"&gt;--no-merges&lt;/span&gt; | &lt;span class="nb"&gt;cat
&lt;/span&gt;git cherry-pick &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; …
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;

&lt;p&gt;To read about all the ways of specifying revisions or revision ranges for Git commands see &lt;a href="https://git-scm.com/docs/gitrevisions"&gt;gitrevisions&lt;/a&gt;. Also, the &lt;a href="https://git-scm.com/book/en/v2"&gt;“Pro Git” book&lt;/a&gt; is freely available online and a great resource.&lt;/p&gt;

&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    Think you need to know more about Git to be more productive? Let’s do a few hours of Git training!
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Ask us!&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Why it is Important to dig in Deep</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2016-07-why-it-is-important-to-dig-in-deep/"/>
    <id>https://die-antwort.eu/techblog/2016-07-why-it-is-important-to-dig-in-deep/</id>
    <published>2016-07-28T00:00:00+02:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="https://www.skylight.io/"&gt;Skylight&lt;/a&gt; is a great service for monitoring and profiling Rails applications (we’re using it in several of our projects). Recently they published a blog post about &lt;a href="http://blog.skylight.io/rails-5-is-alive/"&gt;upgrading their application to Rails 5&lt;/a&gt;. Reading this post I stumbled about one particular detail that puzzled me. Here’s the story:&lt;/p&gt;

&lt;p&gt;One of the errors they faced during the upgrade was caused by this code (slightly simplified):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BlingBling&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CardError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :invalid_card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update_card&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="c1"&gt;# stuff&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="c1"&gt;# other stuff&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_card&lt;/span&gt;
    &lt;span class="c1"&gt;# yet moar stuff&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;invalid_card&lt;/span&gt;
    &lt;span class="n"&gt;render_errors&lt;/span&gt; &lt;span class="ss"&gt;card: &lt;/span&gt;&lt;span class="s2"&gt;"is invalid"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Line 2 raises &lt;code&gt;ArgumentError: unknown keyword: only&lt;/code&gt;. It looks like Rails 5 no longer supports this particular option for &lt;code&gt;#rescue_from&lt;/code&gt;. A quick glance at the &lt;a href="http://api.rubyonrails.org/v5.0.0/classes/ActiveSupport/Rescuable/ClassMethods.html#method-i-rescue_from"&gt;documention &lt;/a&gt; seems to confirm this: No mention of &lt;code&gt;only:&lt;/code&gt; (and none of &lt;code&gt;except:&lt;/code&gt; either, eventhough they are always used together).&lt;/p&gt;

&lt;p&gt;So the team at Skylight decided to work around this now missing option. It just takes a few additional lines of code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BlingBling&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="c1"&gt;# stuff&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="n"&gt;handle_invalid_card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# other stuff&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_card&lt;/span&gt;
    &lt;span class="n"&gt;handle_invalid_card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# yet more stuff&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_invalid_card&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
      &lt;span class="n"&gt;render_errors&lt;/span&gt; &lt;span class="ss"&gt;card: &lt;/span&gt;&lt;span class="s2"&gt;"is invalid"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Problem solved! Except for … that it’s not. In fact, this change introduces a bug. The updated coded behaves differently than the original code. To understand why, we need to dig in deeper.&lt;/p&gt;

&lt;h2&gt;Let’s Dig&lt;/h2&gt;

&lt;p&gt;First we take a look at the &lt;a href="http://api.rubyonrails.org/v4.2.0/classes/ActiveSupport/Rescuable/ClassMethods.html#method-i-rescue_from"&gt;documentation entry&lt;/a&gt; for &lt;code&gt;#rescue_from&lt;/code&gt; in the previous Rails version (4.2):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rescue exceptions raised in controller actions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rescue_from&lt;/code&gt; receives a series of exception classes or class names, and a trailing &lt;code&gt;:with&lt;/code&gt; option with the name of a method or a Proc object to be called to handle them. Alternatively a block can be given. […]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hmm, no mention of &lt;code&gt;:only&lt;/code&gt; or &lt;code&gt;:except&lt;/code&gt; here either, that’s strange. Maybe they had been deprecated? Let’s take a look at the &lt;a href="https://github.com/rails/rails/blob/f62fb985b6a7b90872148f0786219c9cc94c9356/activesupport/lib/active_support/rescuable.rb#L51"&gt;source&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;klasses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klasses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_options!&lt;/span&gt;

  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:with&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;
      &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:with&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Need a handler. Supply an options hash that has a :with key as the last argument."&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;klasses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt;
      &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;klass&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is neither an Exception nor a String"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# put the new handler at the end because the list is read in reverse&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rescue_handlers&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:with&lt;/span&gt;&lt;span class="p"&gt;]]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No, nothing: Even in Rails 4.2, &lt;code&gt;#rescue_from&lt;/code&gt; does not support the &lt;code&gt;:only&lt;/code&gt; or &lt;code&gt;:except&lt;/code&gt; options. But it doesn’t complain about them either. They are just silently ignored.&lt;/p&gt;

&lt;h2&gt;The Root of the Bug&lt;/h2&gt;

&lt;p&gt;To repeat: &lt;strong&gt;In Rails 4.2, using &lt;code&gt;:only&lt;/code&gt; or &lt;code&gt;:except&lt;/code&gt; together with &lt;code&gt;#rescue_from&lt;/code&gt; has no effect whatsoever.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that’s why the upgrade introduced a bug: In the original version of the code, eventhough it suggested otherwise, the &lt;code&gt;#rescue_from&lt;/code&gt; clause affected &lt;em&gt;all&lt;/em&gt; controller actions. After the upgrade, the clause now affects &lt;em&gt;some&lt;/em&gt; actions only.&lt;/p&gt;

&lt;p&gt;Of course this bug is not severe, and it may not be a problem at all. But it&amp;rsquo;s an unintended change of behaviour nonetheless. &lt;strong&gt;If at all possible, do not introduce behavioural changes when upgrading your code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;(Fun fact: One could argue that the original code already had a bug – the &lt;code&gt;#rescue_from&lt;/code&gt; should not have affected all actions –, and that by introducing another bug while incorrectly replicating the original behaviour this has now been fixed. Unintentional bugfixing ftw.)&lt;/p&gt;

&lt;h2&gt;Summary&lt;/h2&gt;

&lt;p&gt;Here’s what happened in a nutshell:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While upgrading the framework, a piece of code stopped working.&lt;/li&gt;
&lt;li&gt;The failing code was analysed to understand its behaviour.&lt;/li&gt;
&lt;li&gt;A workaround was introduced, implementing the very same behaviour in a way compatible with the framework’s new version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far, so good. However:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The analysis of the original code was incomplete.&lt;/li&gt;
&lt;li&gt;Thus the workaround was implemented based on a wrong understanding of the original behaviour.&lt;/li&gt;
&lt;li&gt;Consequently, the workaround now behaves differently than the original code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;How to not Introduce Behavioural Changes Inadvertently&lt;/h2&gt;

&lt;p&gt;Not introducing unintended behavioural changes requires you to to completely understand the current behaviour. In some cases (like in this example), code may behave differently from what it looks like at first glance. To develop a thorough understanding you need to dig in deep.&lt;/p&gt;

&lt;p&gt;And: Don’t be afraid to look at the source code of frameworks and libraries. It helps. Documentation may be incomplete our outdated, but the source code never is.&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;Post Scriptum: Option Hashes vs. Keyword Arguments&lt;/h2&gt;

&lt;p&gt;Are you wondering why the original code started to fail in Rails 5? Let’s dig into this, too. Here is the definition and first line of &lt;code&gt;#rescue_from&lt;/code&gt; in Rails 4.2:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# Rails 4.2&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;klasses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klasses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_options!&lt;/span&gt;
  &lt;span class="c1"&gt;# …&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The method uses an &lt;a href="http://ruby-doc.org/core-2.3.1/doc/syntax/methods_rdoc.html#label-Array-2FHash+Argument"&gt;array/hash argument&lt;/a&gt;: It accepts any number of arguments and stores it as an array in &lt;code&gt;klasses&lt;/code&gt; (let’s ignore the &lt;code&gt;&amp;amp;block&lt;/code&gt; argument for now):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="n"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# =&amp;gt; klasses is [Foo]&lt;/span&gt;
&lt;span class="n"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; klasses is [Foo, :bar]&lt;/span&gt;
&lt;span class="n"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;# =&amp;gt; klasses is []&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But what happens when we call the method like this: &lt;code&gt;rescue_from(Foo, with: bar)&lt;/code&gt;? Well, there’s this particular nice syntactic feature in Ruby:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The array argument will capture a Hash as the last entry if a hash was sent by the caller after all positional arguments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some examples to clarify:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="n"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;# =&amp;gt; klasses is [Foo, {with: :bar}]&lt;/span&gt;
&lt;span class="n"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :baz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;and: &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; klasses is [Foo, :bar, {with: baz, and: 123}]&lt;/span&gt;
&lt;span class="n"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;with: :bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;# =&amp;gt; klasses is [{with: :bar}]&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If the last argument looks like a hash, is is treated as such and added to the end of the array. The method then uses &lt;code&gt;#extract_options!&lt;/code&gt; (&lt;a href="http://api.rubyonrails.org/v4.2.0/classes/Array.html#method-i-extract_options-21"&gt;documentation&lt;/a&gt;) to extract this hash from the array. This is a commonly used idiom in Ruby.&lt;/p&gt;

&lt;p&gt;However, in Ruby 2.0 &lt;a href="http://ruby-doc.org/core-2.3.1/doc/syntax/methods_rdoc.html#label-Keyword+Arguments"&gt;keyword arguments&lt;/a&gt; were introduced. And &lt;code&gt;#rescue_from&lt;/code&gt; has been updated accordingly in Rails 5:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# Rails 5 (note the explicit keyword argument `with:`)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;klasses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# …&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the introduction of keyword arguments, the behaviour of array/hash arguments changed in one important way (emphasis mine):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The array argument will capture a Hash as the last entry if a hash was sent by the caller after all positional arguments.
&lt;strong&gt;However, this only occurs if the method does not declare any keyword arguments.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So because in Rails 5 &lt;code&gt;#rescue_from&lt;/code&gt; does declare &lt;code&gt;with:&lt;/code&gt; as keyword argument, it is no longer possible to call the method with any other hash-like/keyword arguments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="n"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :bar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# =&amp;gt; klasses is [Foo], with is :bar&lt;/span&gt;
&lt;span class="n"&gt;rescue_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :baz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;and: &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; ArgumentError: unknown keyword: and&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that’s why the original line of code started to fail in Rails 5:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CardError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: :invalid_card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update_card&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# =&amp;gt; ArgumentError: unknown keyword: only&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    We offer Ruby on Rails consulting based on over 11 years of experience, going back to Rails 0.9.3 in 2005.
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Want to know more?&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Save Your Future Self Some Debugging Time</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2016-07-save-your-future-self-some-debugging-time/"/>
    <id>https://die-antwort.eu/techblog/2016-07-save-your-future-self-some-debugging-time/</id>
    <published>2016-07-19T00:00:00+02:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;In a &lt;a href="/techblog/2016-07-dynamic-smtp-settings-in-action-mailer"&gt;previous article&lt;/a&gt; we implemented the following method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivering_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Dynamic settings are currently only used in production, but we want to check for a&lt;/span&gt;
  &lt;span class="c1"&gt;# valid sender domain in all environments to make sure we catch missing senders in the&lt;/span&gt;
  &lt;span class="c1"&gt;# development and test, too.&lt;/span&gt;
  &lt;span class="sr"&gt;/@(?&amp;lt;sender_domain&amp;gt;.+)/&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sender&lt;/span&gt;
    &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"No valid sender for determining dynamic SMTP settings: `&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sender&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`"&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:dynamic_smtp_settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dynamic_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dynamic_smtp_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sender_domain&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"No dynamic smtp settings configured for `&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sender_domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`"&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivery_method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dynamic_settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Being an &lt;a href="http://api.rubyonrails.org/classes/ActionMailer/Base.html#method-c-register_interceptor"&gt;ActionMailer interceptor&lt;/a&gt; means this method is automatically (implicitly) called every time a mailer method has prepared a message, right before the mail is going to be sent. Our method then looks at the sender address and sets the SMTP credentials accordingly.&lt;/p&gt;

&lt;p&gt;Why did we chose to have those two explicit safety checks in lines 6 and 10?&lt;/p&gt;

&lt;p&gt;When writing code we always try to ask ourselves: &lt;em&gt;“What are the most probable ‘errors’ in other parts of the application that could affect the code we’re currently writing?”&lt;/em&gt; (Note the quotation marks – we’re not talking about “real” errors here, but more about inadvertently ignoring assumptions that were made in other parts of the code.)&lt;/p&gt;

&lt;p&gt;For the interceptor method we came up with two cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having a mailer method not setting the sender at all (unlike the from address, the sender address is purely optional).&lt;/li&gt;
&lt;li&gt;Having a typo in the sender domain (either when setting the sender in the mailer method, or when specifying the dynamic settings in the configuration file).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Especially the first case is very likely to happen sooner or later, because the interceptor method introduces a new hidden requirement into the application: From now on, every mailer method has to set the message’s sender.&lt;/p&gt;

&lt;p&gt;In both error cases our method would fail with an exception. Before reading on: Can you determine the exact exception that would happen? (Hint: It’s not an immediately obvious one.)&lt;/p&gt;

&lt;p&gt;Here’s the answer: Both cases would lead to an &lt;em&gt;“TypeError: no implicit conversion of nil into Hash”&lt;/em&gt; exception in line 11:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First case: If &lt;code&gt;sender&lt;/code&gt; is not set, the regexp does not match, thus &lt;code&gt;sender_domain&lt;/code&gt; ends up being &lt;code&gt;nil&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Second case: A typo either in the sender address or in the configuration file means that &lt;code&gt;sender_domain&lt;/code&gt; is a string that does not match any key in the &lt;code&gt;dynamic_smtp_settings&lt;/code&gt; hash.&lt;/li&gt;
&lt;li&gt;So in both cases &lt;code&gt;dynamic_settings&lt;/code&gt; gets set to the result of accessing a non-existing key in the &lt;code&gt;dynamic_smtp_settings&lt;/code&gt; hash (line 9). That means it would simply be &lt;code&gt;nil&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Thus in line 11 we effectively call &lt;code&gt;.merge!(nil)&lt;/code&gt;, which results in the aforementioned TypeError.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine this happening at some point in the future were we may have long forgotten about the interceptor method: The error message “TypeError: no implicit conversion of nil into Hash” would not be helpful at all and it would take some time inspecting the stack trace to find out what really has happened.&lt;/p&gt;

&lt;p&gt;In his great book &lt;a href="http://exceptionalruby.com/"&gt;Exceptional Ruby&lt;/a&gt;, Avdi Grimm brings up &lt;em&gt;“five questions to ask yourself before writing code to raise an Exception”&lt;/em&gt;. The fifth question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;#5: Would continuing result in a less informative exception?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes failing to raise an exception just results in things going wrong in less easy-to-diagnose ways down the road. In cases like this, it’s better to raise earlier than later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is exactly why we chose to implement those two checks and to give them nice expressive failure messages, too. Someday our future selves will be really happy about that.&lt;/p&gt;

&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    Struggling with legacy code? We have more than 16 years of professional programming experience.
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Let’s pair up!&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title>Dynamic SMTP Settings in ActionMailer</title>
    <link rel="alternate" href="https://die-antwort.eu/techblog/2016-07-dynamic-smtp-settings-in-action-mailer/"/>
    <id>https://die-antwort.eu/techblog/2016-07-dynamic-smtp-settings-in-action-mailer/</id>
    <published>2016-07-13T00:00:00+02:00</published>
    <updated>2026-06-01T20:05:36+02:00</updated>
    <author>
      <name>Büro DIE ANTWORT</name>
    </author>
    <content type="html">&lt;p&gt;Imagine a single instance Rails application serving two different domain names, let’s say &lt;code&gt;foo.com&lt;/code&gt; and &lt;code&gt;bar.com&lt;/code&gt;. Some things should be handled differently depending on the domain used to access the application. For example transactional mails (notifications, password reset instructions, …) should have different sender addresses (e.g. &lt;code&gt;info@foo.com&lt;/code&gt; or &lt;code&gt;info@bar.com&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Originally, using different sender domains was no problem: ActionMailer was configured to send the mails via a local MTA (Postfix), which gladly accepted any sender address. But with spam filters starting to rely more and more on technologies like &lt;a href="#"&gt;SPF&lt;/a&gt; and &lt;a href="#"&gt;DKIM&lt;/a&gt; the effort of configuring, maintaining and monitoring the mail server was increasing steadily. After all you want to make sure that your password recovery mails aren’t classified as spam.&lt;/p&gt;

&lt;h2&gt;Switching to Mailgun&lt;/h2&gt;

&lt;p&gt;So we decided to switch to &lt;a href="http://www.mailgun.com/"&gt;Mailgun&lt;/a&gt; for increased deliverability and easier maintenance. In general, this is as simple as changing the SMTP credentials to the ones provided by Mailgun. However, in this case there was a gotcha:&lt;/p&gt;

&lt;p&gt;Mailgun requires you to use different SMTP credentials for each sender domain. But ActionMailer settings, including SMTP credentials, are stored in environment-specific configuration files (eg. &lt;code&gt;config/environments/production.rb&lt;/code&gt;) and loaded as part of the app’s initialization process. There is no obvious way of dynamically changing settings while the app is running.&lt;/p&gt;

&lt;h2&gt;Mail interceptors to the rescue&lt;/h2&gt;

&lt;p&gt;Enter mail interceptors: ActionMailer allows you to &lt;a href="http://api.rubyonrails.org/classes/ActionMailer/Base.html#method-c-register_interceptor"&gt;register interceptors&lt;/a&gt; that can modify messages before they are sent. An interceptor can be any class as long as it implements a &lt;code&gt;#delivering_email&lt;/code&gt; class method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# app/models/dynamic_smtp_settings_interceptor.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DynamicSmtpSettingsInterceptor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivering_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# do something with `message`&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course an interceptor also needs to be registered somewhere. Standard way of doing this is to use a Rails initializer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# config/initializers/dynamic_smtp_settings_interceptor.rb&lt;/span&gt;
&lt;span class="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_interceptor&lt;/span&gt; &lt;span class="s2"&gt;"DynamicSmtpSettingsInterceptor"&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Storing the credentials&lt;/h2&gt;

&lt;p&gt;Which credentials we need to use depends on the sender domain. To store these domain-dependent credentials we simply add a hash to the production environment’s configuration file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# config/environments/production.rb&lt;/span&gt;
&lt;span class="no"&gt;FooBar&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# … snip …&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dynamic_smtp_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"foo.com"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;user_name: &lt;/span&gt;&lt;span class="s2"&gt;"postmaster@mg.foo.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="s2"&gt;"(redacted)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"bar.com"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;user_name: &lt;/span&gt;&lt;span class="s2"&gt;"postmaster@mg.bar.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="s2"&gt;"(redacted)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;During runtime the settings can then be accessed with &lt;code&gt;Rails.configuration.dynamic_smtp_settings&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Setting SMTP credentials dynamically&lt;/h2&gt;

&lt;p&gt;Now that the credentials are configured and our interceptor is in place, how can we actually change the SMTP credentials dynamically? Luckily it turns out that the delivery settings (including the credentials) for each message are stored in the message’s Mail object instance. These settings are of course originally set according to ActionMailer’s configuration, but we can access and change them in the interceptor using  &lt;code&gt;message.delivery_method.settings&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DynamicSmtpSettingsInterceptor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivering_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivery_method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settings&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; {:address=&amp;gt;"smtp.mailgun.org",&lt;/span&gt;
                                     &lt;span class="c1"&gt;#     :port=&amp;gt;587,&lt;/span&gt;
                                     &lt;span class="c1"&gt;#     :authentication=&amp;gt;"login",&lt;/span&gt;
                                     &lt;span class="c1"&gt;#     :enable_starttls_auto=&amp;gt;true}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we have everything in place to implement the interceptor (comment present in the original source code, too):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DynamicSmtpSettingsInterceptor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivering_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Dynamic settings are currently only used in production, but we want to check for a&lt;/span&gt;
    &lt;span class="c1"&gt;# valid sender domain in all environments to make sure we catch missing senders in the&lt;/span&gt;
    &lt;span class="c1"&gt;# development and test, too.&lt;/span&gt;
    &lt;span class="sr"&gt;/@(?&amp;lt;sender_domain&amp;gt;.+)/&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sender&lt;/span&gt;
      &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"No valid sender for determining dynamic SMTP settings: `&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sender&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:dynamic_smtp_settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;dynamic_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dynamic_smtp_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sender_domain&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"No dynamic smtp settings configured for `&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;sender_domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`"&lt;/span&gt;
      &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivery_method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dynamic_settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that the regexp in line 6 contains a named capture: &lt;code&gt;(?&amp;lt;sender_domain&amp;gt;.+)&lt;/code&gt;. And because we’re using the &lt;code&gt;=~&lt;/code&gt; operator together with this regexp, we &lt;a href="http://ruby-doc.org/core-2.3.1/Regexp.html#method-i-3D-7E"&gt;automatically get a local variable&lt;/a&gt; with the same name: &lt;code&gt;sender_domain&lt;/code&gt;. The variable will contain the matched string or nil, if the regexp didn’t match. I personally find this way more expressive than using &lt;a href="http://ruby-doc.org/core-2.3.1/doc/globals_rdoc.html"&gt;&lt;code&gt;$1&lt;/code&gt;&lt;/a&gt; and friends.&lt;/p&gt;

&lt;p&gt;If you’re interested in why we decided to implement the specific runtime checks in lines 7 and 11 check out the article &lt;a href="/techblog/2016-07-save-your-future-self-some-debugging-time"&gt;Save Your Future Self Some Debugging Time&lt;/a&gt;.&lt;/p&gt;

&lt;hr&gt;

&lt;div class="notification is-primary is-engage"&gt;
  &lt;p&gt;
    &lt;i class="fas fa-lightbulb fa-2x"&gt;&lt;/i&gt;
  &lt;/p&gt;
  &lt;p class="subtitle is-5"&gt;
    We offer Ruby on Rails consulting based on over 11 years of experience, going back to Rails 0.9.3 in 2005.
  &lt;/p&gt;
  &lt;p&gt;
    &lt;a class="button is-white" data-piwik-goal="1" href="/contact"&gt;&lt;span&gt;Want to know more?&lt;/span&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;
</content>
  </entry>
</feed>
