Cómo usar Rails Helpers: una demostración de carrusel Bootstrap
Publicado: 2022-03-11Una de las estructuras integradas de Rails que más se usa mal, se malinterpreta y se descuida es el asistente de vista . Ubicados en el directorio app/helpers
y generados de forma predeterminada con cada nuevo proyecto de Rails, los helpers a menudo tienen una mala reputación por ser un vertedero de métodos únicos utilizados en toda la capa de vista de la aplicación. Desafortunadamente, el mismo Rails fomenta esta falta de estructura y mala organización al incluir a todos los ayudantes en cada vista de forma predeterminada, creando un espacio de nombres global contaminado.
Pero, ¿y si sus ayudantes pudieran ser más semánticos, mejor organizados e incluso reutilizables en todos los proyectos? ¿Qué pasaría si pudieran ser más que solo funciones únicas esparcidas por toda la vista, sino métodos poderosos que generaran marcas complejas con facilidad, dejando sus vistas libres de código y lógica condicional?
Veamos cómo hacer esto al construir un carrusel de imágenes, con el marco familiar de Twitter Bootstrap y algo de buena programación orientada a objetos a la antigua.
Cuándo usar los ayudantes de Rails
Hay muchos patrones de diseño diferentes que se pueden usar en la capa de vista de Rails: presentadores, decoradores, parciales, así como ayudantes, solo por nombrar algunos. Mi regla general simple es que los ayudantes funcionan muy bien cuando desea generar marcado HTML que requiere una estructura determinada, clases CSS específicas, lógica condicional o reutilización en diferentes páginas.
El mejor ejemplo del poder de los ayudantes de Rails lo demuestra FormBuilder
con todos sus métodos asociados para generar campos de entrada, seleccionar etiquetas, etiquetas y otras estructuras HTML. Estos métodos útiles generan marcado para usted con todos los atributos relevantes configurados correctamente. Una comodidad como esta es la razón por la que todos nos enamoramos de Rails en primer lugar.
Los beneficios de usar ayudantes bien diseñados son los mismos que cualquier código limpio y bien escrito: encapsulación, reducción de la repetición de código (DRY) y mantener la lógica fuera de la vista.
Anatomía de un carrusel Bootstrap de Twitter
Twitter Bootstrap es un marco de front-end ampliamente utilizado que viene con soporte integrado para componentes comunes como modales, pestañas y carruseles de imágenes. Estos componentes de Bootstrap son un gran caso de uso para los ayudantes personalizados porque el marcado está muy estructurado, requiere que ciertas clases, ID y atributos de datos se configuren correctamente para que JavaScript funcione, y configurar esos atributos requiere un poco de lógica condicional.
Un carrusel de Bootstrap 3 tiene el siguiente marcado:
<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 puede ver, hay tres estructuras principales: (1) los indicadores (2) las diapositivas de imagen (3) los controles deslizantes.
El objetivo es poder construir un único método auxiliar que tome una colección de imágenes y represente todo este componente de carrusel, asegurando que los atributos data, id
, href
y clases CSS estén configurados correctamente.
el ayudante
Comencemos con un esquema básico del ayudante:
# 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
El método auxiliar carousel_for
devolverá el marcado de carrusel completo para las URL de imagen dadas. En lugar de crear un conjunto de métodos individuales para representar cada parte del carrusel (lo que requeriría que pasáramos la colección de imágenes y otra información con estado a cada método), crearemos una nueva clase de Ruby llamada Carousel
para representar los datos del carrusel. Esta clase expondrá un método html
que devuelve el marcado completo. Lo inicializamos con la colección de imágenes URL de images
y la vista de contexto de view
.
Tenga en cuenta que el parámetro de view
es una instancia de ActionView
, en la que se mezclan todos los ayudantes de Rails. Lo pasamos a nuestra instancia de objeto para obtener acceso a los métodos auxiliares integrados de Rails, como link_to
, content_tag
, image_tag
y safe_join
, que usaremos para crear el marcado dentro de la clase. También agregaremos la macro de delegate
, para que podamos llamar a esos métodos directamente, sin hacer referencia a la 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 un carrusel se compone de tres componentes separados, así que vamos a extraer métodos que eventualmente nos darán el marcado para cada uno, luego hagamos que el método html
los una en una etiqueta div
de contenedor, aplicando las clases de Bootstrap necesarias para el carrusel en sí.

safe_join
es un práctico método incorporado que concatena una colección de cadenas y llama a html_safe
en el resultado. Recuerde, tenemos acceso a esos métodos a través del parámetro de view
, que pasamos cuando creamos la instancia.
Construiremos los indicadores primero:
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
Los indicadores son una lista ordenada simple ol
que tiene un elemento de lista li
para cada imagen de la colección. El indicador de imagen actualmente activo necesita la clase CSS active
, por lo que nos aseguraremos de que esté configurado para el primer indicador que creamos. Este es un gran ejemplo de lógica que normalmente tendría que estar en la vista misma.
Tenga en cuenta que los indicadores deben hacer referencia a la id
única del elemento del carrusel que lo contiene (en caso de que haya más de un carrusel en la página). Podemos generar fácilmente esta id
en el inicializador y usarla en el resto de la clase (específicamente dentro de los indicadores y los controles). Hacer esto programáticamente dentro de un método auxiliar garantiza que la id
sea consistente en todos los elementos del carrusel. Hay muchas ocasiones en que un pequeño error tipográfico o cambiar la id
en un lugar pero no en los demás hará que se rompa un carrusel; eso no sucederá aquí porque todos los elementos hacen referencia automáticamente al mismo id
.
def initialize(view, images) # ... @uid = SecureRandom.hex(6) end attr_accessor :uid
A continuación están las diapositivas de imágenes:
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
Simplemente iteramos sobre cada una de las imágenes que pasamos a la instancia de Carousel
y creamos el marcado adecuado: una etiqueta de imagen envuelta en un div
con la clase CSS del item
, nuevamente asegurándonos de agregar la clase active
a la primera que creamos.
Por último, necesitamos los controles Anterior/Siguiente:
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
Creamos enlaces que controlan el movimiento del carrusel de ida y vuelta entre las imágenes. Tenga en cuenta el uso de uid
nuevamente; no hay necesidad de preocuparse por no usar la identificación correcta en todos los diferentes lugares dentro de la estructura del carrusel, es automáticamente consistente y único.
El producto terminado:
Con eso, nuestro asistente de carrusel está completo. Aquí está en su totalidad:
# 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
El ayudante en acción:
Finalmente, para recalcar el punto, veamos un ejemplo rápido de cómo este ayudante puede hacernos la vida más fácil. Digamos que estamos construyendo un sitio web para listados de apartamentos en alquiler. Cada objeto Apartment
tiene una lista de las URL de las imágenes:
class Apartment def image_urls # ... end end
Con nuestro asistente de carrusel, podemos representar todo el carrusel de Bootstrap con una sola llamada a carousel_for
, eliminando por completo la lógica bastante compleja de la vista:
<% apartment = Apartment.new %> # ... <%= carousel_for(apartment.image_urls) %>
¿No está seguro de cuándo usar los asistentes de visualización de Rails? Aquí hay una demostración.
Pío
Conclusión
Usando esta técnica simple pero poderosa, hemos movido lo que sería una cantidad significativa de marcado y lógica fuera de la capa de vista y dentro de una función auxiliar que se puede usar para representar componentes de carrusel en cualquier lugar con solo una llamada carousel_for(some_images)
. Esta ayuda genérica se puede utilizar en todos sus proyectos de Rails cada vez que utilice Twitter Bootstrap. Lo que es más importante, ahora tiene una nueva herramienta en su kit de herramientas que también puede usar para componentes específicos del proyecto.
Por lo tanto, la próxima vez que se encuentre escribiendo y volviendo a escribir el mismo tipo de marcado e incrustando lógica condicional en sus vistas, vea si una función auxiliar está esperando a ser escrita para facilitarle la vida.