Why parameterized objects in Rails are so powerful
Tags: rails, ruby, actionmailer, activejob
Published: 2023-01-17 15:40:49

---
title: Why parameterized objects in Rails are so powerful
published: true
description: 
tags: rails, ruby, actionmailer, activejob
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
---

[Matt Swanson](https://twitter.com/_swanson) wrote [an insightful post](https://boringrails.com/articles/writing-better-action-mailers/) today on how to level-up your usage of Mailers in your Rails application. In it, he discussed how using "parameterized mailers" unlocked the ability to build mailers that could be sent from either a custom domain or the applications default domain. As he details in his post, a parameterized mailer is initialized with `params` _before_ calling the mailer method. So instead of:
```ruby
NotificationMailer
  .comment_reply(user, comment)
  .deliver_later
```
You would write:
```ruby
NotificationMailer
  .with(user: user, comment: comment)
  .comment_reply
  .deliver_later
```

I tweeted a quick response to Matt noting that parameterizing jobs can similarly unlock some powerful and useful functionality. Matt reminded me that a fuller post would be more helpful. So, here we are.

- - -

Well, what is "parameterizing" something like a mailer? While [the implementation](https://api.rubyonrails.org/classes/ActionMailer/Parameterized/ClassMethods.html#method-i-with) in Rails is somewhat more complicated for mailers, in essence `with` is simply an alias for `new`. That is, to parameterize is to initialize with state. Instead of passing all of the necessary information into the instance method, we pass it into the initialization method. 

As Matt says in his post, this can feel like a difference without a point:
> My first impression was that I didn’t quite understand the point of this. I generally prefer having the explicit method arguments on the mailer method compared to a generic `params` hash.

But, of course, there is a point. The point is that by moving the required state into a class instance, we make it possible to extend, prepend, and inject behavior that alters how the executing method behaves. In Matt's example, he used a before callback to inject behavior that switches the `from` field for the address, depending on whether the `Account` has a custom domain set or not. 

Notably, `ActiveJob` is "parameterized" by default. As you recall, you define the executing method via `def perform` in your job class; that is, you define an instance method named `perform`. But, you invoke the job by using class methods—either `perform_now` or `perform_later`. Under the hood, ActiveJob's class methods are little more than short-hand for initializing the job class with the passed parameters and invoking the `perform` method:
```ruby
def self.perform_now(...)
  job = new(...)
  job.perform(*job.arguments)
end
```

This isn't _exactly_ what the source code for `perform_now` looks like, but the essence is the same. And what that means is that _before_ your `perform` method is invoked, your job has all of the information it needs for this particular execution. This is what allows callbacks to be useful, for example.

In my particular case, this has proven so powerful because it allows something like [`AcidicJob`](https://github.com/fractaledmind/acidic_job) to exist. `AcidicJob` is a gem that provides a suite of features that you can use to make your jobs both more coherent and more resilient. And, at its heart, it functions by serializing and deserializing your job execution into a database record, and then leaning on the ACID guarantees provided by database engines for transactions. This is easy to do with ActiveJob, since the job instances are parameterized and thus can be serialized _before_ the job is executed with the **full** set of information needed to execute that job.

At present, such behavior is not possible with pure Sidekiq because Sidekiq does not initialize a worker/job instance with the parameters needed to execute the worker/job. I started [a conversation](https://github.com/mperham/sidekiq/issues/5131) on why this would be valuable, but we haven't yet found an API that works well in the context of Sidekiq. This isn't to say anything negative about Sidekiq, which is fantastic software, but simply to point out what kind of flexibility is unlocked when parameterizing operations like jobs and mailers.

Matt's example is only one of many, many possible ways to make your mailers or jobs more flexible, coherent, and perhaps even resilient by leaning on the power of parameterized classes.