Search code examples
rubydependency-injectionmixinsclass-variables

What is a clean interface to set class variables in a mixin module?


In a Ruby project I'm working on, I add ActiveRecord-style, MVC functionality to model classes with a mixin architecture similar to the following:

module Model

  # Classes that mixin this module gain ActiveRecord-style class methods
  # like Model.all, Model.first, Model.last et al.
  #
  # Throughout this module, @@database_bridge will contain a reference to a
  # database ORM bridge, that does the dirty implementation of these methods.

  def all
    # Implementation stuff here, using @@database_bridge as appropriate
  end

  def first
    ...
  end

  # et al

end


class ExampleModel

  extend Model

  # Model-specific implementation goes here...

end

Calling e = ExampleModel.first would assign the first ExampleModel in the database to e.

I want to use dependency injection to set @@database_bridge at runtime, such that every class containing extend Model uses the same, specified ORM object.

How can I do this?

If I could write some kind of helper method to set up that class variable on demand, that would be great.


Solution

  • I've found a better solution than the previous, and it's much simpler than I thought (d'oh!): by prefixing a method with self. in a mixin module, a public interface to that method via Module.method becomes available.

    Thus, we simply add a setter to our module, using a self.attribute_set statement.

    In the above example, the above approach would yield the following code:

    module Model
    
      # Classes that mixin this module gain ActiveRecord-style class methods
      # like Model.all, Model.first, Model.last et al.
      #
      # Throughout this module, @@database_bridge will contain a reference to a
      # database ORM bridge, that does the dirty implementation of these methods.
    
      def all
        # Implementation stuff here, using @@database_bridge as appropriate
      end
    
      def first
        ...
      end
    
      def self.set_database_bridge(ref_to_new_bridge)
        @@database_bridge = ref_to_new_bridge
        ## any additional intialisation/sanitisation logic goes here
      end
    
      # et al
    
    end
    
    
    class ExampleModel
    
      extend Model
    
      # Model-specific implementation goes here...
    
    end
    

    Calling Model.set_database_bridge would allow us to pass in a new database bridge.

    If we don't actually need any initialisation or sanitisation logic in our helper function, there is another, more elegant approach - add an attr_accessor within a class << self block, thus:

    module Model
    
      # ...
    
      class << self
        attr_accessor :database_bridge
      end
    
    end
    

    This way, we can call Ruby's standard setter method: Model.database_bridge = the_new_bridge.

    Sweet.