“...I've been working since 2008 with Ruby / Ruby on Rails, love a bit of Elixir / Phoenix and learning Rust. I also poke through other people's code and make PRs for OpenSource Ruby projects that sometimes make it Currently working for GenieBelt who are based in Copenhagen, Denmark ...”

Rob Lacey
Senior Software Engineer, Copenhagen, Denmark

Puma-dev is one of my favourite tools

Running applications locally on your laptop is normally a doddle. Boot up a Rails app and point your browser at

http://localhost:3000

bash

Easy peasy. But what if your application is not your average application. Suppose it’s multi-tenanted app with a hostname for each tenant and you want to try that locally. Well you could whack a load of entries in your /etc/hosts

/etc/hosts

##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1    demo.fossa-are-great.org
127.0.0.1    demo.fossa-are-ok.org
127.0.0.1	localhost
255.255.255.255	broadcasthost
::1             localhost

bash

Then you could visit…

http://demo.fossa-are-great.org:3000/
http://demo.fossa-are-ok.org:3000/

bash

…with ease and everything would be fine, even if port 3000 is now a little jarring. It is possible to remove the port if we ran our own version of Nginx running on port 80 and configured it to proxy onto our running application on port 3000. I’m going to go out on a limb and say there are will be a high proportion of developers that won’t know how to configure Nginx to do this and it’s a bit overkill anyway. However, puma-dev makes pretty light work of this.

Puma-dev: A fast, zero-config development server for macOS and Linux - https://github.com/puma/puma-dev

On macOS it’s pretty easy to set up with brew

brew install puma/puma/puma-dev
# Configure some DNS settings that have to be done as root
sudo puma-dev -setup
# Configure puma-dev to run in the background on ports 80 and 443 with the domain `.test`.
puma-dev -install

bash

Then in order to configure our hostnames we create a file the ~/.puma-dev per hostname we want to support with the port of the running application.

~/.puma-dev/demo.fossa-are-great

3000

bash ~/.puma-dev/demo.fossa-are-ok

3000

bash

This time we have to visit our domain with the .test Top-Level Domain as Puma Dev, by default, supports that TLD. Puma-dev runs on port 80 and 443 as standard and so there is no need to supply a port.

http://demo.fossa-are-great.test/
http://demo.fossa-are-ok.test/

bash

The way this works is really neat and I’m a bit of a fan to be fair. Hostnames on macOS and Linux flavours uses /etc/resolver/. Puma creates a file /etc/resolver/test to indicate that name resolution for .test domains should use a local nameserver running on port 9253 to resolve hostname blah.test

# Generated by puma-dev
nameserver 127.0.0.1
port 9253

bash

In fact let’s try it

dig blah.test @127.0.0.1 -p 9253

; <<>> DiG 9.10.6 <<>> blah.test @127.0.0.1 -p 9253
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2962
;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;blah.test.			IN	A

;; ANSWER SECTION:
blah.test.		0	IN	A	127.0.0.1

;; Query time: 0 msec
;; SERVER: 127.0.0.1#9253(127.0.0.1)
;; WHEN: Mon Feb 05 15:42:26 GMT 2024
;; MSG SIZE  rcvd: 52

bash

Yep, this returns and resolves our domain to 127.0.0.1, however visiting http://blah.test will result in the following because we haven’t configured puma-dev to point at an internal port.

unknown app

bash

This is pretty neat huh? Well that’s not the end of the story. What if we wanted to use https://demo.fossa-are-great.test. Well that just works out of the box. Like I said it also runs on port 443. Puma manages the certificate authority for the .test domain locally and adds the certificate and key to the local keychain so now we have valid SSL. Magic.

% ls -la ~/Library/Application\ Support/io.puma.dev 
total 16
drwx------   4 rl  staff   128 16 Jan 09:03 .
drwx------+ 62 rl  staff  1984 29 Jan 02:06 ..
-rw-r--r--   1 rl  staff  1208 16 Jan 09:03 cert.pem
-rw-------   1 rl  staff  1679 16 Jan 09:03 key.pem

bash

I’ve been using this setup for years now and it has enabled us to run multiple applications locally and fully test running up to 5 different multi-tenanted Rails apps logging users in with the same cookie domain as an extreme example.

https://app1.blah.something.test
https://app1.club.something.test
https://app2.blah.something.test
https://app2.club.something.test
https://app3.blah.something.test
https://app3.club.something.test

bash

I have only ever found one issue with this setup and that was today.

SSL Certificate seemingly invalid

Today’s issue involved building PDFs with Grover that had missing styles and as it turned out the this was only over SSL so there was an issue with the certificate which was fixed by re-configuring Puma-dev to presumably re-add the certificate to the keychain.

puma-dev -uninstall
sudo puma-dev -setup
puma-dev -install
puma-dev -stop

bash

Thumbs up for puma-dev

I need a new adventure

This year has been a weird one. And life takes a turn.

At the end of last year, after 4 years, I decided to move on from SARD JV Limited they are lovely people and the team are great and we still talk but for varying personal reasons, it could not continue and I set out to seek my fortune elsewhere.

I landed with Builder.ai in January of this year and realised pretty quickly that the role I had taken was not for me. I’m not afraid of meetings, planning, mentoring, code reviews, far from it. But when I realised that I wasn’t writing any code myself that became a problem. With SARD JV I was in the heart of everything, guiding the development of the application and introducing new ways of restructuring code, adding in performance gains the whole package, I had code coming out of my ears and I was waking up thinking about code. Without being in the thick of it I was in a place where I wasn’t happy. This is the first time I felt like I’d made the wrong choice and it was wrong to continue for myself and for Builder.ai so I made a quick exit.

In April I joined Juniper Education and have for the past six months been working as a Senior Developer in an exceptional team on a pretty complex Assessment tracking system for UK schools. Plenty of performance problems to tackle and interesting UIs. We were on the verge of launching the product in the coming months. Sadly, however, the business has decided not to continue with Ruby. I mean I think Ruby is pretty great, but… that’s not our decision. That means a medium size team of developers were released in the wild on Tuesday of this week.

If only there was a startup that needed 5 to 10 developers to that were already comfortable working with one another to start on a new product. Wishful thinking I know.

So for now I’m on the hunt for work myself. I have all of my possessions wrapped in a hankerchief on the end of a stick and I am off to the end of our driveway to seek my fortune. That might be a lie.

So here goes “…I’m a seasoned but not salty Senior Software Engineer from Brighton, UK who’s been working with Ruby/Rails (remotely) for the past 15 years. I’m looking for a fully remote role that is hands-on focusing on code rather than people management although I love working with other developers and wish to continue code review and mentoring. I want to make a positive impact on the team I land in and inject my experience in a new project/adventure…”.

If you have a quest that needs a Tank, I’m available and ready to work with you. Feel free to take a look at my CV and any feedback positive or negative is very welcome.

Image description

Highlighting with HighlightJS

Syntax Highlighting is important, it just makes code all warm and fluffy and more engaging. I currently use HighlightJS in my Rails 7 app.

./bin/importmap pin highlightjs

bash

Which adds the following to `config/importmap.rb`

pin "highlightjs", to: "https://ga.jspm.io/npm:highlight.js@11.4.0/es/index.js"

ruby

Over the years I have used a number of different ways to organise my code examples in blog posts. They are a mixture of RedCloth and HTML and I used to used to use CodeRay and then SyntaxHighlighter to render code blocks nicely.

<x-source:ruby>
class Cat; end
</x-source>
but eventually settled on pre > code and HighlightJS
<pre>
  <code>
    class Rabbit; end
  </code>
</pre>

html

As an aside I never updated the old blog posts but created a renderer to update the body of the posts to comply to the new format, and even ensure that I could embed my pre > code html in examples too by escaping the contents of every pre > code element.

# frozen_string_literal: true
class Content
  def initialize(string)
    @string = string
  end

  def render
    ::Haml::Helpers.preserve(segments.join('')).html_safe
  end

  private

  def string
    @string.gsub(/<source(:[a-z]+)?>/, '<pre><code>')
      .gsub(%r{</code></pre>}, '</code></pre>')
      .gsub(%r{<code>([\s])+}, "<code data-controller='highlight'>")
  end

  def segments
    string.split(/(<pre><code>.*?<\/code><\/pre>)/m).map do |s|
      if s =~ /<pre><code>.*?<\/code><\/pre>/m
        s.gsub(/<pre><code>(.*)?<\/code><\/pre>/m) { "<pre><code data-controller='highlight'>#{CGI::escapeHTML($1)}</code></pre>" }
      else
        RedCloth.new(s).to_html
      end
    end
  end
end

ruby
I could then use just a turbo:load EventListener to apply the highlight the code blocks.
app/javascript/application.js

import "@hotwired/turbo-rails"
import "controllers"

// Turbo.session.drive = true

import HighlightJS from "highlightjs"
window.HighlightJS = HighlightJS

document.addEventListener('turbo:load', (event) => {
  console.log('turbo:load')
  document.querySelectorAll('pre code').forEach((el) => {
    HighlightJS.highlightElement(el);
  });
});

javascript

However, this doesn’t work with Turbo-Frame, the `turbo:load` callback is only run on full page loads and I want to use them in Turbo-Frame(s). I am sure there is another callback I could run but honestly I’d prefer to use a Stimulus Controller and know that it will always trigger when the DOM changes and my code block is added to the DOM.

app/javascript/controllers/highlight_controller.js

import { Controller } from "@hotwired/stimulus"
import HighlightJS from "highlightjs"

export default class extends Controller {
  connect() {
    HighlightJS.highlightElement(this.element)
  }
}

javascript

With code blocks looking like

<pre>
  <code data-controller="highlight">
    class Rabbit; end
  </code>
</pre>

html

This seemed to work in development but failed in production. AS it turns out I was caching the content of every blog post, so it wouldn’t need to re-render every

Rails.cache.clear

ruby

For bonus points I’ve added support for triple back-tick ```language delimited blocks for code.

# frozen_string_literal: true
class Content
  def initialize(string)
    @string = string
  end

  def render
    ::Haml::Helpers.preserve(segments.join('')).html_safe
    # rescue => e
    #   'BROKEN'
  end

  private

  def string
    @string.gsub(/<source(:[a-z]+)?>/, '<pre><code>')
      .gsub(%r{</code></pre>}, '</code></pre>')
  end

  def segments
    rewritten = string.split(/(

[a-z]?\n.*?

)/m).map do |segment|
if segment =~ /
([a-z])?\n.?
/m
segment.gsub(/
([a-z]+)?\n(.
)?```/m) { "

#{CGI::escapeHTML($2)}

" }
else
segment
end
end.join(’’)

rewritten.split(/(
]+?>.*?<\/code><\/pre>)/m).map do |s|
      if s =~ /
.*?<\/code><\/pre>/m
        s.gsub(/<pre><code>(.*)?<\/code><\/pre>/m) { "<pre><code data-controller='highlight'>#{CGI::escapeHTML($1)}

[^

" }
elsif s =~ /

.*?<\/code><\/pre>/m
        s
      else
        RedCloth.new(s).to_html
      end
    end
  end
end
```

However, I should just bulk update the content of the blog bodies to not use the old tag syntax I implemented years ago anyway.

Fun Thursday.

My First Elixir Project

Most people’s first project in a language / framework is a simple ToDo example or a blog. I guess I’m in the same situation. However, my first Ruby project was a blog and I’ve been dumping code snippets, findings and moans on that for 10 years now. So my first Elixir project should really be to port my Rails blog to Phoenix. Here’s some things I learned along the way.

Agile Web Development with Rails

I get the distinct impression that this book, along with particular circumstances may have changed my life.

I was working as a Network / Systems Administrator for whatever particular system was broken that day at that time, trying to migrate every last website off old hardware, and trying not to spend my day desperately trying to build something meaningful in PHP (while doing the normal day job stuff) and generally being bored out of my skull at a job I had loved for years. It was partly being bought out, moving into a data centre and then in almost cubicles where we hardly spoke to colleagues we’d previously spent all day chatting and joking with for make benefit glorious internet. The love had left the building.

So then I heard about Ruby, Rails and then met John well I met him at a job interview that I was going for …erm with him. To do those things I was bored out of my skull doing but still to me it was easy and I could learn Ruby on the job for make benefit glorious brain, career advancement.

I bought this book for significantly more than the £3 it goes for on eBay now. I moaned about how the Order object was marshalled in the Session rather than store the order_id and look it up. But hey. It opened up my eyes to some kind of framework and structure rather than bumbling through making a PHP file per page on my site and boom.

Some 15 years later I am still writing Ruby and money appears in my account every month as a result of this.

My Test Phoenix App is not dead

…it’s just sleeping

Hi, You’re receiving this email because your Gigalixir app, iplay-red, is inactive and is scheduled for scale down to 0 replicas in 7 days. We define an inactive app as one that has not been deployed for over 30 days as opposed to one that does not have any network traffic. We do this so that we never have to put apps to sleep. If you want to keep your app running, just deploy or upgrade within the next 7 days.

Ok, better add some more features. Thanks for the heads up Giggles

So programming languages huh?

Before I started with Ruby, I had worked with Perl, PHP, Python, and BASIC (ok, back before I was 10). Ruby has stuck and has been my goto for everything. I feel like this has left me rather stuck and lacking any kind of contrast to other solutions, problem solving, perspective on how to create effective software. So my list of things to look at over the course of the rest of my life

--
- Ruby
- Go
- Rust
- Python
- Elixir
- Erlang
- Crystal
- Haskell
- Elm
- C++ (Unreal Engine)

With any luck exposure to all of this will make my brain explode in interesting artistic patterns.

Caught my first tube this morning, Sir.

Ok, I threw this up about a week ago. Still I’ve managed to deploy my first Phoenix application to Gigalixir. It’s attempting to be a rather splendid, better than the YouTube UI for I Play Red ’s YouTube Channel …which will allow Kat the ability to publish her videos, which are then synced here but at the same time label them with the game they are for. Which allows for better searching, SEO, OpenGraph, and generally syndicating all over the shop.

Plus I have a project to build something that needs to work and we can measure success on nicely. Not like this blog where the only person that really needs to read it is me :)

Fun with classes and constants

Ruby is somewhat versatile at times. Suppose you want to define a class dynamically, and I do….

class Thing; end

Ok, that’s not dynamic

# still not dynamic but getting there.
def define_a_class
  Thing = Class.new
end

# ok then I see
def define_a_class(name)
  Object.const_set(name, Class.new)
end

define_a_class('Thing')
=> Thing

This is great but what if you wanted to define a class within a module

define_a_class('Nigel::Thing')
NameError: wrong constant name Nigel::Thing
from (pry):19:in `const_set'

Oh. That sucks. What you’re actually trying to do here is

Object.const_set('Thing', Class.new)
#=> Thing
Nigel.const_set('Thing', Class.new)
#=> Nigel::Thing

Object is for root namespace, and the module name for the nested namespace.

So today we had an issue. We normally define a Permit class if one doesn’t already exist like so.

def define_permit_class
  name = "#{self.name}Permit"
  Object.const_get(name)

rescue NameError
  Object.const_set(name, Class.new(Permit))
end

This works for

Thing.define_permit_class

But obviously not for

Nigel::Thing.define_permit_class

Well, easily fixed. A class can find it’s parent module, or if it is doesn’t have one it’s Object.

Thing.parent 
#=> Object
Nigel::Thing.parent
#=> Nigel

So we just refactor this a little and bingo

def define_permit_class
  name = "#{self.name.demodulize}Permit"
  parent.const_get(name)

rescue NameError
  parent.const_set(name, Class.new(Permit))
end

Ok, it now seems that parent is deprecated in Rails 6.1. module_parent it is.

DEPRECATION WARNING: `Module#parent` has been renamed to `module_parent`. `parent` is deprecated and will be removed in Rails 6.1.

Elixir did an explode

Just trying to boot my application after a little time away from the project. Just wondering if my my latest brew upgrade broken something here too. Node and MongoDB went foom too on the last upgrade.

Robs-MBP:i_play_red_umbrella rl$ iex -S mix
Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit:ns] [dtrace]


11:17:06.689 [error] beam/beam_load.c(86): Error loading module 'Elixir.Code.Formatter':
  lambda already defined for label 744. To fix this, please recompile this module with an OTP 25 compiler.


 
11:17:06.689 [error] Loading of /Users/rl/.asdf/installs/elixir/1.13.1/bin/../lib/elixir/ebin/Elixir.Code.Formatter.beam failed: :badfile

** (UndefinedFunctionError) function Code.Formatter.locals_without_parens/0 is undefined (module Code.Formatter is not available)
    (elixir 1.13.1) Code.Formatter.locals_without_parens()
    (elixir 1.13.1) lib/code/normalizer.ex:22: Code.Normalizer.normalize/2
    (elixir 1.13.1) lib/code.ex:1107: Code.quoted_to_algebra/2
    (elixir 1.13.1) lib/macro.ex:948: Macro.to_string/1

Ok so it’s using OTP 25 but it’s compiled with OTP 22?

Robs-MBP:i_play_red_umbrella rl$ elixir -v
Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit:ns] [dtrace]

Elixir 1.13.1 (compiled with Erlang/OTP 22)

Uninstalled and Re-Installed via asdf and it seems fine now.

Robs-MBP:i_play_red_umbrella rl$ asdf list elixir
  1.12.1
  1.13.1
Robs-MBP:i_play_red_umbrella rl$ asdf uninstall elixir
Robs-MBP:i_play_red_umbrella rl$ asdf list elixir
  No versions installed
Robs-MBP:i_play_red_umbrella rl$ asdf install elixir 1.13.1
==> Checking whether specified Elixir release exists...
==> Downloading 1.13.1 to /Users/rl/.asdf/downloads/elixir/1.13.1/elixir-precompiled-1.13.1.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 6068k  100 6068k    0     0  9689k      0 --:--:-- --:--:-- --:--:-- 9772k
==> Copying release into place
Robs-MBP:i_play_red_umbrella rl$ asdf list elixir
  1.13.1
Robs-MBP:i_play_red_umbrella rl$ elixir -v
Erlang/OTP 25 [erts-13.0.3] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit:ns] [dtrace]

Elixir 1.13.1 (compiled with Erlang/OTP 23)