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