编写 Ruby Gem 时如何设置配置项

15

我正在编写一个gem,希望它可以在Rails环境中使用,也可以不依赖于Rails环境。

我有一个Configuration类来允许配置这个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

现在可以这样使用:

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

但是,我似乎无法从我的宝石中的其他类中访问我的配置变量。例如,当我在spec_helper.rb中配置宝石时:

# spec/spec_helper.rb
require "name_checker"

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

然后从我的代码中引用配置:

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

我遇到了一个未定义方法的错误:

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

我的代码有什么问题?


1
这是一篇关于配置 Ruby gems 的文章,供其他感兴趣的人参考:http://robots.thoughtbot.com/mygem-configure-block - Rimian
2个回答

18

尝试重构为:

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

def self.configure
  yield(configuration) if block_given?
end

-2
主要问题在于您使用了过多的间接性。为什么不直接这样做呢?
module NameChecker
  class << self
    attr_accessor :api_key, :log_level
  end
end

就这样搞定了吗?你也可以紧接着覆盖掉生成的两个读取器,以确保你需要的环境存在...

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

现在,实际(技术)问题是您正在定义一个名为NetChecker的类,并在定义时尝试打印假定的Configuration对象上api_key调用的返回值(因此您违反了Demeter法则)。这会失败,因为您在任何人真正有时间定义任何配置之前就定义了NetChecker。因此,在NameChecker上调用configure方法之前,实际上请求了api_key,因此它在configuration ivar中具有nil

我的建议是删除过度工程,并再次尝试;-)


1
Configuration 类的目的是允许使用配置块对 gem 进行配置。我认为这是配置 gem 的标准方式。总的来说,我认为这是一种不错的模式。但是,你所说的“(技术)问题”确实有道理,谢谢你提出。最后,为了简洁起见,我特意构造了迪米特违规问题! - David Tuite
configure块模式完全是可选的,来自Rails,不是每个人都遵循的模式。只是Rails有一些必须在配置块中发生的事情,当所有东西被引导时,主要是为了防止Rails加载过多的代码(这就是为什么他们在一个特殊的块中执行它的原因)。 - Julik
这里的问题不是过度工程化(或违反Demeter法则)。而是哪些代码先执行的问题。如果您在单个文件中运行所有OP代码,则可以正常工作。当NameChecker.configuration.api_key在配置对象填充之前被执行时,就会出现错误。 - eremzeit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接