Так уж случилось, что возникло непреодолимое желание написать свой Rails gem. Во-первых, академический интерес — такого еще не делал, во-вторых, назрела проблема, решение которой важно лично для меня и которое хотелось бы использовать в нескольких своих проектах.
На Хабре уже были статьи про создание gem'ов (раз два три)
Но на их основе создать полноценный gem нельзя — они сильно устарели и, как правило, представляют собой перевод скупой официальной документации. А главное, они в большей части описывают создание Readme и License файлов, а собственно функционал gem'a сводится к Hello World.
Проблема
Не знаю, как у кого, а вот у меня регулярно бывает ситуация — прикрутил новую фичу локально, проверил, вроде работает. Запускаешь cap deploy, смотришь на сервер, а там
«Sorry, but something went wrong.
If you are the application owner check the logs for more information.»
Ну а дальше — ssh к серверу, cd к папке приложения и раскопка логов. Что бы ни говорили поклонники vim и emacs, но пытаться найти что-то в логе с их помощью — то еще занятие. Проще уж запустить tailf и пытаться найти руками. Есть еще rmate, но у меня он как-то не прижился.
Идея
Написать gem, который будет выводить результаты команды tail в браузер по заданному пути. Желательно, чтобы была возможность смотреть все .log файлы в папке log/
Сразу покажу, что получилось в итоге:
Все, что нужно, это прописать в Gemfile
gem 'tail'
установить gem
bundle install
и смонтировать его (config/routes.rb
) в нужную точку приложения, например
mount Tail::Engine, at: "/tail"
После этого на вашем сервере
Локально работает точно так же.
Если в приложении используется Devise, то нужно будет сначала ввести логин и пароль.
Как делать
Обычно для таких случаев используют sinatra-based gem'ы (например)
Лично я для своего gem'a не вижу в этом смысла, поскольку использую Rails и нет необходимости в синатре.
Для начала имеет смысл написать собственно приложение, которое впоследствии будет превращено в отдельный gem.
В моем случае оно будет состоять из одного конроллера и одного экшена:
class LogsController < ApplicationController
before_filter :authenticate_user! if defined? Devise
def index
@web_logger ||= Log.instance
@web_logger.n = params[:n]
log_file_name = params[:file_name] || "#{Rails.env}.log"
@files = @web_logger.tail(log_file_name)
end
end
Все просто, используем один Singleton класс, который выдает заданное количество строк из заданного .log файла. Сам класс Log интереса не представляет. Разве что сам способ получения лога:
@files.include?(file_name) ? `tail -n #{@n} log/#{file_name}`.lines : []
Ruby нативно позволяет выполнить команды ОС, просто заключив ее в вот эти (хрен его знает, как они правильно называются) символы: `tail -n 40 production.log`
— вернет последние 40 строк из файла.
Полученные строки помещаются в таблицу, слегка облагораживаются CSS и выводятся на экран.
Одна строчка JavaScript (известный фреймворк — Vanilla.js) прокручивает экран вниз, к последней строке:
'window.scrollTo(0, document.body.scrollHeight);'
Делаем gem из приложения.
Статьи на Хабре и уважаемый Ryan Bates описывали создание gem'a с помощью команды bundle gem. Последнее видео датируется ноябрем 2011 года.
Сейчас же создение Rails gem'ов рекомендуется делать через engines.
Engine — это по сути еще одно Rails приложение, которое будет запущено вместе с исходным и использующее (взаимно) его ресурсы. Сиамские близнецы, в общем. Или имплантанты, как кому больше нравится. В приложении-родителе указывается точка подключения нового engine/plugin/gem и далее по этому адресу доступна вся функциональность нового приложения. Объяснение примитивное, на пальцах, кому интересны подробности — я указал ссылки внизу.
Таким образом достаточно удобно реализовывать всяческие админки, gem'ы статистики, наблюдения за активностью и пр. В общем все то, что, с одной стороны, относительно независимо и не так тесно переплетается с основным приложением, а с другой стороны, задействует полную функциональность Rails — модели, контроллеры, представления и т.п.
В противном случае надо будет серьезно попотеть с порядком инициализации приложений, миграциями, разграничением доступа, безопасностью и т.п. Головняк тот еще, но нет ничего невозможного. Кому интересно — можно начать с вот этой статьи
Для gem'ов, работающих только с ограниченной функциональностью Rails (например создающих новый хелпер или фильтр контроллера) — лучше использовать плагины
Итак, создаем будущий gem с именем tail
rails plugin new tail --mountable
Ключ --mountable
собственно и отделяет создание mountable engine от относительно простого плагина.
Поскольку не использую работу с базой.
Очень забавная команда. Если посмотреть на структуру папок, созданную в результате, то это будет смесь обычного Rails приложения и результата команды bundle gem
.
Три основных момента:
Первое
папка lib будет основой будущего gem'a и самый главный файл в нем — engine.rb
module Tail
class Engine < ::Rails::Engine
isolate_namespace Tail
end
end
Создается модуль с именем gem'a с изолированным пространством имен, в который будут завернуты все ваши модели, контроллеры, вьюхи и классы. Т.е. вместо класса SomeClass
у вас будет Tail::SomeClass
, то же самое с routes и путями — вместо, например, messages_path
вы будете писать tail.messages_path
, если нужно будет попасть в веб-часть gem'a.
Само-собой, это делается для того, чтобы исключить конфликты имен gem'a и приложений, куда он будет монтироваться.
Обратите внимание на структуру папок app/
в приложении: в каждую из них добавлена дополнительная подпапка с названием gem'a, к которую складываются нужные файлы. Влияет на пути к файлам и хелперам.
Второе,
файл .gemspec — кроме описания песональной информации в нем содержится список зависимостей от других gem'ов и список файлов, необходимых для сборки.
lib/version.rb — номер версии gem'a. Рекомендуется использовать нотацию, описанную в semver.org. Простая штука, но обращаю на нее внимание, потому что очередная публикация gem'a без изменения версии не допускается.
И третье:
папка test/dummy — здесь содержится фейковое приложение, которое уже смонтировано (/test/dummy/config/routes.rb). Т.е для того, чтобы проверить работы gem'a, достаточно перейти в эту папкe и запустить rails server. По адресу localhost:3000/tail будет мое приложение. Действительно очень удобно.
Наполняем gem
rails generate resource log
Не использую scaffold, поскольку нет необходимости в CRUD и генерации представлений.
Переношу в сгенерированные файлы код из существующих классов. Стоит убедиться, что все работает, запустив фейковое приложение из test/dummy
.
Всё, с этого момента gem можно подключать и отлаживать в другом приложении:
gem 'tail', path: '~/projects/tail'
или
gem 'tail' , git: 'git://github.com/k2m30/tail.git' #после коммита и push на github
Главное не забывать обновлять файл с версиями и делать bundle update tail
в приложении, использующим gem после каждого изменения.
Публикация
Чтобы опубликовать gem на rubygems, нужно зарегистрироваться там.
После этого в папке gem-проекта
Для сборки gem
rake build
Вспомогательная команда
gem push
в создании gem'a не участвует, просто это легкий способ указать rubygems credetnials для релиза. Нужно ввести только один раз.
Собственно публикация.
rake release
Сразу после этого gem появится в поиске на rubygems и станет доступным через gem install для других разработчиков.
Итого
1. Чтобы создать mountable Rails gem, нужно сделать следующее:
создать скелет
rails plugin new tail --mountable
2. Наполнить его привычным Rails приложением
Есть незначительные особенности, но их мало и они хорошо описаны
Очень просто посмотреть, что же вы сделали, запустив rails server
в папке test/dummy/
3. Собрать gem
rake build
4. Зарегистрироваться на rubygems.org и указать свои логин и пароль:
gem push
Сделать релиз
rake release
Вот и всё, вы счастливый обладатель собственного gem'a
К слову сказать, на момент публикации rubygems утверждает, что мой gem скачали уже 151 раз. Не очень верится, но приятно.
Вообще при разработке упор делался на простоту установки и использования — ничего лишнего. Совсем. Хотя есть и другие варианты — например, gem webtail отправляет содержимое лога в сокет и, соответственно, можно смотреть изменения логов в реальном времени. Меня такая реализация не устраивает, хотя бы потому, что нужна дополнительная пляска с фаерволами. Хотя красиво.
Очень надеюсь, что моя статья, и, кто его знает, сам gem окажутся небесполезными и сэкономят кому-нибудь время и нервы.
Ссылки
http://wangjohn.github.io/railties/rails/gsoc/2013/07/10/introduction-to-railties.html
http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html
Rails по-русски
Сам gem на github.com
Он же на rubygems.org
Автор: k2m30