Best approach to mark specific Rails Models to be used in a module

158 Views Asked by At

I am trying to write a lib plugin/extension to perform an action where I need to know which Models have been marked for use with this plugin.

Currently, I am marking the models in the fashion of acts_as_something method which is added to each Model intended to be used with the plugin.

The main file of the plugin looks like this

# lib/foo.rb
module Foo
  class << self
    attr_accessor :models
  end
  self.models = []

  module Model
    def acts_as_foo
      Foo.models << self
    end
end

ActiveSupport.on_load(:active_record) do
  extend Foo::Model
end

The intended use is to then call in a controller Foo.perform, which needs to know the marked models in order to carry out the intended action, the idea being getting the list of models from Foo.models.

It works as intended if when config.eager_load is set to true in development.rb , otherwise the files of the models have not been used/loaded yet and therefore Foo.models is an empty Array.

My goal is to be able to add more models to Foo without having to change Foo's code like this.

#app/models/bar.rb
class Bar < ApplicationRecord
  acts_as_foo
end

Any ideas on the best way to implement this?

1

There are 1 best solutions below

5
Jay-Ar Polidario On

I had a similar problem before in my gem.

I ended up loading ONLY model files (which is the minimum of my requirement same as yours because the DSL code is there like your acts_as_foo), and not immediately eager loading all Rails-related files using Rails.application.eager_load!.

# lib/foo.rb
module Foo
  class << self
    attr_accessor :models
  end

  self.models = []

  class Engine < Rails::Engine
    initializer 'foo.eager_load_models' do |app|
      unless app.config.eager_load
        models_load_path = File.join(Rails.root, 'app', 'models')

        # copied from https://apidock.com/rails/Rails/Engine/eager_load%21/class
        matcher = /\A#{Regexp.escape(models_load_path.to_s)}\/(.*)\.rb\Z/
        Dir.glob("#{models_load_path}/**/*.rb").sort.each do |file|
          app.require_dependency file.sub(matcher, '\1')
        end
      end
    end
  end

  module Model
    def acts_as_foo
      Foo.models << self
    end
  end
end

ActiveSupport.on_load(:active_record) do
  extend Foo::Model
end