Конфігуруємо модуль Ruby

Я думаю, ви знайомі з методом configure, який багато геми надають для конфігурації. Наприклад конфігурація carrierwave:

Як реалізувати це у своєму модулі?

Почнемо з тестів, що падають.

Тепер, коли у нас є тести, що падають, приступимо до реалізації функціональності. Насамперед оголосимо модуль, що містить метод configure.

Нам потрібне місце для зберігання нашої конфігурації. Я думаю змінна модуля добре підійде для цього.

Тут є проблема. Ми не зможемо зберігати конфігурацію у хеші. Поки я заміню хеш OpenStruct, який відповідає функціональності, яку ми збираємося отримати в кінцевому рахунку. Після цього я вже можу викликати блок усередині методу та передати йому сховище як аргумент.

Потрібна функціональність готова. Тести відбуваються.

Рефакторинг

Настав час провести рефакторинг цього рішення. Відразу видно дві проблеми:

  • Ми можемо зберігати будь що всередині нашої конфігурації. Набір методів, яким ми можемо передати значення, нічим не обмежений. Це не круто для конфігурації, тому що це ховає помилки користувача.Якщо користувач припуститься помилкиу назві конфігураційного методу,ми повинні негайно дати йому знати про це, викинувши виняток.
  • OpenStruct не дуже гарна ідея для продакшн-коду. Він набагато повільніший, ніж звичайний Struct або клас і використовує набагато більше пам'яті.
Додамо тести, щоб бути впевненими, що при виклику неіснуючого конфігураційного методу ми отримаємо виняток.

У нас є два способи виправити це. Перший — використовувати Struct із білим списком доступних конфігураційних методів.

Все виглядає чудово. Тести проходять, код простий та читаний.Але я забув одну важливу деталь. Конфігураційні значення за замовчуванням. Для них слід додати ще один тест.

Щоб уникнути перезапису конфігураційних значень у різних тестахпотрібно додати скидання попередньої конфігурації перед запуском кожного тесту. Я додам метод скидання прямо в тестовому класі, тому що він потрібен тільки для тестових потреб і не потрібно робити його частиною публічного API.

Повернімося до вирішення проблеми зі значеннями за замовчуванням. Найпростіше рішення виглядатиме так:

Мені подобається це рішення. Воно все ще дуже просто ічитане. Воно також доситьгнучке. Ми можемо встановлювати складні значення за замовчуванням і за необхідності виносити їх у окремі методи. Ми маємо також два способи отримувати конфігураційні значення: за допомогою методу і через subscript.

Це все, що я хотів поділитися сьогодні. Вихідники доступні тут: goo.gl/feCwCC

northbear запропонував альтернативний варіант, у якому замість класу використовується Struct з ініціалізатором.

У цьому випадку немає необхідності використовувати public_send для визначення subscript'а, що робить код ще простіше. Дякую, northbear!