Пишем простой DSL на Ruby

Andrey Viktorov
3 min readApr 3, 2017

--

Трудно найти хоть одного рубиста, ни разу не использовавшего какой-либо DSL: Sinatra, RSpec, ActiveModel — все они так или иначе являются примерами сей замечательной штуки.

Для лучшего понимания вещей, которые лежат в их основе, мы с вами склепаем небольшой класс, позволяющий объявлять хеши вот таким образом:

Основа

Объявим DSLHash, а в нем пустой статический метод build:

В дальнейшем этот метод будет возвращать нам готовый хеш.

Блоки, Proc’ы, и так далее

Для начала определим в build, действительно ли нам скормили блок. Для этого в рубях есть метод “block_given?” Изменим наш build следующим образом:

Почему не “yield”? Про это ниже.

instance_eval и method_missing

Возможно, вы видели возникающий, казалось бы, из ниоткуда params в Rails или Sinatra, содержащий параметры запроса, но явно в функцию не передаваемый. Что за магия? А магия эта именуется instance_eval

instance_eval позволяет нам запускать код в контексте определенного инстанса класса. В этом и есть главное отличие от yield, запускающего блок в текущем контексте.

Добавим вложенный класс в DSLHash:

В нем мы объявляем пустой хеш и геттер для него.

Посмотрим на наш пример и подумаем, чего же нам не хватает. Например, до сих пор не ясно, как first_name “Andrey” превратится в :first_name => “Andrey” внутри хеша.

Тут нам поможет своеобразный коллбек method_missing, который нужно задать в нашем классе-контексте. Каждый раз, когда мы вызываем несуществующий метод, Ruby будет вызвать method_missing. Конечно, если он есть.

Добавим его в наш DSLHashContext:

В name нам заботливо прилетит символ (для тех, кто не знает что эта штука называется символами, выглядит оно так: :first_name), равный имени вызванного метода.

В args нас будут ждать аргументы, а в block — соответственно, блок.

И так, теперь каждый раз, когда внутри DSLHashContext мы будем вызывать несуществующий метод, в консоль будет писаться его имя.

Теперь можно перейти и к instance_eval. Изменим наш метод build следующим образом:

Теперь мы можем попробовать запустить пример из начала статьи. И увидим мы вот что:

Именно то, что мы увидим

Прикольно, но мы не видим того, что прячется внутри social. Немного изменим method_missing:

И вот, совсем другое дело:

Скучные мелочи

Про method_missing, block_given? и instance_eval я вам уже рассказал, осталось из всего что у нас есть “добить” наш DSLHash до запланированного функционала.

Вся магия будет происходить внутри method_missing, вот так он будет выглядеть после всех метаморфоз:

Контрольный прогон:

Just as planned 👌

Репозиторий GitHub с полным кодом из этой статьи и парочкой дополнительных примеров: https://github.com/4ndv/dslhash

Угостить меня чаем или чем покрепче всегда можно тут: http://andv.xyz/buymeacoffee

--

--

No responses yet