Как создать интерфейс на уровне типа объекта?
В моем приложении 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 ответ:
Есть несколько способов сделать это в 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}"
.