Использование классов в хелперах

Хелперы (helpers) – функции, выполняющиеся в контексте шаблона и использующиеся для организации логики при построении html. С ростом проекта таких функций может стать достаточно много, растет количество строк в них, одни функции начинают зависеть от других, в общем – код начинает превращаться в кашу. Самый большой недостаток хелпер-функций состоит в том, что они располагаются в модулях, что делает тяжелым их логическое разделение, также все они расположены в едином пространстве имен, что может создать проблемы с именованием функций. Может возникнуть ситуация, когда будут существовать несколько функций с похожими названиями, и через некоторое время будет тяжело выбрать из них нужную.

Одним из выходов из этой ситуации может быть использование классов, которые будут инкапсулировать в себе всю логику построения сложного объекта (например, календаря). Рассмотрим небольшой пример.

Предположим, что в нашем проекте используется Twitter Bootstrap (без него сейчас никуда). Он содержит много полезных элементов, которые часто используются при построении страниц. Например, группы кнопок (button groups), которые очень симпатично выглядят и могут быть использованы, например, для переключения вкладок с какими-то данными. Html код для группы кнопок выглядит так:

</pre>
<div class="btn-group" data-toggle="buttons-radio">
 <button class="btn active" data-toggle="#1">1</button>
 <button class="btn" data-target="#2">2</button>
 <button class="btn" data-target="#3">3</button></div>
<pre>

Попробуем представить себе, как это будет выглядет в erb-коде.

<%= button_group(:radio) do |g| %>
  <%= g.button('1', target: '1', active: true) %>
  <%= g.button('2', target: '2') %>
  <%= g.button('2', target: '2') %>
<% end %>

Первым делом надо создать хелпер-функцию button_group, которая будет выглядеть следующим образом:

def button_group(type, &block)
  builder = ButtonGroupBuilder.new(type, self)
  builder.build(block)
end

Теперь посмотрим на то, как выглядит класс ButtonGroupBuilder:

class ButtonGroupBuilder
  def initialize(type, view_context)
    @type = type
    @view_context = view_context # 1

    @buttons = "" # 2
  end

  def build(block)
    block.call(self) # 3
    @view_context.content_tag(:div, @buttons.html_safe, class: 'btn-group', data: {toggle: "buttons-#{@type}"})
  end

  def button(label, options = {})
    button_class = ['btn']

    button_class << options[:class] if options[:class]
    button_class << 'active' if options[:active]

    button_class = button_class.join(' ')

    data = {toggle: 'button'}
    data[:target] = "##{options[:target]}" if options[:target]

    @buttons << @view_context.content_tag(:button, label, class: button_class, data: data)

    "" # 4
  end
end

Несколько комментариев по коду:

  1. В конструктор класса передается объект вьюшки, в контексте которой происходит генерация html-кода. Через этот объект можно вызывать стандартные рельсовые хелперы, тем самым не перегружаю интерфейс класса лишними функциями. В принципе можно через include добавить необходимые модули прямо в класс, тем самым сделать класс полностью независимым от контекста вызова. Здесь решение остается за вами.
  2. Буфер, который собирает код для кнопок.
  3. Ключевой вызов, обеспечивающий выполнение блока, переданного в хелпер buttons_group. Здесь можно избавить пользователя класса от работы с объктом класса, вызвав блок в его контексте, используя instance_eval, однако мне этот способ нравится больше, так как пользователь класса понимает с чем он работает, меньше магии.
  4. Пустая строка возвращается из особенностей работы шаблонизатора. В случае, если пользователь использует метод button как в примере приведенном выше (<%= … %>), то шаблонизатор подставит в сгенерированный html результат вызова этого метода. Возврат пустой строки в качестве результата выполнения метода, дает пользователю класса возможность не задумываться об этом.

 

Posted in RubyonRails, Tips | Leave a comment

Capistrano recipes for Redmine deployment

Redmine is good project management software, but installation process is difficult, requires many manual steps and has many small details should be kept in mind. This is because Redmine still depends on Rails 2.3 and does not use all benefits and perks that we have today. I talk about bundler, rvm. Last time I decided not to go again through that boring installation process and created Capistrano deployment script. I shared it on GitHub where you can find all details.

Posted in RubyonRails | Tagged , , | Leave a comment

Chebit 11-02

Вчера в Чебоксарах состоялась вторая конференция Chebit. По общему впечатлению все прошло лучше, чем в первый раз, было больше организованности. Мне тоже довелось выступить, с докладом “Ajax и рельсы”. Выступал я последним, тема простенькая, так что я уложился в 15 минут. К сожалению, большинство докладов были на темы, не связанные непосредственно с разработкой, так что разработчикам надо, наверное, собираться отдельно.

Posted in RubyonRails | Tagged , , , | Leave a comment

Default Capistrano Task

Развертывание приложения не менее важная задача, чем собственно его создание. Для развертывания приложений, созданных при помощи Ruby on Rails, часто используется Capistrano. Capistrano – это утилита и фрейморк, которые позволяют выполнять команды паралелльно на удаленных машинах. Для описания этих команд используется собственный DSL язык, синтаксис которого очень напоминает синтаксис Rake.

При использовании Capistrano вместе с Rails пользователь получает набор предустановленных команд, основной из которых является команда deploy. Сейчас я не буду подробно останавливаться на том, как нужно настраивать Capistrano, благо в сети полно ресурсов на эту тему. Мне же хочется акцентировать внимание на следующем – процесс развертывания достаточно сложен и ответственен, поэтому необходимо максимально снизить влияние человеческого фактора. Как можно это сделать? В случае со сложными процессами ответ, думаю, очевиден – пользователь должен иметь возможность запустить процесс “в один клик”. В случае работы с Capistrano этим кликом я для себя определил запуск команды cap deploy. Однако те команды, которые выполняются при этом по умолчанию не всегда покрывают весь процесс деплоймента (не буду ничего придумывать и в дальнейшем буду называть процесс развертывания приложения именно так).

Что же именно должно выполняться при деплое рельсового приложения? Для себя я выделил следующие действия:

  • копирование нового кода на сервер
  • копирование конфигурационных файлов (database.yml, settings.yml и т.д.)
  • запуск миграций (migrations)
  • перезапуск всех демонов (delayed_job, loop_dance)
  • перезапуск сервера приложения (в последнее время предпочтение отдаю unicorn)

При этом надо отметить, что важна очередность и то, что некоторые задачи не могу быть выполнены, если не была выполнена одна из предыдущих задач. Например, нет смысла продолжать деплой, если не были загружены новые версии конфигурационных файлов. Соблюдение всех этих условий не составляет труда в Capistrano. Итог всех этих размышлений может быть новая версия команды deploy, описанная в файле config/deploy.rb. У меня она выглядит следующим образом:

namespace :deploy do
  task :default do
    update
    transaction do
      update_code
      finalize_update
      config.setup
      migrate
    end
    restart
  end
end

Что здесь можно отметить? Как видно для обеспечения целостности процесса используется функция transaction. Перезапуск демонов осуществляется при выполнении команды restart.

Если есть вопросы, постараюсь ответить на них в комментариях.

Posted in RubyonRails, Tips | Tagged , , | Leave a comment

My ZSH theme

Тема, которую я использую в своем терминале, теперь находится в наборе тем, которые идут вместе с oh-my-zsh! Тема называется – kolo :)

Posted in linux | Tagged , , | Leave a comment