Очень хотелось изучить Ruby получше, а рабочего проекта не было. И я попробовал написать gem для работы с Yandex Direct API. Причин было несколько. Среди них: Yandex Direct API очень типичен для Яндекса и современных REST-сервисов вообще. Если разобраться и преодолеть типичные ошибки, то можно легко и быстро написать аналоги для прочих API Яндекса (и не только). И ещё: у всех аналогов, которые мне удалось найти, были проблемы с поддержкой версий Директа: одни были заточены под 4, другие под новую 5, и поддержке units я нигде не нашёл. Основная идея gem-а — раз в языке вроде Ruby или Python можно создавать новые методы и JSON-подобные объекты на лету, то методы интерфейс для доступа к REST-сервису могут повторять функции самого Rest-сервиса. Чтобы можно было писать так: А вместо того, чтобы писать справку, отсылать пользователей к мануалам по указанному API. Методы из старых версий вызывать, например, так: На тот случай, если вам не интересно читать, а хочется попробовать — готовый gem можно взять отсюда: О получении omniauth-token из rails можно узнать из примера по twitter. А названия методов и процедура регистрации очень подробно расписана в документации от Яндекса. Если интересны подробности — они дальше. Разумеется, в статье описан самый базовый опыт и самые простые вещи. Но она может быть полезна начинающим (вроде меня), как памятка по созданию типового gem-а. Собирать информацию по статьям, конечно, интересно, — но долго. Наконец, может быть, что кому-то из читателей действительно надо по быстрому добавить поддержку Yandex Direct API в свой проект. А ещё она будет полезна мне — в плане фидбека. Для начала зарегистрируемся в Yandex Direct, создадим там тестовое приложение и получим для него временный Token. Потом откроем справку по Yandex Direct API и поучимся вызывать методы. Как-нибудь так: Для версии 5: Для версии 4 Live (Token подходит к обоим): Эти скрипты уже годятся для отладки и быстрых тестовых запросов. Но, как учит нас (мифический) человеко-месяц, скрипт для себя и библиотека для других — это два разных класса приложений. И чтобы передалать один в другой, предстоит попотеть. Для начала надо было определиться с названием — простым и не занятым. И пришёл к выводу, что ya-api-direct — это то, что надо. Во-первых, сама структура логична — и если появится, к примеру, ещё и ya-api-weather, то будет ясно, к чему он относится. Во-вторых, у меня всё-таки не официальный продукт от Яндекса, чтобы использовать торговую марку как префикс. К тому же, это намёк на ya.ru, где бережно хранится прежний лаконичный дизайн. Создавать руками все папки немного лениво. Пусть за нас это сделает bundler: В качестве средства для UnitTest я указал minitest. Потом будет ясно, почему. Теперь у нас есть папка, и в ней готовый для сборки gem. Его единственный недостаток в том, что он совершенно пуст. Но сейчас мы это исправим. UnitTest-ы невероятно полезны для выявления хитро спрятаных багов. Почти каждый программист, который всё-таки сподобился их написать и исправил попутно пару десятков багов, что затаились в исходниках, обещает себе, что будет их теперь писать всегда. Но всё равно не пишет.  В некоторых проектах (наверное, их пишут особенно неленивые программисты) есть одновременно и test и spec-тесты. Но в последних версиях minitest вдруг научился spec-интерфейсу, и я решил обойтись и одними spec-ами. Так как интерфейс у нас онлайновый и, к тому же, за каждый запрос с нас списываются баллы, мы подделаем ответы от Yandex Direct API. Для этого нам потребуются хитрый gem webmock. Добавляем в gems Обновляем, переименовываем папку test в spec. Так как я торопился, то тесты написал только для внешних интерфейсов. webmock подменяет методы стандартных библиотек для работы с HTTP, чтобы при запросах с определёнными телом и заголовками возвращался соответствующий ответ.  Если вы ошиблись настройке, это не страшно. Когда вы попытаетесь отправить запрос, которого нет в фильтре, то webmock сообщит об ошибке и даже подскажет, как написать стаб правильно. И пишем spec-и: Rake реализован настолько гибко и просто, что чуть ли не в каждой библиотеке он устроен по-своему. Поэтому я просто велел ему запускать все файлы, которые назваются spec_*.rb и лежат в директории spec: Теперь наши spec-и можно вызывать так: Или даже: Правда, тестировать ему пока нечего. Сначала заполяем с информацией о gem-е (без этого bundle откажется запускаться). Потом пишем в gemspec, какие сторонние библиотеки будем использовать. Делаем и отправляемся в lib создавать файлы. Файлы у нас будут такие: Для начала создадим файл с константами, в который и запишем все функции из API. contants.rb Теперь создадим базовый сервис-обёртку, от которого мы унаследуем сервис для версий 4 и 5. direct_service_base.rb В конструкторе он получает исходный клиент и список методов. А потом создаёт их внутри себя через :define_method. А почему нам не обойтись методом respond_to_missing? (как до сих пор делают многие gem-ы)? Потому что он медленней и не такой удобный. И без того небыстрый интерпретатор попадает в него после исключения и проверки в is_respond_to_missing?.. К тому же, созданные таким образом методы попадают в результаты вызова methods, а это удобно для отладки. Теперь создадим сервис для версий 4 и 4 Live. direct_service_v4.rb В версии 5 сервер не просто отвечает на запросы пользователя, но ещё и сообщает, сколько баллов потрачено на последнем запросе, сколько осталось и сколько их было в текущей сессии всего. Наш сервис должен уметь их разбирать (но мы пока не написали, как он это сделает). Но мы заранее укажем, что он должен обновлять поля в основном клиентском классе. direct_service_v5.rb Кстати, вы заметили, что за вызов запроса отвечает какой-то загадочный gateway? Класс Gateway обеспечивает запросы. В него переехала большая часть кода из нашего скрипта.
gateway.rb Стандартый Net::HTTP задействован, потому что прост как грабли. Вполне можно посылать запросы и из faraday. На ней и так работает OmniAuth (про который я расскажу ниже), так что лишними gem-ами приложение не обрастёт. Наконец, UrlHelper заполняем статичными функциями, которые генерируют URL, разбирают данные и парсят Units (что несложно): Если сервер вернул ошибку, мы кидаем Exception с её текстом. Код выглядит самоочевидным и это весьма хорошо. Самоочевидный код легче поддерживать. Теперь нам нужно написать сам класс клиента, с которым взаимодействуют внешние интерфейсы. Так как большая часть функционала уже переехала во внутренние классы, то он будет очень коротким. Методы версии 4 записываются в свойство v4, методы версии 5, сгруппированные по отдельным сервисам, становятся методами класса клиента через уже знакомую нам конструкцию. Теперь, когда мы вызываем client.campaigns.get Ruby сначала выполнит client.campaigns(), а потом вызовет у полученного сервиса метод get. Последняя срока конструктора нужна, чтобы класс можно было использовать в конструкции do… end. Сразу после инициализации же выполняет (если это указано в настройках) start_cache!, чтобы послать API команду на включение кэширования. Версия в настройках влияет только на это, из экземпляра класса можно вызывать методы обоих версий. Полученная дата будет доступна в свойстве cache_timestamp. А в свойстве units_data будут лежать последние сведения по Units. Также в проекте есть класс с настройками версии и исключения. С ними всё настолько понятно, что даже и сказать нечего. Класс с настройками версий и вовсе сгенерирован bundle вместе с проектом. Ну а файле direct.rb нужно указать те классы, которые должны быть видны пользователю снаружи. В нашем случае это только класс клиента. Плюс версия и исключение (он они совсем служебные). Чтобы cкомпилировать gem, можно следовать мануалу с RubyGems.org (там ничего сложного). Или применить Mountable Engine из Rails. А потом загружаем на rubygems — вдруг этот gem может быть полезен не только нам. Войти из Rails в Yandec API и получить токен — дело очень простое для любого разработчика… если не в первый раз. Как мы уже узнали, для доступа к Direct API требуется токен. Из справки от Яндекса следует, что перед нами — старый добрый OAuth2, которым пользуется куча сервисов, включая Twitter и Facebook. Для Ruby есть классический gem omniauth, от которого и наследуют реализации OAuth2 для различных сервисов. Уже реализован и omniauth-yandex. С ним мы и попытаемся разобраться. Создадим новое rails приложение (добавлять в рабочие проекты будем после того, как научимся). Добавляем в Gemfile: И делаем bundle install. А потом пользуемся любым мануалом по установке Omniauth-аутенфикации для rails. Вот пример для twitter. Переводить и пересказывать его, я думаю, не стоит — статья и так получилась огромная. У меня описанный в статье пример заработал. Единственной поправкой было то, что я не стал писать в таблицу User дополнительные индексы, потому что их не поддерживает SQLite. Правда, в статье не указано, где скрывается token. Но это совсем не секрет. В SessionController его можно будет получить через Только не забывайте — каждая такая аутенфикация генерирует token заново. И если вы потом попытаетесь использовать скрипты с прямым указанием token, то сервер будет говорить, что старый уже не подходит. Надо вернуться в настройки приложения Яндекса, снова указать отладочный callback URL (__https://oauth.yandex.ru/verification_code__), а затем заново сгенерировать token. А ещё лучше — создать для статичного токена отдельное приложение, чтобы отлаживать не мешал. ya-api-direct на GitHub Настройка omniauth-twitter Программист