2025: The year of new beginnings

There have been a few positive, blog-worthy notes happening in my life the past few months.

The first is that by the grace of God, I found actual full-time employment working with a wonderful team doing important work. And, as luck would have it, they actually care about work/life balance as well. That means I will still have time to contribute to open source and work on the projects I am passionate about at the consultancy, too. It has been a joyful time, and I am very thankful to everyone involved.

The second, speaking of the consultancy: we are gearing up to launch an AGPL 3-licensed, Rails-powered e-commerce application. We hope to empower people with the best parts of Libre Software, including the ability of everyone to contribute to and audit the code base, and the ability of enterprising people to stand up their own servers. We also hope to be able to continue our efforts maintaining this software, in addition to the other work we do, by offering a hosted version for a nominal monthly fee. More details will be forthcoming in the next months on the WTI Blog, so be sure to check it out if you’re interested!

The final is a more personal note, and it is that my extended family is growing! I won’t go into exact details, but suffice to say, we feel very blessed to be welcoming more of us into the world 🙂

All in all, it has been quite a start to 2025 – and I’m hoping it gets even better from here. I am polishing up a few articles for Mac Monday and FOSS Friday, and I’ll be posting them soon. Until then, happy hacking!

An RSpec matcher for validating Rails <meta/> tags

While writing a project for Wilcox Tech that we hope to open source soon, I had reason to test the content (or, in some cases, presence) of <meta/> tags in a Rails app.

I came across a good start in dB.’s blog article, Custom RSpec Meta Tag Validator. However, the article was from 2013, did not follow the modern RSpec protocol, and the matcher felt a bit off for my purposes.

What I really wanted was a matcher that felt like the built-in ones to Rails, such as assert_dom or assert_select. So, using dB.’s XPath query as a starting point, I wrote a new one.

I am under the assumption that dB.’s snippet is licensed CC-BY as is the rest of his post. I am therefore licensing my matcher under a dual-license, as I believe this to be legal: CC-BY with my authorship, or MIT (the standard license of most Rails-related Ruby Gems). To fully comply, you will need to acknowledge him as well.

Usage looks like this:

  describe 'meta product properties' do
before { assign(:item, create(:item, description: 'This is a test description.', price: 50.50)) }

it 'has the correct Open Graph type' do
render_item
expect(rendered).to have_meta 'og:type', 'product'
end

it 'has the description set' do
render_item
expect(rendered).to have_meta 'og:description', 'This is a test description'
end

it 'has the price set' do
render_item
expect(rendered).to have_meta 'product:price.amount', '50.50'
end
end

describe 'meta image properties' do
context 'with no photos' do
it 'does not have an og:image property' do
assign(:item, create(:item))
render_item
expect(rendered).not_to have_meta 'og:image'
end
end

context 'with a photo' do
before do
item = create(:item)
photo = mock_item_photo_for item
photo.description = ‘My Photo Description’
assign(:item, item)
end

it 'has an og:image property' do
render_item
expect(rendered).to have_meta 'og:image'
end

it 'has the og:image:alt property set to the photo description' do
render_item
expect(rendered).to have_meta 'og:image:alt', 'My Photo Description'
end
end
end

And here’s my spec/support/matchers/have_meta.rb:

# frozen_string_literal: true

class HaveMeta
attr_accessor :expected, :actual, :key

def initialize(*args)
raise ArgumentError, 'Need at least one argument' if args.empty?

@key = args.shift
@expected = args.shift
end

def diffable?
@expected.present?
end

def matches?(page)
meta = page.html.at_xpath("//head/meta[@property='#{@key}' or @name='#{@key}']")
return false if meta.blank?

return true if @expected.blank?

@actual = meta['content']
@actual.include? @expected
end

def failure_message
return "expected meta property '#{key}'" if @expected.blank?

"expected '#{key}' to contain '#{@expected}' in '#{@actual}'"
end

def failure_message_when_negated
return "expected not to find meta property '#{key}'" if @expected.blank?

"expected '#{key}' to not contain '#{@expected}' in '#{@actual}'"
end
end

# Determines if the rendered page has a given meta tag.
def have_meta(*args)
HaveMeta.new(*args)
end