Saying stuff about stuff.

Introducing Parklife

Parklife turns any Rack app (Rails, Sinatra, Hanami, Roda, Camping) into a static HTML build ready to be hosted on GitHub Pages, Netlify, Now, S3, or any other web server.

The great thing is that your development flow remains completely unchanged – keep working with the frameworks you already know and love and all those other gems you favour, keep running the usual development server, and keep writing unit and browser tests. Then when it comes to production, instead of deploying and managing a server-side Ruby app you generate a static build that can live indefinitely with no maintenance cost.

While Parklife works great with Rails – this site is a 15+ year old Rails app using SQLite/Paperclip/Sprockets+Sass/Parklife and hosted on Netlify – where I’ve really felt its impact is with Sinatra. Now you can use Sinatra’s renowned rapid and minimal server-side development flow to build a static production site – I feel like it’s fully unlocked Sinatra’s microbenefits and installed it in its rightful place as monarch of the microframeworks. So now there’s no reason not to use Ruby for those little ad hoc microsites that might otherwise have ended up using some other complicated technology – or maybe even not be created at all.

Parklife doesn’t start an actual web server and instead interfaces directly with your app via Rack by sending mock HTTP requests and writing the response body to disk – this is what allows it to work with any Rack-compatible framework. It can also detect and follow links to crawl an entire site so you can often get away with only configuring a starting root route.

It’s easy to add Parklife to your app, start by installing the gem and then run parklife init --sinatra --github-pages to generate a starter Parkfile configuration file (tailored for a Sinatra app), a build script, and a full GitHub Actions workflow to generate and deploy your site to GitHub Pages whenever you push to the main branch. It’s also not a one-way trip because at any point in the future you can choose to set sail and return to a life on the server-side seas – you can’t lose.

For me Parklife has somehow managed to roll back the decades to bring simplicity back to making websites. I’ve once again felt that je ne sais quoi immediacy of writing HTML in Dreamweaver and syncing it over FTP… Only this time I get to use today’s Ruby, today’s frameworks, and today’s CI and hosting providers.

Git aliases that make my life easier

Before thinking about aliases the best thing you can do to make git nicer to use is configure tab completion. If you’re using Homebrew follow their shell completion docs otherwise you’re on your own – but do it! The great thing about git aliases is that they’re also tab completable.

Here are the git aliases I use all the time, add them to your ~/.gitconfig:

[alias]
  browse = !gh browse
  co = checkout
  recent = branch --sort=-committerdate
  st = status
  touch = commit --amend --date=now --no-edit

First of all git co and git st are a throwback to my SVN transition days but they really should come as default – on unfamiliar systems I can’t believe how much of an impediment it is having to type the whole of git status each time.

I’ve only just switched from hub (which seems to be very nearly deprecated) to GitHub’s “official” command line tool. Hub’s browse command was pretty much the only thing I used and although gh also comes with its own browse command my muscle memory is just too ingrained to be able to switch. Fortunately adding a preceding ! will execute the alias as an external shell command so now I can type git browse and it will run gh browse for me. It’s not me it’s you.

Focus and deep work are an imperative to achieving quality and continual progress but sometimes reality hits and you find yourself working across many branches at the same time. Now what was the name of that branch I was working on the other day 🤔 This is an example of a feature that’s already part of git but only if you know the magic incantation – it’s git branch --sort=-committerdate or git recent with this alias.

I use git touch more than I should admit but something in me needs the commit’s date to represent the real date I finished the commit (even though squash and merge will have its way eventually). FYI you can pass any date and use --amend for evil 😈 “Yes I really was working late last night” git commit --amend --date=2023-01-23T23:34:51 --no-edit 😴

One more bonus alias that’s useful but I rarely use:

[alias]
  graph = log --oneline --graph

This is less of a graph nowadays when everything’s squashed and merged 😞 but I find being able to visually navigate the git tree really useful. Again this is already a part of git if you can remember where to find it but with an alias it’s a single tab-completable command away. The nice thing with all of these is that you can pass further options and they’re appended to the alias, so with this you can include all local branches with git graph --all or all remote branches with git graph --remotes (again tab completion came to my rescue – even through the alias – because I didn’t remember if it was --remote singular).

Announcing Operatic

Operatic defines a minimal standard interface to encapsulate your Ruby operations (often referred to as “service objects”). The job of Operatic is to receive input and make it available to the operation, and to gather output and return it via a result object, this leaves you a well-defined space to write the actual code by implementing the #call method. And with so much of the ceremony taken care of it feels like writing a function but in a Ruby wrapping.

Creating an Operatic operation

To define an operation include Operatic and implement the instance method #call, data can be attached to the operation’s Operatic::Result object:

class SayHello
  include Operatic

  def call
    result[:message] = 'Hello world'
  end
end

The operation is called with the class method .call and the result object is returned with its attached data, the result is either a #success? or a #failure?:

result = SayHello.call
result.success?  # => true
result.failure?  # => false
result[:message] # => "Hello world"

Data can be passed via keyword arguments that are set as instance variables on the operation and can be made available as methods with a standard Ruby attr_reader:

class SayHello
  include Operatic

  attr_reader :name

  def call
    result[:message] = "Hello #{name}"
  end
end

result = SayHello.call(name: 'Dave')
result.success?  # => true
result.failure?  # => false
result[:message] # => "Hello Dave"

An operation can specifically mark its result as a success or failure using #success!/#failure! (which are convenience delegates to the Operatic::Result’s counterparts) and attach data at the same time:

class SayHello
  include Operatic

  attr_reader :name

  def call
    return failure! unless name

    success!(message: "Hello #{name}")
  end
end

result = SayHello.call(name: 'Dave')
result.success?  # => true
result.failure?  # => false
result[:message] # => "Hello Dave"

result = SayHello.call
result.success?  # => false
result.failure?  # => true
result[:message] # => nil

Once the result is returned or has been marked as a success/failure it’s frozen and any further attempt at modification will raise a FrozenError. This helps to ensure that even a complicated operation with many code paths can only finalise its result once:

result = Operatic::Result.new
result.success!
result.success!
# => #<FrozenError: can't modify frozen Operatic::Result: #<Operatic::Result:0x00000001130c0710 @data={}, @success=true>>

Using Operatic in Rails

A Rails controller might use Operatic like this:

class HellosController < ApplicationController
  def create
    result = SayHello.call(name: params[:name])

    if result.success?
      render plain: result[:message]
    else
      render :new
    end
  end
end

A pattern matching future

Where I think Operatic gets interesting is in being able to pattern match against an Operatic::Result (in Ruby 2.7+) allowing you to think of it as a standardised tuple – an array of its success status and data – instead of an object with methods.

Here’s the Rails example rewritten to use pattern matching, I think it’d be fair to describe it as dense (particularly if you’re unfamiliar with pattern matching) though I’d prefer to call it succinct. But it’s doing quite a lot by allowing you to declare both the expected status and the shape of the data, and to extract certain keys, all at the same time:

class HellosController < ApplicationController
  def create
    case SayHello.call(name: params[:name])
    in [true, { message: }]
      render plain: message
    in [false, _]
      render :new
    end
  end
end

In summary

Operatic defines a standard interface for your operations and takes care of input and output leaving you to concentrate on writing code.

You can find the source on GitHub.

The Return of Screamshot

Screamshot is an HTTP screenshot service I first released back in 2016. Although light on features it served its purpose before suffering from the usual bitrot, but I’ve finally resurrected the project and updated it to meet my current needs.

The biggest change is that Screamshot now uses Chrome. Back in 2016 I found PhantomJS to be the most convenient headless browser to run — especially on Heroku — but headless browsers have moved on greatly over the intervening years and it was deprecated soon after the release of headless Chrome in mid 2017.

I’ve added some options that were missing from the previous version like being able to specify the size of the window/viewport, the scale of the image to capture (retina/devicePixelRatio), and whether to capture the full page. But my favourite addition is prefers-reduced-motion=reduce which can be used as a standards-based hint to the page to disable animations that might otherwise be visually problematic in a static screenshot. Screenshot services often include a delay option that can be used (or abused) to wait for animations to finish but if you support prefers-reduced-motion=reduce in your pages then you can prevent animations from running in the first place which not only makes taking a screenshot easier but can also aid accessibility — win win.

Another big difference is that I’ve added a much more comprehensive test suite which will make the project easier to maintain and help prevent further bitrot. The app now includes a set of tests that exercise the entire stack and verify that everything is working as expected by checking a few key pixels in the captured images.

As ever the source is on GitHub and pull requests are welcome. It’s a surprisingly small amount of code for the functionality it provides but that’s because it rests on a heap of great open source libraries like ferrum, rack, sinatra, and capybara to name just a few.

Supporting fractions in Ruby/Rails

Unicode comes with a selection of common single-character fractions like ½, ¼, and even ⅐ (see en.wikipedia.org/wiki/Number_Forms for more), but I need to store and display somewhat arbitrary fractions in a Rails app and it isn’t something I’ve had to do before so I’m documenting my workings.

Ruby has the Rational class that makes it easy to work with fractions and can perform a specific calculation I need (½ + ⅐ = ⁹⁄₁₄):

a = Rational(1, 2)
# => (1/2)

b = Rational(1, 7)
# => (1/7)

a + b
# => (9/14)

My numbers are stored in a PostgreSQL database which doesn’t support rationals but fortunately in Ruby any number can be turned into a Rational with #to_r:

0.5.to_r
# => (1/2)

Unfortunately you don’t often get a sensible fraction:

(9.0 / 14).to_r
# => (5790342378047781/9007199254740992)

A Rational can be simplified with Rational#rationalize and with a bit of trial and error I determined that to arrive at ⁹⁄₁₄ I need to store the number with 3 decimal places and pass a magic number of ~0.005 to Rational#rationalize:

0.643.to_r
# => (2895814560399229/4503599627370496)

0.643.to_r.rationalize(0.1)
# => (2/3)

0.643.to_r.rationalize(0.01)
# => (7/11)

0.643.to_r.rationalize(0.001)
# => (9/14) ✅

# Can I store the number with two decimal places? No.
0.64.to_r.rationalize(0.001)
# => (16/25)

# What magic number do I need?
0.643.to_r.rationalize(0.005)
# => (9/14) ✅

Next, I can never remember how to use decimals with Rails/PostgreSQL. The following Rails migration adds a decimal type column that can store numbers up to 999.999 — precision: 6 means the number can have total of 6 digits, scale: 3 means 3 of those digits come after the decimal point (surely it should be the other way round, scale for how big the number is and precision for its decimal places?):

add_column :standings, :points, :decimal, precision: 6, scale: 3

Now that I’m storing the number with an appropriate fidelity and can turn it into a sensible fraction I want it to look nice. The following Rails helper method determines if a number is a fraction and formats it with <sup>/<sub> tags:

module ApplicationHelper
  def fraction(numerator, denominator)
    capture do
      concat tag.sup(numerator)
      concat '⁄' # Unicode fraction slash.
      concat tag.sub(denominator)
    end
  end

  def points(number)
    return '0' if number.zero?

    rational = number.to_r.rationalize(0.005)
    whole, remainder = rational.numerator.divmod(rational.denominator)

    capture do
      concat whole.to_s unless whole.zero?
      concat fraction(remainder, rational.denominator) unless remainder.zero?
    end
  end
end

Storing approximate values in the database means you might have to be careful about losing precision, for example ⅐ × 49 = 7 but 0.143 × 49 ≠ 7:

BigDecimal('0.143').to_r.rationalize(0.005) * 49
# => (7/1)

BigDecimal('0.143') * 49
# => 0.7007e1

(BigDecimal('0.143') * 49).to_r.rationalize(0.005)
# => (589/84)

Luckily I perform limited calculations on my data so this isn’t something I have to worry about.

Bonus: Unicode fractions

I happened upon a site that demonstrates how to construct fractions from Unicode superscript and subscript characters. Even better is that the source is on GitHub so I borrowed the approach and implemented it in Ruby/Rails:

module ApplicationHelper
  SUB = %w(₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉)
  SUP = %w(⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹)

  def fraction(numerator, denominator)
    numerator.digits.reverse.map { |char| SUP.fetch(char) }.join \
      + '⁄' \ # Unicode fraction slash.
      + denominator.digits.reverse.map { |char| SUB.fetch(char) }.join
  end
end