有没有 Ruby 方法来删​​除初始化程序中的样板代码?

Is there a Ruby way to remove boilerplate code in initializers?

我写了很多 initialize 代码将 attrs 设置为参数,类似于:

  class SiteClient
    attr_reader :login, :password, :domain

    def initialize(login, password, domain='somedefaultsite.com')
      @login = login
      @password = password
      @domain = domain
    end
  end

有没有更Ruby的方法?我觉得我在一遍又一遍地编写相同的样板设置代码。

你可以像这样做得更好一点:

def initialize(login, password, site = 'somedefaultsite.com')
  @login, @password, @domain = login, password, domain
end

如果你没有可选参数,那么你可以更懒一点:

def initialize(*a)
  @login, @password, @domain = a
end

您可以使用 Ruby Struct:

class MyClass < Struct.new(:login, :password, :domain)
end

或者您可以为此尝试一些 gems,即 Virtus:

class MyClass
  include Virtus.model

  attribute :login, String
  attribute :password, String
  attribute :domain, String
end

然后(在这两种情况下):

MyClass.new(login: 'a', password: 'b', domain: 'c')

有一个名为 fattr 的 gem,您可以将其包含在 Ruby 应用程序中,或者 gem 来执行此操作。

require 'fattr'
class SiteClient
  fattr :login
  fattr :password
  fattr :domain => 'somedefaultsite.com'

  def initialize(**attributes)
    attributes.each do |k, v|
      public_send k, v
    end
  end
end

client = SiteClient.new
client.username = 'susan'
client.password = 'anything'

another_client = SiteClient.new username: 'bob', password: 'p@ssword1'

在此示例中,fattr 方法将为定义的每个属性定义 reader 和编写器方法。此外,它可以为每个属性分配一个默认值。初始化器被设置为接受键值对的散列,它将迭代通过调用属性方法来填充属性,方法是使用要分配的值。

需要注意的是,此示例将为您的属性生成 reader 和 writer 方法。在您的问题中,您只使用 attr_reader 因此如果您希望 class 数据不可变,这可能不是您所需要的。可能有一种方法可以配置 fattr 来执行此操作,但我还没有广泛使用它。

特别感谢 Avdi Grimm 和 RubyTapas 的第 276 集,这是我了解到这一点的地方。

也在 this DAS screencast, Gary Bernhardt walks through a few metaprogramming tricks that can shorten the syntax of common class constructs, one of which is the sort of initialize method you're describing. He bundled it into a gem 中,这将使您的代码看起来像

class SiteClient
  extend Cls
  takes       :login, :password, :domain
  attr_reader :login, :password, :domain
end

如果您愿意进行一些猴子修补,您也可以按照截屏视频中的方式实施 takes 方法

class Module
  def takes(*names)
    define_method(:initialize) do |*values|
      names.zip(values).each do |name, value|
        instance_variable_set(:"@#{name}", value)
      end
    end
  end
end

这从上面的示例中删除了 extend Cls 行的需要。