Como usar os auxiliares do Rails: uma demonstração do carrossel do Bootstrap
Publicados: 2022-03-11Uma das mais mal utilizadas, mal compreendidas e negligenciadas de todas as estruturas internas do Rails é o view helper . Localizados em seu diretório app/helpers
e gerados por padrão a cada novo projeto Rails, os helpers geralmente têm uma má reputação por serem um depósito de lixo para métodos únicos usados em toda a camada de visualização da aplicação. Infelizmente, o próprio Rails encoraja essa falta de estrutura e organização ruim ao incluir todos os auxiliares em todas as visualizações por padrão, criando um namespace global poluído.
Mas e se seus ajudantes pudessem ser mais semânticos, mais bem organizados e até reutilizáveis em todos os projetos? E se eles pudessem ser mais do que apenas funções pontuais espalhadas por toda a exibição, mas métodos poderosos que geraram marcação complexa com facilidade, deixando suas exibições livres de lógica e código condicionais?
Vamos ver como fazer isso ao construir um carrossel de imagens, com o conhecido framework Bootstrap do Twitter e uma boa e velha programação orientada a objetos.
Quando usar auxiliares Rails
Existem muitos padrões de design diferentes que podem ser usados na camada de visualização do Rails: apresentadores, decoradores, parciais, bem como auxiliares, só para citar alguns. Minha regra simples é que os auxiliares funcionam muito bem quando você deseja gerar marcação HTML que requer uma determinada estrutura, classes CSS específicas, lógica condicional ou reutilização em diferentes páginas.
O melhor exemplo do poder dos auxiliares Rails é demonstrado pelo FormBuilder
com todos os seus métodos associados para gerar campos de entrada, selecionar tags, rótulos e outras estruturas HTML. Esses métodos úteis geram marcação para você com todos os atributos relevantes definidos corretamente. Conveniência como essa é o motivo pelo qual todos nós nos apaixonamos pelo Rails em primeiro lugar.
Os benefícios de usar auxiliares bem elaborados são os mesmos de qualquer código limpo e bem escrito: encapsulamento, redução de repetição de código (DRY) e manter a lógica fora da visão.
Anatomia de um carrossel de Bootstrap do Twitter
O Twitter Bootstrap é uma estrutura de front-end amplamente usada que vem com suporte interno para componentes comuns, como modais, guias e carrosséis de imagens. Esses componentes do Bootstrap são um ótimo caso de uso para ajudantes personalizados porque a marcação é altamente estruturada, requer que certas classes, IDs e atributos de dados sejam definidos corretamente para que o JavaScript funcione e a configuração desses atributos requer um pouco de lógica condicional.
Um carrossel Bootstrap 3 tem a seguinte marcação:
<div class="carousel slide" data-ride="carousel"> <!-- Indicators --> <ol class="carousel-indicators"> <li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li> <li data-target="#carousel-example-generic" data-slide-to="1"></li> <li data-target="#carousel-example-generic" data-slide-to="2"></li> </ol> <!-- Wrapper for slides --> <div class="carousel-inner"> <div class="item active"> <img src="..." alt="..."> </div> <div class="item"> <img src="..." alt="..."> </div> ... </div> <!-- Controls --> <a class="left carousel-control" href="#carousel-example-generic" data-slide="prev"> <span class="glyphicon glyphicon-chevron-left"></span> </a> <a class="right carousel-control" href="#carousel-example-generic" data-slide="next"> <span class="glyphicon glyphicon-chevron-right"></span> </a> </div>
Como você pode ver, existem três estruturas principais: (1) os indicadores (2) os slides de imagem (3) os controles de slide.
O objetivo é ser capaz de construir um único método auxiliar que pegue uma coleção de imagens e renderize todo este componente de carrossel, garantindo que data, id
, atributos href
e classes CSS estejam todos configurados corretamente.
O Ajudante
Vamos começar com um esboço básico do auxiliar:
# app/helpers/carousel_helper.rb module CarouselHelper def carousel_for(images) Carousel.new(self, images).html end class Carousel def initialize(view, images) @view, @images = view, images end def html # TO FILL IN end private attr_accessor :view, :images end end
O método auxiliar carousel_for
retornará a marcação completa do carrossel para os URLs de imagem fornecidos. Em vez de construir um conjunto de métodos individuais para renderizar cada parte do carrossel (o que exigiria que passássemos a coleção de imagens e outras informações de estado para cada método), criaremos uma nova classe Ruby simples chamada Carousel
para representar os dados do carrossel. Esta classe irá expor um método html
que retorna a marcação totalmente renderizada. Nós o inicializamos com a coleção de imagens de URLs de images
e a visualização de contexto de view
.
Observe que o parâmetro view
é uma instância de ActionView
, na qual todos os auxiliares do Rails são misturados. Nós o passamos para nossa instância de objeto para obter acesso aos métodos auxiliares internos do Rails, como link_to
, content_tag
, image_tag
e safe_join
, que usaremos para construir a marcação dentro da classe. Também adicionaremos a macro delegate
, para que possamos chamar esses métodos diretamente, sem nos referirmos a view
:
def html content = view.safe_join([indicators, slides, controls]) view.content_tag(:div, content, class: 'carousel slide') end private attr_accessor :view, :images delegate :link_to, :content_tag, :image_tag, :safe_join, to: :view def indicators # TO FILL IN end def slides # TO FILL IN end def controls # TO FILL IN end
Sabemos que um carrossel é composto de três componentes separados, então vamos stub métodos que eventualmente nos darão a marcação para cada um, então faça com que o método html
os junte em uma tag div
de contêiner, aplicando as classes Bootstrap necessárias para o próprio carrossel.

safe_join
é um método interno útil que concatena uma coleção de strings e chama html_safe
no resultado. Lembre-se, temos acesso a esses métodos por meio do parâmetro view
, que passamos quando criamos a instância.
Vamos construir os indicadores primeiro:
def indicators items = images.count.times.map { |index| indicator_tag(index) } content_tag(:ol, safe_join(items), class: 'carousel-indicators') end def indicator_tag(index) options = { class: (index.zero? ? 'active' : ''), data: { target: uid, slide_to: index } } content_tag(:li, '', options) end
Os indicadores são uma lista ordenada simples ol
que possui um elemento li
de item de lista para cada imagem na coleção. O indicador de imagem ativo no momento precisa da classe CSS active
, portanto, garantiremos que ele esteja definido para o primeiro indicador que criarmos. Este é um ótimo exemplo de lógica que normalmente teria que estar na própria visão.
Observe que os indicadores precisam referenciar o id
exclusivo do elemento carrossel que o contém (caso haja mais de um carrossel na página). Podemos facilmente gerar esse id
no inicializador e usá-lo no restante da classe (especificamente nos indicadores e nos controles). Fazer isso programaticamente dentro de um método auxiliar garante que o id
seja consistente em todos os elementos do carrossel. Há muitas vezes em que um pequeno erro de digitação ou alteração do id
em um lugar, mas não nos outros, causará a quebra de um carrossel; isso não acontecerá aqui porque todos os elementos referenciam automaticamente o mesmo id
.
def initialize(view, images) # ... @uid = SecureRandom.hex(6) end attr_accessor :uid
A seguir estão os slides da imagem:
def slides items = images.map.with_index { |image, index| slide_tag(image, index.zero?) } content_tag(:div, safe_join(items), class: 'carousel-inner') end def slide_tag(image, is_active) options = { class: (is_active ? 'item active' : 'item'), } content_tag(:div, image_tag(image), options) end
Simplesmente iteramos sobre cada uma das imagens que passamos para a instância Carousel
e criamos a marcação adequada: uma tag de imagem envolvida em uma div
com a classe CSS do item
, novamente nos certificando de adicionar a classe active
à primeira que criamos.
Por fim, precisamos dos controles Anterior/Próximo:
def controls safe_join([control_tag('left'), control_tag('right')]) end def control_tag(direction) options = { class: "#{direction} carousel-control", data: { slide: direction == 'left' ? 'prev' : 'next' } } icon = content_tag(:i, nil, class: "glyphicon glyphicon-chevron-#{direction}") control = link_to(icon, "##{uid}", options) end
Criamos links que controlam o movimento do carrossel entre as imagens. Observe o uso de uid
novamente; não precisa se preocupar em não usar o ID correto em todos os diferentes locais da estrutura do carrossel, ele é automaticamente consistente e exclusivo.
O produto acabado:
Com isso, nosso ajudante de carrossel está completo. Aqui está na íntegra:
# app/helpers/carousel_helper.rb module CarouselHelper def carousel_for(images) Carousel.new(self, images).html end class Carousel def initialize(view, images) @view, @images = view, images @uid = SecureRandom.hex(6) end def html content = safe_join([indicators, slides, controls]) content_tag(:div, content, id: uid, class: 'carousel slide') end private attr_accessor :view, :images, :uid delegate :link_to, :content_tag, :image_tag, :safe_join, to: :view def indicators items = images.count.times.map { |index| indicator_tag(index) } content_tag(:ol, safe_join(items), class: 'carousel-indicators') end def indicator_tag(index) options = { class: (index.zero? ? 'active' : ''), data: { target: uid, slide_to: index } } content_tag(:li, '', options) end def slides items = images.map.with_index { |image, index| slide_tag(image, index.zero?) } content_tag(:div, safe_join(items), class: 'carousel-inner') end def slide_tag(image, is_active) options = { class: (is_active ? 'item active' : 'item'), } content_tag(:div, image_tag(image), options) end def controls safe_join([control_tag('left'), control_tag('right')]) end def control_tag(direction) options = { class: "#{direction} carousel-control", data: { slide: direction == 'left' ? 'prev' : 'next' } } icon = content_tag(:i, '', class: "glyphicon glyphicon-chevron-#{direction}") control = link_to(icon, "##{uid}", options) end end end
O ajudante em ação:
Finalmente, para esclarecer o ponto, vejamos um exemplo rápido de como esse ajudante pode tornar nossas vidas mais fáceis. Digamos que estamos construindo um site para anúncios de aluguel de apartamentos. Cada objeto Apartment
tem uma lista de URLs de imagem:
class Apartment def image_urls # ... end end
Com nosso auxiliar de carrossel, podemos renderizar todo o carrossel Bootstrap com uma única chamada para carousel_for
, removendo completamente a lógica bastante complexa da visão:
<% apartment = Apartment.new %> # ... <%= carousel_for(apartment.image_urls) %>
Não tem certeza de quando usar os auxiliares de visualização do Rails? Aqui está uma demonstração.
Tweet
Conclusão
Usando essa técnica simples, mas poderosa, movemos o que seria uma quantidade significativa de marcação e lógica para fora da camada de visualização e para uma função auxiliar que pode ser usada para renderizar componentes do carrossel em qualquer lugar com apenas uma chamada carousel_for(some_images)
. Este helper genérico pode ser usado em todos os seus projetos Rails sempre que você estiver usando o Twitter Bootstrap. Mais importante, agora você tem uma nova ferramenta em seu kit de ferramentas que também pode ser usada para componentes específicos do projeto.
Portanto, da próxima vez que você estiver digitando e redigitando o mesmo tipo de marcação e incorporando lógica condicional em suas visualizações, veja se uma função auxiliar está apenas esperando para ser escrita para facilitar sua vida.