Search code examples
ruby-on-railsactivesupport-concern

Skip ActiveSupport Concern on Model Create


Problem statement

I need to call create on a model, and skip just one of the many Concerns that are included in that model definition.

Requirements

  • Can't make changes to the model, or any controller, or the concern itself
  • Can't skip all after_create callbacks
  • Can't wait for the delayed job queue

Code examples

Model

class Team < ApplicationRecord
  include SomeConcernINeed
  include SomeConcernIDontNeed
  include AnotherConcernINeed

  belongs_to :organization

  ...
  # etc

Concern

module SomeConcernIDontNeed
  extend ActiveSupport::Concern

  included do
    after_create :call_some_service

    def call_some_service
      ::SomeServiceCallINeed.perform_later(self)
    end
  ...
  # etc

My code

Team.find_or_create_by!(name: "Foo") do |team|
  # need to skip concern here
end

Why

I'm not a rails developer, but a software tester. I am creating a rake task to seed a heroku review app for automation tests.

I am working with a large legacy codebase that I am not allowed, nor advised, to make any changes, because of how deeply coupled and nested the logic of the application is.

The rake task I am running creates records during the release phase of the heroku deployment, and many of the these models are dependent on other tables, or create dependent records on a perform_later service call.

Because the workers are not up and running during the release phase, and I can't count on the dependent records I need later in the script to be created by the Concerns, so I call some of the services manually. E.g. A Team needs a User which needs an Application which has many Questions.

The problem is after the release phase, the server boots up, and the workers start processing all the delayed jobs that are enqueued by the 1000+ records I just seeded. This is fine for some jobs, as the services they call are idempotent. But one in particular creates 1300 duplicate records.

With the heroku-postgresql:hobby-dev plan that comes standard with all review apps, I am limited to 10k rows in the DB before write privileges are revoked. I need to limit the total number of rows in my DB, and this is the place where I can have the greatest impact.

Research and due diligence

I have not seen any other questions to address this specific need.

I know there is a way to skip callbacks

  • e.g. Team.skip_callbacks(:after_create) But this will negate the other after_create callbacks I still need (I think).

I don't know if I can use a similar utility or line of code that will work for my situation.

I tried moving the rake task to the postdeploy script in my app.json config, but with the limited computing resources at my disposal, the delayed_job queue just falls over with the sheer volume it's hammered with all at once.

I tried to reduce my seeds, but I must mock up pre-existing data that an existing test suite is running against. I am not able to copy the DB to the review app because of its size.


Solution

  • Skipping an ActiveSupport:Concern callback is essentially no different than skipping a callback that is directly in the model. The concern name is of no importance, as according the code, this is all happening in one execution.

    Therefore, you have access to the callback method directly, so simply skip the callback by name before creating the record.

    Team.skip_callback(:create, :after, :call_some_service)
    Team.create()
    

    Make sure to clean up afterwards by setting the callback again so that future records still use the callback.

    Team.set_callback(:create, :after, :call_some_service)