• Saukko Stout Revisited

    Last weekend I took a trip to nostalgia land and cracked open a bottle of one of the first beers I ever made: Saukko Stout.

    Saukko Stout bottle and pint

    Saukko Stout was my third beer, brewed all the way back in February 2014 (bottled on March 2014). This means that it had been bottle conditioning for about 18 months at this point (and not in any particularly optimal setting).

    Considering its age and storage conditions (and the fact that it was one of my first beers), it was actually pretty decent. I’m not sure I’ll be rushing off to brew another gyle of it, but it was eminently drinkable and enjoyable. Definitely far better than the bottle of Ilves IPA I had back in January at any rate…

    PS. Check out my free iOS apps Beer Styles 2 and Piranhas for iOS

  • Beer Styles 2

    Beer Styles 2 has been available in the App Store for a few days now. Beer Styles 2 is a big update and has several swell improvements, especially for iOS 9 devices.

    BJCP 2015 and 2008 guidelines

    2015 and 2008 guideline switcher

    Just like the previous version this includes both the 2008 and 2015 guidelines (and quite a few small formatting and typo corrections).

    Slide Over and Split View

    On iOS 9 you can use Slide Over and Split View on iPads to multitask while browsing style guidelines. Very handy if you’re trying to build a recipe in one app while referring to the style guideline!

    Beer Styles in a Split View

    Spotlight search integration

    Beer Styles now has Spotlight integration so you can search for style guidelines without having to first launch the app. Just swipe down on the home screen…

    Beer Styles Spotlight search

    Nicer sharing

    Sharing individual styles is now nicer because your friends can now see the same view as you do, even if they don’t have Beer Styles installed or if they don’t even have an iOS device.

    Beer Styles on the web

    Still free

    There’s still one thing that hasn’t changed: Beer Styles is still completely free!

    Download on the App Store

    PS. If you like Beer Styles (or even if you don’t), you might like to try my latest iOS app: Piranhas for iOS. Piranhas is a free book price comparison tool for the web and iOS.

    Oh, and if you like using Beer Styles, a review on the App Store would be highly appreciated…

  • Piranhas for iOS

    I’m very excited to announce that there’s now a native iOS app for Piranhas and it’s available on the App Store for free! It has all the features of the web version plus barcode scanning.

    Piranhas for iOS on an iPad and iPhone 6s


    • Scan ISBN barcodes from books
    • Supports Split View and Slide Over on iOS 9 iPads
    • Supports iOS 9’s new in-app browser
    • Shipping charge estimates
    • Automatic currency conversions

    Supported stores:

    • Amazon.com (United States)
    • Amazon.co.uk (United Kingdom)
    • Amazon.ca (Canada)
    • Amazon.de (Germany)
    • Amazon.es (Spain)
    • Amazon.fr (France)
    • Book Depository
    • Wordery

    Download on the App Store

  • Beer Styles 1.1.0 + BJCP 2015

    Version 1.1.0 of Beer Styles just hit the iOS App Store. The biggest new feature is that the BJCP 2015 Style Guidelines are now included!

    Beer Styles 1.1.0 on an iPad and iPhone 6

    The 2015 and 2008 beer, cider, and mead style guidelines from the Beer Judge Certification Program (BJCP).

    The style guidelines describe what each style of beer, cider, and mead should taste, look, and smell like.

    The guidelines include vital stats such as OG (original gravity), FG (final gravity), IBU (bitterness), SRM (colour), and ABV readings as well as descriptions for aroma, appearance, flavour, and mouth feel.

    In Beer Styles all the vital stats have been given a visual representation to make it easier to fathom how differences in styles.

    Download on the App Store

  • Bad Tidings

    Bad Tidings is a beer made from a bunch of leftover malts I had accumulated. I decided to try for a Belgian style beer largely because the Belgians seem to have a pretty relaxed attitude about what adjuncts they use, plus it gave me a chance to try out the Fermentis T-58 (PDF link) yeast.

    I chose the name Bad Tidings because I wanted to invoke a certain sense of dread or foreboding with the name (as it was unsure whether the beer would turn out to be any good at all).

    For this label I went with a bit of a literary theme because Bad Tidings sounds like the title of an 18th or 19th century novel in my head. The sole typeface is Adobe Caslon Pro, chosen mostly of how it looks but also because Caslon seemed to be historically accurate for the 18th century.

    “Practically all American and English printing from 1735 to 1800 was done using the Caslon font.” (source)

    As for the beer itself, the small sample I tasted while bottling seemed pretty good but I’ll have to wait a couple of weeks for it to bottle condition before rendering a final verdict.

    Bad Tidings label

  • The Summoning Dark

    The Summoning Dark is my first Black IPA, it’s named after a character in Thud!, a book by Terry Pratchett (if you can call a pan-dimensional creature and spirit of vengeance a character).

    For the main graphic of the label I used the mine sign rune for the Summoning Dark (described in the book as “a floating eyeball with a curly tail”). The beer name itself is in a rune-like typeface. The labels are stamped with my brewery stamp.

    The beer itself came out quite well, though on the brew day I worried about the colour coming out too light, but that turned out to be a false alarm in the end.

    As an attempt to minimise the “roasty” flavours from the Carafa Special II, instead of mashing it with the rest of the grains, I cold steeped it overnight in about two litres of water. I filtered out the malt from the water and added it to the wort before beginning my boil.

    In the end I think the beer got approximately the right about of the roasted flavours, but I it could have been more heavily hopped (especially in terms of aroma).

    The Summoning Dark

  • An improved email address validator

    For the last couple of years we’ve mostly used Devise’s extremely simple email address regex to validate email addresses. After all, the real proof of whether an address is correct is if the confirmation email gets through to the end user…

    # Email regex used to validate email formats. It simply asserts that
    # one (and only one) @ exists in the given string. This is mainly
    # to give user feedback and not to assert the e-mail validity.
    config.email_regexp = /\A[^@]+@[^@]+\z/

    If you haven’t read it already, you should read David Celis’s Stop Validating Email Addresses With Regex blog post for a more detailed explanation of why you shouldn’t validate email addresses with regexes.

    The downside of this regex is that it can and will let through email addresses that raise Mail::Field::ParseError exceptions once Mail actually tries to parse them.

    For example, some non-latin characters (such as Ä, Ö, or Å) cause explosions:

    re = /\A[^@]+@[^@]+\z/
    email_address = "matti.meikäläinen@example.com"
    puts "Valid" if email_address =~ re
    #=> Valid
    Mail::Address.new email_address
    # Mail::Field::ParseError: Mail::AddressList can not parse |matti.meikäläinen@example.com|
    # Reason was: Only able to parse up to matti.meikä

    Obviously if Mail can’t parse the email address the email won’t be sent to the user…

    My latest approach is to combine the Devise regular expression validation with an additional check that the Mail library can actually parse the user supplied address:

    # app/validators/email_validator.rb
    class EmailValidator < ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        email = Mail::Address.new(value)
        unless email.address =~ Devise.email_regexp
          record.errors.add(attribute, :invalid_email)
      rescue Mail::Field::ParseError => e
        record.errors.add(attribute, :invalid_email)

    If you’re wondering why we need to check that the email address both passes the regex and Mail::Address.new(…), it’s because Mail will happily accept a string such as foo as a valid address:

    email = Mail::Address.new "foo"
    # => #<Mail::Address:2309836100 Address: |foo| >
    # => "foo"

    It would also be possible to pass Mail’s error message back to the user to give her a more detailed explanation of why the email address looks invalid, though this may be more difficult if your app uses internationalisation (as is the case with many of our apps).

    If you enjoyed this, you might enjoy learning more about why emails are generally pretty terrible.

  • Milwaukee MA871

    My latest beer making toy was delivered today: a Milwaukee MA871 digital refractometer.

    I briefly tested it on a couple of samples of water and sugar water and the experience was definitely nicer than trying to peer at the scale through my (admitedly cheap) optical refractometer.

    While perhaps not exactly a necessity over hydrometers or optical refractometers, I’m still really excited to get to use this in anger.

  • Capistrano 3: Deploying via a gateway

    At Kisko some of our projects have production environments with IP restrictions, meaning that we can access the servers from our office but not from any random IP address.

    Despite this, it is handy and perhaps even necessary that we can deploy and access the servers when working remotely or if an emergency crops up when we’re away from the office. Using a VPN or SOCKS proxy is one option, but luckily it is also pretty easy to configure Capistrano 3 to deploy via a bastion or gateway host.

    Set-up Capistrano as your normally would, then add this to your deploy.rb:

      require 'net/ssh/proxy/command'
      # Use a default host for the bastion, but allow it to be overridden
      bastion_host = ENV['BASTION_HOST'] || 'bastion.example.com'
      # Use the local username by default
      bastion_user = ENV['BASTION_USER'] || ENV['USER']
      # Configure Capistrano to use the bastion host as a proxy
      ssh_command = "ssh #{bastion_user}@#{bastion_host} -W %h:%p"
      set :ssh_options, proxy: Net::SSH::Proxy::Command.new(ssh_command)

    Note: Remember to replace the bastion hostname with your own…

    Now when your not deploying from your regular IP address, you can simply set the VIA_BASTION environment variable to deploy via the bastion host. For example:

    VIA_BASTION=1 cap production deploy

    And that’s it!

  • Brewery stamp

    I designed and bought a stamp for my home “brewery” (known as Baltic Sea Brewing).

    Why? Mostly because I could and wanted to…