如何使用实例变量值动态创建类方法



让我们先看看有助于我想要实现的代码:

class PostalInfo
  attr_reader :name, :code
  def initialize (id, name, code)
    @id = id
    @name = name
    @code = code
  end
  def method_missing(method, *args, &blk)
    if method.to_s == "#{name}"
      return code
    else
      super
    end
  end
end
pi1 = PostalInfo.new(1, 'united_states', 'US')
pi2 = PostalInfo.new(2, 'united_kingdom', 'UK')

因此,当我运行下面的代码时,它给出的输出为:

pi1.united_states => 'US'
pi2.united_kingdom => 'UK'

到这里为止很好,但我也想做一些类似的事情

PostalInfo.united_states => 'US'
PostalInfo.united_kingdom => 'UK'

怎么做,提前谢谢

这将设置一个类属性来保存数据,并且每当初始化实例时,它都会添加到该数据结构中,并使用类似的类级method_missing

class PostalInfo
  attr_reader :name, :code
  @@postal_info = {}
  def self.method_missing(method, *args, &blk)
    name = method.to_s
    if @@postal_info[name]
      @@postal_info[name]
    else
      super
    end
  end
  def initialize (id, name, code)
    @id = id
    @name = name
    @code = code
    @@postal_info[@name] = @code
  end
  def method_missing(method, *args, &blk)
    if method.to_s == "#{name}"
      return code
    else
      super
    end
  end
end
pi1 = PostalInfo.new(1, 'united_states', 'US')
pi2 = PostalInfo.new(2, 'united_kingdom', 'UK')
PostalInfo.united_states #=> 'US'
PostalInfo.united_kingdom #=> 'UK'

我会说,这似乎是一个奇怪的设计,我通常建议避免在类方法中使用可变状态,并尽可能method_missing

你可以写这样的东西:

class PostalInfo
  POSTAL_HASH = {
    united_states: 'US',
    united_kingdom: 'UK',
  }.freeze
  def self.method_missing(method, *args, &blk)
    POSTAL_HASH[method] || super
  end
end

跳过缺少的方法可能会提高性能:

class PostalInfo
  POSTAL_HASH = {
    united_states: 'US',
    united_kingdom: 'UK',
  }.freeze
  class << self
    POSTAL_HASH.each do |name, code|
      define_method(name) do
        code
      end
    end
  end
end

除了一个例外,您需要在类的单例类中模仿答案第一部分中的代码。差异涉及实例变量的初始化。而不是使用 PostalInfo::newPostalInfo#initialize ,你需要创建一个类方法来执行此操作(我称之为 add_country_data (。请注意,由于不使用类的实例变量id,因此我没有将其包含在代码中。

class PostalInfo
  class << self
    attr_reader :country_data
    def add_country_data(name, code)
      (@country_data ||= {})[name] = code
    end
    def add_country_data(name, code)
      @country_data[name] = code
    end
    def method_missing(method, *args, &blk)
      return country_data[method.to_s] if country_data.key?(method.to_s)
      super
    end
  end
end
PostalInfo.add_country_data('united_states', 'US')
PostalInfo.add_country_data('united_kingdom', 'UK')
PostalInfo.united_states
  #=> "US" 
PostalInfo.united_kingdom
  #=> "UK" 
PostalInfo.france
  #=> NoMethodError (undefined method `france' for PostalInfo:Class)

虽然这符合您的要求,但我倾向于以更传统的方式构建类:

class PostalInfo
  attr_reader :name, :code
  @instances = []
  def initialize(name, code)
    @name = name
    @code = code
    self.class.instances << self
  end
  singleton_class.public_send(:attr_reader, :instances)
end
us = PostalInfo.new('united_states', 'US')
uk = PostalInfo.new('united_kingdom', 'UK')
us.code
  #=> "US" 
uk.code
  #=> "UK"
PostalInfo.instances
  #=> [#<PostalInfo:0x00005c1f24c5ccf0 @name="united_states", @code="US">,
  #    #<PostalInfo:0x00005c1f24c71858 @name="united_kingdom", @code="UK">] 

最新更新