• Stars
    star
    136
  • Rank 267,670 (Top 6 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 2 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

ActiveJob::Performs adds the `performs` macro to set up jobs by convention.

ActiveJob::Performs

ActiveJob::Performs adds the performs macro to set up jobs by convention.

Usage with ActiveRecord::Base & other GlobalID::Identification objects

ActiveJob::Performs works with any object that has include GlobalID::Identification and responds to that interface.

ActiveRecord::Base implements this, so here's how that looks:

class Post < ActiveRecord::Base
  extend ActiveJob::Performs # We technically auto-extend ActiveRecord::Base, but other object hierarchies need this.

  # `performs` builds a `Post::PublishJob` and routes configs over to it.
  performs :publish, queue_as: :important, discard_on: SomeError do
    retry_on TimeoutError, wait: :exponentially_longer
  end

  def publish
    …
  end
end

Here's what performs generates under the hood:

class Post < ActiveRecord::Base
  # We setup a general Job class that's shared between method jobs.
  class Job < ApplicationJob; end

  # Individual method jobs inherit from the `Post::Job` defined above.
  class PublishJob < Job
    queue_as :important
    discard_on SomeError
    retry_on TimeoutError, wait: :exponentially_longer

    # We generate `perform` passing in the `post` and calling `publish` on it.
    def perform(post, *arguments, **options)
      post.publish(*arguments, **options)
    end
  end

  # On Rails 7.1, where `ActiveJob.perform_all_later` exists, we also generate
  # a bulk method to enqueue many jobs at once. So you can do this:
  #
  #   Post.unpublished.in_batches.each(&:publish_later_bulk)
  def self.publish_later_bulk
    ActiveJob.perform_all_later all.map { PublishJob.new(_1) }
  end

  # We generate `publish_later` to wrap the job execution.
  def publish_later(*arguments, **options)
    PublishJob.perform_later(self, *arguments, **options)
  end

  def publish
    …
  end
end

We generate the Post::Job class above to share configuration between method level jobs. E.g. if you had a retract method that was setup very similar, you could do:

class Post < ActiveRecord::Base
  performs queue_as: :important
  performs :publish
  performs :retract

  def publish
    …
  end

  def retract(reason:)
    …
  end
end

Which would then become:

class Post < ActiveRecord::Base
  class Job < ApplicationJob
    queue_as :important
  end

  class PublishJob < Job
    …
  end

  class RetractJob < Job
    …
  end

  …
end

Establishing patterns across your app

If there's an Active Record method that you'd like any model to be able to run from a background job, you can set them up in your ApplicationRecord:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  # We're passing specific queues for monitoring, but you may not need or want them.
  performs :touch,   queue_as: "active_record.touch"
  performs :update,  queue_as: "active_record.update"
  performs :destroy, queue_as: "active_record.destroy"
end

Then a model could now run things like:

record.touch_later
record.touch_later :reminded_at, time: 5.minutes.from_now # Pass supported arguments to `touch`

record.update_later reminded_at: 1.year.ago

# Particularly handy to use on a record with many `dependent: :destroy` associations.
# Plus if anything fails, the transaction will rollback and the job fails, so you can retry it later!
record.destroy_later

You may not want this for touch and update, and maybe you'd rather architect your system in such a way that they don't have so many side-effects, but having the option can be handy!

Also, I haven't tested all the Active Record methods, so please file an issue if you encounter any.

Method suffixes

ActiveJob::Performs supports Ruby's stylistic method suffixes, i.e. ? and ! respectively.

class Post < ActiveRecord::Base
  performs :publish! # Generates `publish_later!` which calls `publish!`.
  performs :retract? # Generates `retract_later?` which calls `retract?`.

  def publish!
    …
  end

  def retract?
    …
  end
end

Private methods

ActiveJob::Performs also works with private methods in case you only want to expose the generated _later method.

class Post < ActiveRecord::Base
  performs :publish # Generates the public `publish_later` instance method.

  # Private implementation, only call `publish_later` please!
  private def publish
    …
  end
end

Additionally, in case the job is meant to be internal to the object, performs :some_method returns :some_method_later which you can pass to private.

E.g. private performs :some_method will generate a private some_method_later method.

Usage with ActiveRecord::AssociatedObject

The ActiveRecord::AssociatedObject gem also implements GlobalID::Identification, so you can do this too:

class Post::Publisher < ActiveRecord::AssociatedObject
  extend ActiveJob::Performs # We technically auto-extend ActiveRecord::AssociatedObject, but other object hierarchies need this.

  performs queue_as: :important
  performs :publish
  performs :retract

  def publish
    …
  end

  def retract(reason:)
    …
  end
end

Passing wait to performs

If there's a job you want to defer, performs can set it for each invocation:

class Post < ActiveRecord::Base
  mattr_reader :config, default: Rails.application.config_for(:posts)

  performs :social_media_boost, wait: config.social_media_boost_after
  performs :social_media_boost, wait: 5.minutes # Alternatively, this works too.

  # Additionally, a block can be passed to have access to the `post`:
  performs :social_media_boost, wait: -> post { post.social_media_boost_grace_period }
end

Now, social_media_boost_later can be called immediately, but automatically run after the grace period.

wait_until is also supported:

class Post < ActiveRecord::Base
  performs :publish, wait_until: -> post { Date.tomorrow.noon if post.graceful? }
end

Praise from people

Here's what @nshki found when they tried ActiveJob::Performs:

Spent some time playing with @kaspth's ActiveRecord::AssociatedObject and ActiveJob::Performs and wow! The conventions these gems put in place help simplify a codebase drastically. I particularly love ActiveJob::Performsβ€”it helped me refactor out all ApplicationJob classes I had and keep important context in the right domain model.

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add active_job-performs

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install active_job-performs

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/kaspth/active_job-performs.

License

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

More Repositories

1

active_record-associated_object

Extract Collaborator Objects from your Active Records, a new concept called Associated Objects
Ruby
191
star
2

oaken

A fresh blended alternative to Fixtures & FactoryBot for dev and test data.
Ruby
183
star
3

conventional_extensions

ConventionalExtensions sets up a file naming convention to extend your domain model
Ruby
71
star
4

action_controller-stashed_redirects

Stash a redirect to execute a controller flow within another and return to the original flow later.
Ruby
57
star
5

minitest-byebug

Start a byebug session on test failures.
Ruby
49
star
6

active_record-ingress

Pass control of Active Record methods to a dedicated object.
Ruby
37
star
7

struct-initializer

Reuse Struct.new's attr_reader and initialize generation in any class.
Ruby
34
star
8

riffing-on-rails

A more artful way to Domain Model your Rails apps.
Ruby
33
star
9

process_sip

Make ad-hoc adapters for CLIs to interface with from Ruby.
Ruby
16
star
10

brb

BRB is a backslashed Ruby template system, to let you be-right-back to ERB
Ruby
13
star
11

activemodel-aggregator

Treat several models as one in your forms.
Ruby
7
star
12

active_job-inlined

ActiveJob::Inlined lets you run jobs inline within other jobs.
Ruby
7
star
13

hercule-poro

Hercule::PORO helps give your Ruby POROs a healthy distance to their genius.
Ruby
5
star
14

tokie

Tokie signs, verifies, encrypts and decrypts JSON Web Tokens in Ruby.
Ruby
3
star
15

gsoc-application

My project proposal for Google Summer of Code 2013.
3
star
16

CVPredicateFilter

Flexible filtering of arrays on a background thread with NSPredicate.
Objective-C
1
star
17

linked_ticket_app

This is Zendesk's linked ticket App
JavaScript
1
star
18

CVSearchTableViewController

A table view controller and a search bar meets pain free setting up and filtering of search strings.
Objective-C
1
star