RailsAsyncMethods

Rails Async Methods is an opinionated gem meant to remove boilerplate while creating Rails ActiveJobs. It provides a declarative interface for converting any model method into an asychronous method by providing an abstracted wrapper around rails ActiveJob API.

Usage

Model Declaration

class User
  def example_method
    # logic...
  end
  async :example_method

  # or...

  async def example_method2
    # logic...
  end
end

This will give you access to user_instance.async_example_method, which when called will use ActiveJob's API to create an ActiveJob with your backend of choice and call the example_method when the job is ran.

One important distinction is that for a method call like

def example_method_with_args(a:, b:)
  #logic
end
async :example_method_with_args

the async_example_method_with_args method will have a signature that matches the original method. This makes testing and debugging during development faster, as both sync and async method calls will fail when called with improper arguments instead of silently failing as an active job.

Object Wrapper

Alternatively, if you don't want to declare methods as async in your model, you can utilize the async object wrapper made available globally to all objects.

class ResourceController < ApplicationController
    def create
        async(@resource).any_method_available_to_resource
    end
end

If you don't want to wrap objects in a utility method, you can also use the expressive type converter.

Type Converter

class ResourceService
    def run
        @resource.to_active_job.any_method_available_to_resource
    end
end

Note: Under the hood, the object wrapper just makes a call to_active_job, so these objects work the same.

It is recommended that you default to the model declaration syntax.

Options

You can pass the following options to your async declaration.

  • prefix: specifies a prefix other than async_, i.e. ```ruby async :example_method, prefix: :asynchrounous_

user_instance.asynchronous_example_method


- job: use a custom job other than the generated ```RailsAsyncMethods::AbstractJob``` - see section on Custom Jobs below i.e.
```ruby
async :example_method, job: CustomExampleMethodJob # defined in model
async(@resource, job: CustomExampleMethodJob).example_method # callable anywhere
@resource.to_active_job(job: CustomExampleMethodJob).example_method

  • ActiveJob configurations
    • queue: specify a custom queue
    • wait_until: specify a date for the job to run at
    • wait: give an amount of time for the job to wait until executing
    • priority: delayed job priority ruby async :example_method, queue: :fast, wait_until: 1.week.from_now, wait: 1.week, priority: 1 # defined in model async(@resource, queue: :fast, wait_until: 1.week.from_now, wait: 1.week, priority: 1).example_method # same as calling @resource.async_example_method when above is defined in model

Installation

First, make sure your ActiveJob is setup with the backend of your choice. Add this line to your application's Gemfile:

gem "rails_async_methods"

And then execute:

$ bundle

Then, run the generator

$ rails generate rails_async_methods

Or install it yourself as:

$ gem install rails_async_methods

Extras

Custom Jobs

While you can implement any custom job, and have it implement the perform method with this signature.

def perform(receiver, method, *args, **kwargs)
  receiver.send(method, *args, **kwargs)
end

I would instead recommend inherting from the generated ActiveJob, i.e.

class RailsAsyncMethods::CustomJob < RailsAsyncMethods::AbstractJob
  around_perform :do_special_thing

  private
  def do_special_thing
    # Special Things
  end
end

Contributing

Contributions Welcome! Please create an issue first, and then fork and create a PR.

License

The gem is available as open source under the terms of the MIT License.

Why?

You don't need to move 1-2 LOC out of your model. If it's larger, use a service object. Either way, your controllers and requests should be interacting with resources typically. They should not need to know about Jobs/Service/Other Design Pattern implementations of interacting with your resources. In this line of thinking, most calls that interact with a resource should be called on that resource's model. Thus, Job's that wrap 1-2 LOC or a call to a Service object are unneccesary boilerplate.

So, either write your minimal logic in the model, or use whatever refactoring you deem necessary, but leave the model as the entry point for interacting with the resource. Then, declare that method as Async when you need fit.

I told you this gem is opinionated.

Also, the existing alternatives override the method's name, leading to confusion as what appears to be synchronous was made asyncronous.