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

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

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

Основа

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

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

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

instance_eval и method_missing

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, вот так он будет выглядеть после всех метаморфоз:

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

Just as planned 👌

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

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

Immolate Improved