如何在Ruby中在运行时重写动态创建的setter方法?

问题描述 投票:0回答:2

其他人可以帮助我通过一个与覆盖先前定义的方法有关的方案吗?

我有一个可以接收Hash的类,以便在运行时为每个键值对创建实例变量。如果值是Hash,则我们需要实例化一个新的Config类,并将其分配给上下文中的实例变量,并且该类在initialize方法中固定。]

而且,此类必须响应不同的方法,如果不存在它们,那么我们需要即时创建它们。这将通过覆盖method_missing方法并评估给定方法的值是否为Hash来覆盖,然后应用与初始化程序中相同的逻辑。

class Config
  def initialize(parameters = {})
    raise ArgumentError.new unless parameters.is_a?(Hash)
    parameters.each do |key, value|
      raise ArgumentError.new unless key.is_a?(String) || key.is_a?(Symbol)
      create_new_method_on_instance(key, value)
    end
  end

  def method_missing(method_name, *args)
    name = method_name.to_s.delete_suffix('=')
    create_new_method_on_instance(name, args.first)
  end

  private

  def create_new_method_on_instance(name, value)
    singleton_class.send(:attr_accessor, name)
    if value.is_a?(Hash)
      instance_variable_set("@#{name}", Config.new(value))
    else
      instance_variable_set("@#{name}", value)
    end
  end
end

一切正常,但是问题在于,现在我需要即时重写foo方法。例如,首先创建一个Config.new({foo => 23})对象,该对象将具有一个foo实例变量,然后我想像这样config.foo = {x: 23}传递一个新值(重新分配它)。由于此新值是hash,因此我需要对其进行拦截,并应用与以前相同的逻辑,使用该值创建一个新的Config对象,并将其分配给foo实例变量。

这里的问题是,由于已经定义了foo方法,因此我无法在method_missing方法中拦截其新分配以应用所需的逻辑。当我们动态调用setter方法时,有人知道一种拦截方法吗?

测试:

describe 'VerifiedConfig' do
  it 'should return nil for non-existing config values' do
    config = Config.new

    expect(config.foo).to be_nil
    expect(config.bar).to be_nil
  end

  it 'should allow assigning new simple config values' do
    config = Config.new

    config.foo = 13
    config.bar = "foo-bar"

    expect(config.foo).to eq(13)
    expect(config.bar).to eq("foo-bar")
  end

  it 'should allow assigning hash values' do
    config = Config.new

    config.foo = {bar: {'baz' => 'x'}}
    config.bar = {'foo' => {bar: [12, 13], baz: 14}}

    expect(config.foo).to be_a(Config)
    expect(config.foo.bar).to be_a(Config)
    expect(config.foo.bar.baz).to eq('x')
    expect(config.bar.foo.bar).to eq([12, 13])
    expect(config.bar.foo.baz).to eq(14)
  end

  it 'should allow initialization through constructor' do
    config = Config.new({'foo' => {bar: [12, 13], baz: 14}})

    expect(config.foo.bar).to eq([12, 13])
    expect(config.foo.baz).to eq(14)
  end

  it 'should override values' do
    config = Config.new({'foo' => {bar: 'baz'}})

    config.foo = 10
    config.foo = {x: {y: 'z'}}

    expect(config.foo.x.y).to eq('z')
  end

  it 'should raise an error when keys have illegal type' do
    config = Config.new

    expect {config.x = {14 => 15}}.to raise_error(ArgumentError)
  end

  it 'should not accept anything that Hash in the constructor' do
    expect {Config.new(11)}.to raise_error(ArgumentError)
    expect {Config.new('test')}.to raise_error(ArgumentError)
  end
end

这是失败的情况:

it 'should override values' do
  config = Config.new({'foo' => {bar: 'baz'}})

  config.foo = 10
  config.foo = {x: {y: 'z'}}

  expect(config.foo.x.y).to eq('z')
end

注意:

我无法使用OpenStruct

[还有其他人可以帮助我解决与覆盖先前定义的方法有关的一种情况吗?我有一个可以接收哈希值的类,以便在运行时为每个...创建实例变量...

ruby hash metaprogramming dynamic-programming
2个回答
0
投票

而不是使用默认的getter和setter:


0
投票

这是另一种可能的解决方法。有点相似,但这通过overriding

© www.soinside.com 2019 - 2024. All rights reserved.