2. type
– тип объекта недвижимости.
3. class
– класс, частью которого является свойство.
4. priority
– необязательное числовое значение из аннотации.
5. id
– необходимое числовое значение из аннотации.
Конечно, то, какие данные вам нужны, во многом зависит от конкретного варианта использования, но, как правило, имя, тип и класс полезно иметь во всех случаях. Тип может быть, например, типом переменной экземпляра или типом возвращаемого значения метода.
Мы можем использовать макрос record
, чтобы упростить создание нашей структуры. В конечном итоге это будет выглядеть так:
abstract struct MetadataBase; end
record PropertyMetadata(ClassType, PropertyType, Propertyldx)
< MetadataBase,
name : String,
id : Int32,
priority : Int32 = 0 do
def class_name : ClassType.class
ClassType
end
def type : PropertyType.class
PropertyType
end
end
Мы используем дженерики, чтобы указать тип класса и переменную экземпляра. У нас также есть еще одна универсальная переменная, с которой мы вскоре разберемся. Мы представили эти дженерики как методы, поскольку универсальные типы уже будут ограничены каждым экземпляром, и поэтому нет необходимости также хранить их как переменные экземпляра.
У каждой записи будет имя, и мы также добавили к ней два дополнительных свойства. Поскольку значение priority
является необязательным, мы установили для него значение по умолчанию, равное 0
, тогда как идентификатор является обязательным, поэтому у него нет значения по умолчанию.
Далее нам нужно создать модуль, который будет создавать и предоставлять хеш метаданных свойств. Мы можем использовать некоторые концепции макросов, которые мы изучили несколько глав назад, такие как макроперехваты и дословное выполнение. В конечном итоге этот модуль будет выглядеть так:
annotation Metadata; end
module Metadatable
macro included
class_property metadata : Hash(String, MetadataBase) do
{% verbatim do %}
{% begin %}
{
{% for ivar, idx in @type.instance_vars.select &.
annotation Metadata %}
{{ivar.name.stringify}} => (PropertyMetadata(
{{@type}}, {{ivar.type.resolve}},{{idx}}
).new({{ivar.name.stringify}},
{{ivar.annotation(Metadata).named_args
.double_splat}}
)),
{% end %}
} of String => MetadataBase
{% end %}
{% end %}
end
end
end
Мы также используем блочную версию макроса class_getter
для определения ленивого метода получения. Включенный хук используется для того, чтобы гарантировать, что метод получения определен внутри класса, в который включен модуль. Функции дословного макроса и начала также используются для обеспечения выполнения кода дочернего макроса в контексте включающего типа, а не самого модуля.
Фактическая логика макроса довольно проста и делает многое из того, что мы делали в предыдущем разделе. Однако в этом примере мы также передаем некоторые общие значения при создании экземпляра нашего экземпляра PropertyMetadata
.
На этом этапе наша логика готова к испытанию. Создайте класс, включающий модуль и некоторые свойства, использующие аннотацию, например:
class MyClass
include Metadatable
@[Metadata(id: 1)]
property name : String = "Jim"
@[Metadata(id: 2, priority: 7)]
property created_at : Time = Time.utc
property weight : Float32 = 56.789
end
pp MyClass.metadata["created_at"]
Если бы вы запустили эту программу, вы бы увидели, что она выводит экземпляр PropertyMetadata
со значениями из аннотации и самой переменной экземпляра, установленными правильно. Однако есть еще одна вещь, с которой нам нужно разобраться; как мы можем получить доступ к значению связанного экземпляра метаданных? Именно это мы и собираемся исследовать дальше.
Доступ к значению
Малоизвестный факт об обобщениях заключается в том, что в качестве значения универсального аргумента можно также передать число. В первую очередь это сделано для поддержки типа StaticArray
, который использует синтаксис StaticArray(Int32, 3)
для обозначения статического массива из трех значений Int32
.