...

вторник, 18 марта 2014 г.

Rails 4 Engines. Разработка gem'а через mountable engine — читаем логи сервера



Так уж случилось, что возникло непреодолимое желание написать свой 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 plugin new tail --mountable --skip-active-record

Поскольку не использую работу с базой.





Очень забавная команда. Если посмотреть на структуру папок, созданную в результате, то это будет смесь обычного 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://ift.tt/18fRV12

http://ift.tt/1cVANQZ

Rails по-русски

Сам gem на github.com

Он же на rubygems.org

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий