When someone on Mastodon asked for help with a Ruby question, my friend and business partner Markus kindly referred them to me, knowing that I’m always happy to help other Ruby developers. Their question was about executing Mastodon code from a custom script. In this post, I describe how I solved this problem using the Rails console and a custom subclass.
The problem the person wanted to solve was testing the logic Mastodon uses for verifying the ownership of a user’s websites.
For details on how Mastodon verifies websites, see the section “Link verification”
in the Mastodon user profile
documentation. In short, you have
to add a link from your website back to your Mastodon account using either a <a
rel="me" ...>
link in the page body or a <link rel="me" ...>
reference in its
header.
Ideally, this test would use the original Mastodon codebase to make sure it worked across application updates.
Not the solution: standalone script
On my live stream, I first attempted to write a
standalone script. My thought was to instantiate the VerifyLinkService
class
from
app/services/verify_link_service.rb
.
I was hoping I’d get away with requiring a few dependencies from the Mastodon
application, and maybe replacing a few classes with mocks. But I quickly ended
up including ActionSupport
and the kitchen sink, which made this approach
unviable.
The solution: Rails console
In order to use the original verification code, I had to to run it in the context of the Mastodon application. The easiest way to do this is using the Rails console. However, spinning up a Rails console requires a working Rails application–database and all. Before I describe how to solve this, let’s first build the code we’re going to run inside the Rails console to test the verification.
Building the test
Maybe I would have been able to use the original VerifyLinkService
class with
a few tweaks, but after closer inspection, I was actually only interested in its
methods perform_request!
and link_back_present?
. One fetches the website in
question, the other checks if it contains a valid link back to Mastodon.
Unfortunately, both methods use instance variables set in the constructor using
Mastodon user data. And, to make matters worse, they’re (rightfully) private
methods, which meant that nobody is allowed to call them. And just when I told
my viewers, “Well, nobody but the class itself, of course”, inspiration hit me:
Not only a class is allowed to call its private methods, but also its child
classes! All I had to do is create a subclass of VerifyLinkService
, provide
our test details in its constructor, and call the two methods required to
execute the verification. This is what I ended up with:
class ManualVerifyLinkService < VerifyLinkService
def initialize
@link_back = "https://mastodon.social/@geewiz"
@url = "https://www.geewiz.dev"
end
def check
perform_request!
if link_back_present?
puts "Verification successful."
else
puts "Verification failed."
end
end
end
Spinning up Mastodon
This left the problem of getting Mastodon running. The fastest way to run a
Rails application with its auxiliary services and not have to do lots of manual
installation is generally with Docker containers, and Mastodon is no exception.
Conveniently, the Mastodon code repository already comes with a
docker-compose.yml
definition that contains everything we need. I found a
How-To
on the web that describes the launch process well. All I had to do was follow it
until, and including, the step where the whole setup is started by the
docker-compose up -d
command.
In this state, I was able to launch a Rails console inside the already running web application container:
docker-compose exec -it web bundle exec rails console
Running the test
Into this console, I pasted the definition of my ManualVerifyLinkService
class from above.
And finally, I was able to execute the verification by instantiating an object
and calling its check
method:
v=ManualVerifyLinkService.new
v.check
Since my website is already verified by Mastodon, the result was positive.
This was a fun experiment, and as is often the case when I help someone, I’ve learned something myself in the process.