Как создать интерфейс на уровне типа объекта?


В моем приложении rails (4.2.1) у меня есть тип (модель), который содержит записи с :именем "string", "integer" и т. д.

Я хочу, чтобы пользователь мог передавать значения и проверять, является ли он допустимым объектом данного типа. Таким образом, псевдокод:

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

Я хотел бы добавить новые типы с течением времени, которые имеют свою собственную логику для check_value для этого типа.

Вот несколько вариантов, которые я рассмотрел:

1 добавьте один метод на тип непосредственно в модель типа -

# 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

Это будет работать, но потребует, чтобы я изменил тип.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
Проблема с этим заключается в том, что я не могу вызвать этот конкретный экземпляр целочисленного типа для передачи в вызове проверки, так как я не строю его в своем вызывающем коде.

Итак, ни один из них не кажется идеальным - я хотел бы технику, которая делегирует вызов verify от типа.РБ от типа обработки. Возможно ли это? Как я мог это сделать?

1 2

1 ответ:

Есть несколько способов сделать это в Ruby. Вот один чрезвычайно простой способ. Пусть каждый модуль типа определяет метод check, а затем "регистрирует" себя с модулем типа, например, Type.register(:integer, IntegerType). Тогда Type.check(type, value) нужно только проверить зарегистрированные типы и, если один соответствует, делегировать его методу check:

Тип.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!

Типы / целое число.rb

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

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

Типы / строка.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 вместо хранения зарегистрированных модулей в простом хэше) или, скажем, ленивой загрузки, но вы получите идею. Модуль типа "ядро" не должен знать имена других модулей (и вы можете определить load_types!, как хотите, или сделать это где-то еще). Единственное требование состоит в том, чтобы модули отвечали на "check_#{type}".