shikhalev.*

Вообще-то я сначала собирался написать только про is-module-reset, но понял, что без упоминания ключевой зависимости — is-deep — а главное, ограничений, с нею связанных, обойтись никак нельзя.

И ключевое ограничение — это категорическая несовместимость с activesupport, поскольку is-deep monkey-патчит те же методы (с теми же целями но полностью другим способом)… Возможно, я когда-нибудь придумаю другие имена, но пока не знаю, чем корректно и идиоматично заменить deep_merge.

Начнем с того, что попроще.

is-module-reset

Собственно, задача, которая передо мной встала: это тестирование плагинов для Jekyll, в част­нос­ти — jekyll-is-hookdown, о ко­то­ром я писал ранее. Проблема возникает, в первую очередь, с тестированием хуков, поскольку Jekyll не предоставляет возможности их дерегистрации, и пересоздание объекта сайта тут не помогает, поскольку хуки хранятся в переменных модуля Jekyll.

Соответственно, новый гем тем и занимается, что сохраняет и восстанавливает внутреннее состояние модулей (и классов). Интерфейс простой:

dump(*mods)

Копирует данные модулей, возвращая Hash, который затем можно использовать для восстановления.

undump(hash)

Восстанавливает ранее сохраненные данные. Очевидным образом dump и undump используются в паре.

isolate(*mods) { ... }

Сохраняет данные указанных модулей, выполняет блок кода, восстанавливает сохраненные данные обратно.

Сохраняются только переменные объекта указанных классов и модулей и переменные класса, причем последние — только принадлежащие непосредственно указанному классу или модулю, а не унаследованные им. Что это такое и в чем разница, можно посмотреть в моей давней статье. Константы не сохраняются, чтобы не вызывать лишних предупреждений. Если у вас где-то меняется содержимое констант-контейнеров, это нужно учитывать и обрабатывать как-то иначе.

Тут я хочу подчеркнуть, что гем предназначен для использования только в тестах и не обеспечивает полноценной изоляции контекста. Это не средство для создания «песочницы» или чего-то подобного в рабочих задачах.

Данные копируются через deep_dup, со всеми его ограничениями. И тут самое время перейти к следующему гему…

is-deep

Один из множества вариантов «глубокого» копирования и слияния. Основная особенность именно этой моей реализации — слияние массивов по ключам.

Если коротко, то гем добавляет классам Array и Hash три метода:

deep_dup

«Глубокое» копирование, т.е. новый объект содержит копии (причем тоже «глубокие») элементов исходного, а не сами эти элементы.

При этом для вложенных объектов рекурсивно проверяется, поддерживает ли тот метод deep_dup, если да, то вызывается он, если нет — стандартный dup. Если dup бросает исключение (например, так делают объекты IO), берется сам исходный объект.

deep_merge!(other, array_strategy: nil)

«Глубокое» слияние «на месте», т.е. внутрь объекта, на котором вызван метод.

deep_merge(other, array_strategy: nil)

«Глубокое» слияние в новый объект.

Причем данный метод определен в общем виде в модуле IS::Deep примерно как deep_dup.deep_merge!(...) (на са­мом деле сложнее, но не суть).

Стратегию необязательно передавать при каждом вызове, можно указать присвоив значение IS::Deep::array_strategy.

Теперь что касается стратегий. Определены следующие варианты:

IS::Deep::ArrayStrategies::REPLACE

Заменяет старый массив новым.

IS::Deep::ArrayStrategies::CONCAT

Тупо плюсует новый массив к старому.

IS::Deep::ArrayStrategies::UNION

Объединяет уникальные значения.

IS::Deep::ArrayStrategies::KeyBased

А это уже класс, объекты которого создаются с указанием поля ключа. Для наиболее распространенных вариантов есть предсозданные в константе IS::Deep::ArrayStrategies::KEY_BASED — это хеш с ключами :detect (пытается найти один их этих вариантов), :id, :name, :key, :host.

Произвольный callable-объект

В первую очередь имеются в виду лямбды и другие объекты класса Proc, но необязательно. Главное, нужен метод call, принимающий на вход два массива и возвращающий один.

Замечу, что ни deep_dup, ни deep_merge! ничего не знают о внутренних переменных объектов. Для ваших собственных классов, если нужно, следует заинклюдить IS::Deep и определить deep_dup и deep_merge! так, как вам будет угодно.

Подробнее см. в README репозитория inat-get/is-deep.

Вместо итого

Оба гема в принципе рабочие, но, как обычно, версия 0.8.x означает публичную альфу, не более того.