The Fellowship of Code: My Quest at Ruby for Good in Ghent

Posted by on 27 July 2026

It isn’t very hard to pitch tech conferences. Offering a getaway from the usual desk work, a chance to pick up on new wisdoms from like-minded and genuinely inspiring folk, and room in the itinerary for sightseeing and socialising in the hip host city in between times? It’s a no-brainer… well, for most. I had never travelled by myself, let alone flown solo. I’m a junior as well as a woman in tech, so that should make me no stranger to navigating new territories… but this was a whole different venture! While every interaction I’ve had in the Rails community has been nothing short of friendly and insightful, and I was sure there was lots to get out of these conferences, the idea of going to one? It was too daunting.

Ruby for Good compelled me though. I’ve always vowed that I will use my coding skills to make things better in the world. Ruby for Good was an opportunity to do just that. That’s how I arrived at Edinburgh Airport, packed with my belongings and full of nerves, jetting out to Ghent to attend the Ruby for Good event.

The Band Assembles…

On day one, after sneaking in a Belgian carrot cake waffle for breakfast (delightful), I identified and met my fellow Ruby for Gooders. Then, we got dove right in. I learned all things SkillRX. SkillRX is a project with the primary goal of delivering ongoing medical education for people in low-resource areas, where the internet is a premium or non-existent.

It starts with medical training providers, who create topics and attach training materials with search tags. Originally, Raspberry Pis would be sent out to health providers that would broadcast a web interface to find and view these training materials. There was an issue though with how they were providing the content. That being an inefficient, slow, and duplicative sync through Azure file storage for media files. Our mission then? Get that dependency on Azure gone! We would roll in Beacons – devices that would directly communicate with the health professionals’ CMS.

So marked our noble quest. We put our issues into digital ink. We spoke to stakeholders. We diagrammed over dinner. I use the word “quest” purposefully here. Because, at the end of the day, much like a knight who had battled the dragon and saved the princess, I was exhausted. I also had a hero-esque realisation: Working on open-source code would be very different from working on code at my job.

Beginner Mission: The Tests of Open Source

The first thing I learned, which tends to go hand-in-hand with most open-source projects, is the asynchronous working style. While we were all gathered together as a merry crew for this event, the work we were going to do wouldn’t be wrapped up in a few days. To continue contributing to this project, we’d have to get comfortable with working across different parts of the world. Also, our stakeholder from before? Different time zone. That gave weight to the time I spent with the people involved in the project. Getting a change request on a PR for a single typo could be ten times more cringeworthy and hold up the approval pipeline much longer.

Even with mistake-free code though, there was still that learning curve of a new codebase and a new context. Have I understood the project’s architecture correctly? What will I add? Am I a skilled enough programmer to even contribute?

The Power of the Party

It first helped to find that some of my team were also in similar situations. It’s harder to feel like an imposter when there are people to empathise with. It was also encouraging to see how others were contributing. For instance, someone started by cleaning up the app’s Tailwind code, which was very much appreciated. This showed me that a good start to my open source journey would be to ask myself what my current strong suits are, and how I could bring them to the table. I recognised that APIs were something I was familiar with, from my fourth year university project. This seemed helpful for configuring beacons. When a beacon was to be distributed, it’d come with a token. Once plugged in, the beacon would download content based on its configuration. This seemed similar to token-based API authentication, so I took up issues surrounding beacon configuration.

As for those on the team who were already familiar with the project, they were more than open to help. In fact, these maintainers were willing volunteers to act as our sounding boards, and to sit down to explain concepts or pair-program. There was an unspoken understanding from the maintainers that by empowering new members, and teaching them to fish, was how projects like this will be successful in the long term. What did this mean for me? Well, not only is it cool and extremely valuable to learn from developers who have been in the Ruby on Rails world long before me, it was this kind of support that really got my motivation flowing.

Realising how I could be useful and contribute allowed me to wrap up the conference with a nice little bow. By a nice little bow, I mean coding an admin dashboard interface that allows users to manage and work with beacons, including configuring tokens. While being in a space where it was easy to collaborate, form bonds and share skills played a big (massive!) part in accomplishing this contribution, Rails, and the cornerstones around it, was also instrumental.

The show page for a dummy beacon – with options for its token and general configuration
The show page for a dummy beacon – with options for its token and general configuration

Ruby on Rails: A Magic Potion?

To be more specific, I’m talking about the “Convention over Configuration” paradigm. We work with an opinionated framework, so we’ve naturally gravitated to some community-wide defaults. The Ruby for Good project used Hotwire, for example, because it enables you to build uncomplicated yet highly interactive frontends. Its reputation precedes it because it is used here at FreeAgent for that same reason. Seeing some familiar faces like this in the codebase helped.

Although there were some comforting familiarities, the Ruby for Good codebase had its own distinct identity I had to learn! Because the Rails framework handles the grunt work of most of the architectural and project design decisions, legacy will rear its head in codebases that have been around for a long time. An example of this is how we test controllers. This project used the more recent default of request specs, where FreeAgent goes with controller specs. There’s also the scenario where you’ll want to stray away from the beaten Rails path and explore other paradigms. Take service objects, for instance. As their purpose is usually to thin out models or controllers by handling certain business logic, where do they fit into the MVC pattern?

Last but not least, every programmer knows it well: Code efficiency versus code readability. This will always be a subjective matter. I was surrounded by brilliant people at Ruby for Good. I witnessed many clever ways to write code. Yet, there were compromises made in not making the code smarter – simple code is easier to reason about. That isn’t to say I’ve swept any and all ingenious suggestions under the rug – I’m extremely appreciative of all the comments on my Ruby for Good pull request! I’ve seen and learned interesting ways of doing things, and I plan to incorporate some of these learnings into my FreeAgent work!

So Marks the End of the Quest…But not quite!

FreeAgent has always seen all the value in conferences: an opportunity to get inspiration for your craft and learn from the Ruby community, veterans and juniors in between. While this sort of thing was my first, I can imagine that Ruby for Good isn’t your typical run-of-the-mill tech event. Our work is still ongoing, and the point of the event is to help build more of a community that can go forward and make a difference through code. Having something like this to work on during my development time feels significant. I’m solving a big, real-world problem and helping people in need! In (fun) fact, SkillRx is helping over 20 million people in the global south! I’m dedicated to continuing to contribute: existing issues are waiting, and I’ve even spun off my own to tackle in the future! I’m very proud to be part of this project, and I am very excited to see how it forms.

The fabled carrot cake waffle of mystery and wonder
PS Here is your spoil for reaching the end of the blog: The fabled carrot cake waffle of mystery and wonder

Spec-tacular: ways to think about writing expressive RSpec

Posted by on 7 July 2026

I’ve always enjoyed the interface given to us by RSpec. RSpec, when used to its fullest, gives us ways to extract out test setup and teardown, and draw focus to how the output of the test should change given the variable we’re changing. I tend to think about this terms of scientific testing:

RSpec.describe VatReturn do
  # This, and any other setup we do in the top-level `describe` block
  # or the `#can_be_filed?` `describe` block, sets out the global
  # state – these would be considered control variables.
  let(:vat_return) { described_class.new(filed:) }

  describe "#can_be_filed?" do
    # This defines the dependent variable: the thing we
    # expect to change.
    subject { vat_return.can_be_filed? }

    context "when the VAT return has already been filed" do
      # This is the independent variable: the one thing we
      # intentionally change.
      let(:filed) { true }
      
      # We document our expectation here in the test.
      it { is_expected.to be false }
    end
  end
end

When done correctly, this can be really illustrative of a class’s behaviour. While the widespread nature of RSpec wasn’t really intended by its original creator, I think the interface it gives us is maybe one of the most beautiful things to come out of Ruby. For that reason, I’ve always been partial to using what it gives us to the fullest, rather than including all four phases of a test in one example block.

One significant part of this style is the combination of subject and is_expected – subject sets up a variable subject in the same way that let does, and is_expected acts as a shortcut for expect(subject) . In the test above, it { is_expected.to be false } calls the block passed to subject, and checks that its return value is false.

On its own, it { is_expected.to be false } reads like a sentence. RSpec builds up what it calls an implicit description for the test at runtime, which you can see when running RSpec using the --format documentation option. In this case, it’s quite nicely able to derive a sentence from this: VatReturn#can_be_filed? when the VAT return has already been filed is expected to be false.

That sentence tells you what the return value of the method is under those conditions. It describes the interface presented by the class. However, it’s not always the perfect framing for behaviour.

Dealing with views

The testing style we had back there works nicely for plain old Ruby objects (POROs). If what we’re doing is as simple as calling a method and checking its return value, then RSpec is a great fit for documenting that interface, when used as I’ve described. But many things in Rails aren’t quite that straightforward, such as views.

A view isn’t a class. A view is a template that’s rendered by a controller. View specs generally try to forge the idea of a “return value” by saying that the subject is the HTML output of rendering the view. That means you might specify your subject as:

subject do
  render    # Renders the view
  rendered  # The output of doing so is saved to this helper method.
end

subject then contains that HTML output immediately after rendering the view, which means you can write expectations like:

it { is_expected.to have_css("[data-testid='...']") }

It would follow, then, that the description for the example would be it is expected to have css [data-testid="..."]. It does document the interface – when we render the view under these conditions, it does include an element with the test ID we’re looking for. But that’s not immediately illustrative of what the view is rendering. Even if we choose a descriptive name for our test ID, the implicit description doesn’t read as nicely as it should. In this scenario, we’d prefer to phrase things in terms of the behaviour.

We might instead want to say “it is expected to include the delete button”, for example. Referring to an element by its CSS is a layer of abstraction away from what the element is actually doing on the page – we’re not particularly interested in what attributes the element has, we want to know what the element is for. In that case, we’d want to specify our own description:

it "includes the delete button" do
  expect(subject).to have_css("[data-testid='...']")
end

At that point, we’ve lost a bit of the brevity given to us by implicit example descriptions, but it’s worth the cost – we’re framing this example in a way that’s more suited to how we’d expect a view to be described. If it’s regularly optimal to write our own description over using an implicit one, it’s a signal that the scenario we’re dealing with calls for things to be expressed in terms of behaviour.

Dealing with controllers

Changing the subject

When testing controllers, we’d similarly want to build up a subject from calling multiple methods. Our subject might be something like:

subject do
  get :new # Make a request to the route that triggers the `#new` action
  response # Contains the response from that request
end

response is the perfect name for a subject if we do want to use a named subject – which we likely do if we want to make multiple assertions. Since response is usually defined as a test helper already, we could use the underlying instance variable in this case to safely set up a named subject called response:

subject(:response) do
  get :new
  @response
end

That’ll look great in a test like:

it do
  expect(response).to(
    have_http_status(:forbidden).and(
      render_template("forbidden")
    )
  )
end

We’re achieving a few things here:

  • we’re using multiple expectations in one spec, so we’re only making the request once
  • we’re reusing the response variable name, which is perfectly expressive
  • we’re still keeping the request in subject so that it can be reused

When would you change the description?

We might also want to phrase the test description in terms of behaviour, but it depends. Sometimes the description might be too verbose by default, or we might want to more elegantly describe a create/read/update/delete action taken against a record.

In the example above, the HTTP status and the template that is rendered nicely describe the behaviour of the controller action – we’re presenting the user with an error status and a view that indicates to them that they’re not allowed to access this area. But if we were testing a validation response from a form, something like:

it do
  expect(response).to(
    have_http_status(:unprocessable_entity).and(
      render_template("edit").and(
        have_attributes(body: include(params[:invoice][:name]))
      )
    )
  )
end

…then it’s not as immediately clear why we’re re-rendering the edit page on a 422 without an additional mental hop to say “that’s how we typically respond in case of a validation error in Rails.” So this is another case where we might benefit from setting a manual description to describe the behaviour – something like it "rerenders the edit form with the invoice details".

Why wouldn’t you use this?

I’m keen on this style myself, but over time I’ve seen some valid objections to it. Murad Iusufov raised an excellent case for using Minitest over RSpec – the highlight of that talk was really that Minitest makes it easier to write a cleaner four-phase test by encouraging you to make use of what Ruby already has (such as private methods), which can be useful for navigating some of the nesting that comes with RSpec. I think that RSpec’s tendency for nesting is fine if you go no more than two contexts deep:

# Setup omitted for brevity
RSpec.describe DeanMartin do
  describe "#amore?" do
    context "when the moon hits your eye like a big pizza pie" do
      context "when the world seems to shine like you've had too much wine" do
        it { is_expected.to be true }

As I illustrated at the start of this post – and feel free to disagree with me here! – that same nesting really helps to organise tests and lets us focus on the independent variables local to each example. That does, however, come with a complexity cost – good RSpec hygiene is something that needs to be taught, and that’s not currently done comprehensively as far as I’ve seen, though betterspecs.org makes some good syntactic suggestions. If I were advising on a brand-new Rails codebase, I would say that the expressiveness that comes with RSpec is well worth it if you’re committed to the idea of tests as documentation.


RSpec is immensely useful in documenting your code through tests if you make full use of its functionality and take a scientific approach to organising your tests. Thanks to its many matchers, you can usually let RSpec set an example description for you – but needing to describe behaviour as opposed to an interface is a good litmus test for whether you need to step in with your own string.