Ruby не перестает меня удивлять! Как по мне, то самое удивительное и мощное в нем - метапрограмминг. После статического языка, в котором твой арсенал ограничен заложенными ключевыми словами и конструкциями, просто невозможно вообразить насколько развязаны твои руки и что ты можешь со всем этим делать! Используя динамику и выразительность Ruby можно превратить кусок кода практически в понятную фразу на английском.
Элементы конструкций не ограничены! Можно создавать их самому сколько угодно, получая в результате высокоуровневый и понятный почти любому человеку DSL.
Самое главное вовремя остановится… Но не сейчас)
Варианты объявления класса
Все мы привыкли объявлять наши классы используя элемент синтаксиса ruby, а именно ключевое слово class:
1 2 |
|
Но существует еще один вариант. Т.к. класс является экземпляром класса Class, то справедлива следующая запись:
1 2 3 |
|
Используя способ выше также можно указать суперкласс, просто передав его
как параметр в метод Class.new
:
1 2 3 4 5 6 7 |
|
Но есть одна интересная особенность!
Когда вы несколько раз объявляете класс используя ключевое слово class, то реально класс инициализируется только один раз. Все следующие разы класс открывается для внесения изменений. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
В случае с объявлением класса, используя метод Class.new
, ситуация
немного другая. На самом деле слово User
, которое мы используем для
инициализации экземпляров, является практически такой же переменной,
которая ссылается на экземпляр класса Class
. Поэтому код ниже дает,
может быть, вполне очевидные результаты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Получается мы взяли и заменили значение нашей переменной User
на
другое. Об этом также свидетельствует значение object_id
. Поэтому наш
новый класс User
ничего не знает о методе #name
.
Правда и тут можно сделать хитрость: добавить наш существующий класс в цепочку наследования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Немного магии
А почему бы не объявить динамически класс, как суперкласс для другого класса?
1 2 3 4 |
|
Данную фичу я использовал в одной из наших библиотек exracted_validator.
У меня есть некоторый базовый класс, который содержит общую логику для возможности вынесения валидаций из модели:
1 2 3 4 5 |
|
Но с некоторыми видами валидаций могут возникнуть проблемы, т.к. они
жестко завязаны на именование класса, в котором они объявлены. Например,
валидация uniqueness. Для того, чтобы иметь возможность использовать
такую валидацию в кастомном валидаторе, необходимо переопределить метод
.model_class
:
1 2 3 4 5 |
|
Используя последний код, все будет работать как и ожидается и все останутся довольны.
Ну почти все… Я не доволен тем, что мне принудительно нужно добавлять еще один метод каждый раз, когда буду объявлять новый валидатор. Поэтому я решил сделать небольшой рефакторинг используя метапрограмминг :-)
И вот что получилось:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
В данном примере динамически создается класс-обертка, в котором
объявляется требуемый метод .model_class
и этот метод возвращает
переданный класс, целевой для данного валидатора, модели. Цепочка
наследования может выглядеть следующим образом:
1
|
|
Вот #<Class:0x007f8ec9b604b8>
как раз и есть тот самый класс-обертка!
На этом у меня все. Спасибо за внимание!