Setting up configuration settings when writing a g

2019-03-24 14:21发布

I'm writing a gem which I would like to work with and without the Rails environment.

I have a Configuration class to allow configuration of the gem:

module NameChecker
  class Configuration
    attr_accessor :api_key, :log_level

    def initialize
      self.api_key = nil
      self.log_level = 'info'
    end
  end

  class << self
    attr_accessor :configuration
  end

  def self.configure
    self.configuration ||= Configuration.new
    yield(configuration) if block_given?
  end
end

This can now be used like so:

NameChecker.configure do |config|
  config.api_key = 'dfskljkf'
end

However, I don't seem to be able to access my configuration variables from withing the other classes in my gem. For example, when I configure the gem in my spec_helper.rb like so:

# spec/spec_helper.rb
require "name_checker"

NameChecker.configure do |config|
  config.api_key = 'dfskljkf'
end

and reference the configuration from my code:

# lib/name_checker/net_checker.rb
module NameChecker
  class NetChecker
    p NameChecker.configuration.api_key
  end
end

I get an undefined method error:

`<class:NetChecker>': undefined method `api_key' for nil:NilClass (NoMethodError)

What is wrong with my code?

标签: ruby rubygems
2条回答
劫难
2楼-- · 2019-03-24 15:10

Try refactoring to:

def self.configuration
  @configuration ||=  Configuration.new
end

def self.configure
  yield(configuration) if block_given?
end
查看更多
叼着烟拽天下
3楼-- · 2019-03-24 15:10

The main issue is that you've applied too much indirection. Why don't you just do

module NameChecker
  class << self
    attr_accessor :api_key, :log_level
  end
end

and be done with it? You could also override the two generated readers right afterwards so that they ensure the presence of the environment that you need...

module NameChecker
  class << self
    attr_accessor :api_key, :log_level

    def api_key
      raise "NameChecker really needs is't api_key set to work" unless @api_key
      @api_key
    end

    DEFAULT_LOG_LEVEL = 'info'

    def log_level
      @log_level || DEFAULT_LOG_LEVEL
    end

  end
end

Now, the actual (technical) problem is that you are defining a class called NetChecker and while defining it you are trying to print the return value of the api_key call on an assumed Configuration object (so you are violating the law of Demeter here). This fails, because you are defining NetChecker before anyone really has had time to define any configuration. So you are in fact requesting api_key before the configure method has been called on NameChecker, so it has nil in it's configuration ivar.

My advice would be to remove the overengineering and try again ;-)

查看更多
登录 后发表回答