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> как раз и есть тот самый класс-обертка!
На этом у меня все. Спасибо за внимание!