0

Let's say I am working on a problem where a class object will only be instantiated once, such as a Gem configuration object. The configuration class calls many classes that use the configuration class object's instance variables.

How do you access the configuration object instance variables in these other classes? Here are some example implementations (passing the config object as self versus storing the config object as a class instance variable on the Module) that hopefully help clarify:

# This example passes the Config object as self
module NumberTally
  class Config
    attr_reader :to_tally

    def initialize
      @to_tally = [1,2,3,4,5]
      perform
    end

    private

    def perform
      validate_to_tally
      output_to_tally
    end

    def validate_to_tally
      ValidateToTally.new(self).call
    end

    def output_to_tally
      OutputToTally.new(self).call
    end
  end

  class ValidateToTally
    attr_reader :to_tally

    def initialize(config)
      @to_tally = config.to_tally
    end

    def call
      raise TypeError if to_tally.any?{|num| num.class != Integer }
    end
  end

  class OutputToTally
    attr_reader :to_tally

    def initialize(config)
      @to_tally = config.to_tally
    end

    def call
      to_tally.each do |num|
        puts num
      end
    end
  end
end


#use Class instance variable in the Module
module NumberTally
  class << self
    attr_accessor :config
  end

  class Config
    attr_reader :to_tally

    def initialize
      @to_tally = [1,2,3,4,5]
      NumberTally.config = self
      perform
    end

    private

    def perform
      validate_to_tally
      output_to_tally
    end

    def validate_to_tally
      ValidateToTally.new.call
    end

    def output_to_tally
      OutputToTally.new.call
    end
  end

  class ValidateToTally
    def call
      raise TypeError if to_tally.any?{|num| num.class != Integer }
    end

    private

    def to_tally
      @to_tally ||= NumberTally.config.to_tally
    end
  end

  class OutputToTally
    def call
      to_tally.each do |num|
        puts num
      end
    end

    private

    def to_tally
      @to_tally ||= NumberTally.config.to_tally
    end
  end
end

3
  • "a class object will only be instantiated once" That wording is a bit confusing. Do you mean you want everything to use one instance of a class? Could you show an example? Commented Apr 24, 2023 at 23:35
  • Are you talking about singleton classes? Commented Apr 25, 2023 at 9:21
  • I just meant that the Config class will only be instantiated once so storing data as Module instance variables is ok. Commented Apr 25, 2023 at 17:01

2 Answers 2

1

If I understand, you want a single Config object that everything in the code-base uses. We can make something like Rails.configuration.

Make a Config object. Make class to encompass your code (like the Rails class) to hold defaults such as the configuration. Give it a class method to instantiate and offer the default config object.

class App
  class Config
    attr_accessor :numbers
  end

  class << self
    attr_writer :config
    def config
      @config ||= make_config
    end

    private def make_config
      # Do any app-specific configuration here, such as reading a config file
      # and setting defaults.
      config = Config.new
      config.numbers = [1,2,3,4,5]
    end
  end
end

# The default App::Config
config = App.config

# Change the default.
App.config = App::Config.new

Then everything can use App.config by default.

class App
  class Tally
    # Your classes have no state, no need for an object.
    class << self
      def call(to_tally = App.config.numbers)
        raise TypeError if to_tally.any?{|num| num.class != Integer }

        to_tally.each(&:puts)
      end
    end
  end

  class Thing
    attr_accessor :config

    def initialize
      @config = App.config
    end
  end
end

# Uses App.config.numbers
App::Tally.call
# Uses user applied Array.
App::Tally.call([5, 4, 3, 2, 1])

# thing.config is App.config
thing = App::Thing.new
# or maybe you want to use a different config
thing.config = App::Config.new
  1. Write a Config class that is a fairly simple Value object.
  2. Provide a class method to get the default Config object.
  3. Everything uses that class method to get the default config.
    • But can also be provided with a different config for flexibility.
    • For example, testing.
Sign up to request clarification or add additional context in comments.

2 Comments

This is useful. Thanks. To be sure, the namespace class App can be a Module instead of a Class, right?
@TenJack Yes. I don't have a strong recommendation whether it should be a class or a module. Both can be used as namespaces. It's not a mixin, but it's also never meant to be instantiated.
0

The typical patterns for making a gem configurable is to provide a simple method which yields a configuration object to to the block:

module MyGem
  def self.configuration
    @configuration
  end

  def self.configure(**attributes, &block)
    @configuration ||= Configuration.new
    @configuration.assign_attributes(**attributes) 
    yield(@configuration)
  end
end

This object is just a simple value object with some mass assignment tossed in:

module MyGem
  class Configuration
    attr_accessor :numbers, :foo, :bar, :baz

    def initialize(**attributes)
      assign_attributes(**attributes)
    end
  
    def assign_attributes(**attributes)
      attributes.each { |key, value| send "#{key}=", value }
    end
  end
end
MyGem.configure(foo: 'bar') do |config|
  config.numbers = [1, 2, 3, 4]
end

MyGem.configuration.numbers # [1, 2, 3, 4]
MyGem.configuration.foo # "bar"

Since MyGem.configuration is a module method there isn't exactly a convenvience method for calling it from other classes / modules in the same namespace. If you need that create such a method that just delegates to MyGem.configuration.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.