如何在每个对象类型级别创建一个接口?

时间:2016-01-12 05:22:47

标签: ruby-on-rails metaprogramming delegation

在我的rails(4.2.1)app中,我有一个Type(模型),其中包含以下内容的记录:" string"," integer"等等。

我希望用户能够传入值并检查它是否是给定类型的有效对象。所以,伪代码是:

check_value(:integer, "1") #=> true
check_value(:integer, "foo") #=>false

我想随着时间的推移添加新类型,这些类型对于该类型有check_value的逻辑。

以下是我看过的一些替代方案:

1将每种类型的一种方法直接添加到Type model -

# app/models/type.rb
# inside class Type...
def check_string_value(val)
 true
end

def integer_value(val)
 begin
  Integer(val)
 rescue
  return false
 end
 return true    
end

这可行,但每次添加新字段类型时都需要我修改type.rb文件,我想避免这种情况。

每个类型的文件中每个对象方法

2:

# lib/types/integer_type/integer_type.rb
int = Type.where(name: "integer").first
class << int
  def check_value(val)
    begin
     Integer(val)
    rescue
     return false
    end
    return true   
  end
end

这个问题是我无法调用整数类型的特定实例来传递验证调用,因为我没有在我的调用代码中构造它。

所以,这些似乎都不理想 - 我想要一种技术,将来自type.rb的验证调用委托给要处理的单个类型。这可能吗?我怎么能这样做?

1 个答案:

答案 0 :(得分:2)

在Ruby中有很多方法可以做到这一点。这是一种非常基本的方式。让每个类型模块定义check方法然后&#34;注册&#34;本身与Type模块有关,例如Type.register(:integer, IntegerType)。然后Type.check(type, value)只需检查已注册的类型,如果匹配,则委托其check方法:

type.rb

module Type
  @@checkers = {}

  def self.check(type, value)
    if @@checkers.key?(type)
      @@checkers[type].check(value)
    else
      raise "No registered type checker for type `#{type}'"
    end
  end

  def self.register(type, mod)
    @@checkers[type] = mod
  end

  def self.registered_types
    @@checkers.keys
  end

  def self.load_types!
    Dir['./types/*.rb'].each do |file|
      require file
    end
  end
end

Type.load_types!

类型/ integer.rb

module Type
  module Integer
    def self.check(value)
      !!Integer(value)
    rescue ArgumentError
      false
    end
  end
end

Type.register(:integer, Type::Integer)

类型/ string.rb

module Type
  module String
    def self.check(value)
      true
    end
  end
end

Type.register(:string, Type::String)

然后......

p Type.registered_types # => [ :integer, :string ]
p Type.check(:integer, "1") # => true
p Type.check(:integer, "a") # => false
p Type.check(:string, "a") # => true

当然,使用元编程可以比这更好看(请参阅此答案的先前修订版,以获得使用Module#extend而不是将注册模块保存在简单哈希中的解决方案),或者说,延迟加载,但是你明白了。 &#34;核心&#34;类型模块不必知道其他模块的名称(您可以根据需要定义load_types!,或者完全在其他地方执行此操作)。唯一的要求是模块响应"check_#{type}"