Using Rails Helpers: An Example of a Bootstrap Carousel

One of the most frequently misused, misinterpreted, and overlooked aspects of Rails’ built-in functionality is the view helper. Automatically generated within the app/helpers directory of each new Rails project, helpers are often perceived negatively, viewed as repositories for miscellaneous methods used throughout an application’s view layer. This lack of organization is, unfortunately, reinforced by Rails itself, which incorporates all helpers into every view by default, cluttering the global namespace.

However, what if these helpers were more descriptive, structured, and reusable across various projects? What if they could transcend simple one-off functions scattered throughout views, becoming powerful tools capable of generating intricate markup effortlessly, freeing views from conditional logic and excess code?

Let’s explore how to achieve this while constructing an image carousel using the well-known Twitter Bootstrap framework and fundamental object-oriented programming principles.

When are Rails Helpers Appropriate?

Rails’ view layer offers a variety of design patterns, including presenters, decorators, partials, and of course, helpers. As a general guideline, helpers excel when generating HTML markup that demands a particular structure, specific CSS classes, conditional logic, or reusability across multiple pages.

The FormBuilder and its associated methods, which generate input fields, select tags, labels, and other HTML structures, exemplify the potential of Rails helpers. These methods automatically generate markup with the appropriate attributes, showcasing the convenience that drew developers to Rails initially.

Well-designed helpers offer the same benefits as any well-written, clean code: encapsulation, reduced code duplication (DRY), and separation of logic from the view.

Twitter Bootstrap is a [popular front-end framework offering built-in support for common components like modals, tabs, and image carousels. Bootstrap components present a compelling case for custom helpers due to their highly structured markup, reliance on specific classes, IDs, and data attributes for JavaScript functionality, and the need for conditional logic in attribute setting.

A Bootstrap 3 carousel features the following markup structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div id="carousel-example-generic" 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>

As observed, three primary structures exist: (1) indicators, (2) image slides, and (3) slide controls.

A blueprint showing a centered widescreen-ratio rectangle (slide) with three small circles near its bottom (indicators). It's flanked by two thin rectangles with left and right arrows, respectively (controls).
The parts of a Bootstrap carousel.

The objective is to create a single helper method that accepts a collection of images and renders the complete carousel component, ensuring accurate data, id, href attribute, and CSS class assignments.

Constructing the Helper

Let’s begin with a basic helper outline:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 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

The carousel_for helper method will return the complete carousel markup for the provided image URLs. Instead of creating multiple individual methods for each carousel part (requiring passing image collections and other state information), a new plain Ruby class called Carousel will represent the carousel data. This class will feature an html method that returns the fully rendered markup. Initialization occurs with the images collection of image URLs and the view context.

The view parameter, an instance of ActionView into which all Rails helpers are mixed, is passed to access built-in Rails helper methods like link_to, content_tag, image_tag, and safe_join for markup construction within the class. The delegate macro is added to enable direct method calls without referencing view:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    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

Knowing that a carousel consists of three distinct components, let’s stub out methods to generate markup for each. The html method will then combine them within a container div tag, applying the necessary Bootstrap classes for the carousel itself.

The built-in safe_join method efficiently concatenates string collections and applies html_safe to the result. Access to these methods is provided through the view parameter passed during instance creation.

Building the Indicators:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    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

Indicators are implemented as a simple ordered list (ol) containing a list item (li) element for each image in the collection. The active image indicator requires the active CSS class, which will be applied to the first indicator created. This exemplifies logic typically handled within the view itself.

Note that indicators need to reference the unique id of the containing carousel element (in case of multiple carousels on a page). This id, easily generated in the initializer, can be used throughout the class (particularly within indicators and controls). Programmatically generating this id within the helper method ensures consistency across carousel elements. This approach prevents potential carousel breakage due to typos or inconsistent id updates across elements.

1
2
3
4
5
6
    def initialize(view, images)
      # ...
      @uid = SecureRandom.hex(6)
    end

    attr_accessor :uid

Creating the Image Slides:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    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

This section iterates over each image passed to the Carousel instance, creating the appropriate markup: an image tag wrapped in a div with the item CSS class. The active class is added to the first slide created.

Implementing Previous/Next Controls:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    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

Links are created to control carousel movement between images. The use of uid ensures consistent and unique ID usage throughout the carousel structure.

The Complete Helper:

The complete carousel helper code is presented below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 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

Helper in Action:

Let’s illustrate the helper’s practicality with an example. Consider a website for apartment rental listings, where each Apartment object possesses a list of image URLs:

1
2
3
4
5
class Apartment
  def image_urls
    # ...
  end
end

Using the carousel helper, the entire Bootstrap carousel can be rendered with a single carousel_for call, eliminating complex logic from the view:

1
2
3
<% apartment = Apartment.new %>
# ...
<%= carousel_for(apartment.image_urls) %>

Unsure about when to leverage Rails view helpers? Here’s a practical demonstration.

Tweet

Conclusion

This straightforward yet effective technique moves a significant amount of markup and logic from the view layer into a reusable helper function. A simple carousel_for(some_images) call now renders carousel components anywhere. This generic helper, usable across all Rails projects employing Twitter Bootstrap, provides a valuable tool for project-specific components.

Next time repetitive markup and embedded conditional logic clutter your views, consider whether a helper function can simplify your workflow.

Licensed under CC BY-NC-SA 4.0