One of the most misused, misunderstood, and neglected of all the Rails built-in structures is the view helper. Located in your app/helpers directory and generated by default with every new Rails project, helpers often get a bad reputation for being a dumping ground for one-off methods used across the entire application’s view layer. Unfortunately, Rails itself encourages this lack of structure and poor organization by including all helpers into every view by default, creating a polluted global namespace.

But what if your helpers could be more semantic, better organized, and even reusable across projects? What if they could be more than just one-off functions sprinkled throughout the view, but powerful methods that generated complex markup with ease leaving your views free of conditional logic and code?

Let’s see how to do this when building an image carousel, with the familiar Twitter Bootstrap framework and some good old-fashioned object-oriented programming.

Rails helpers should deliver useful structures.

When to use Rails helpers

There are many different design patterns that can be used in Rails’ view layer: presenters, decorators, partials, as well as helpers, just to name a few. My simple rule of thumb is that helpers work great when you want to generate HTML markup that requires a certain structure, specific CSS classes, conditional logic, or reuse across different pages.

The best example of the power of Rails helpers is demonstrated by the FormBuilder with all its associated methods for generating input fields, select tags, labels, and other HTML structures. These helpful methods generate markup for you with the all the relevant attributes set properly. Convenience like this is why we all fell in love with Rails in the first place.

The benefits of using well-crafted helpers is the same as any well-written, clean code: encapsulation, reduction of code repetition (DRY), and keeping logic out of the view.

Use Rails helpers to build complex markup

Twitter Bootstrap is a widely used front-end framework that comes with built-in support for common components such as modals, tabs, and image carousels. These Bootstrap components are a great use case for custom helpers because the markup is highly structured, requires certain classes, IDs, and data attributes to be set correctly for the JavaScript to work, and setting those attributes requires a bit of conditional logic.

A Bootstrap 3 carousel has the following markup:

<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 you can see, there are three main structures: (1) the indicators (2) the image slides (3) the slide controls.

The parts of a Bootstrap carousel.

The goal is to be able to build a single helper method that takes a collection of images and renders this entire carousel component, ensuring that data, id, href attributes and CSS classes are all set properly.

The Helper

Let’s start with a basic outline of the helper:

# 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 helper method carousel_for will return the complete carousel markup for the given image URLs. Rather than building out a suite of individual methods to render each portion of the carousel (which would require us passing around the image collection and other stateful information to each method), we’ll create a new plain-old-Ruby class called Carousel to represent the carousel data. This class will expose an html method which returns the fully rendered markup. We initialize it with the collection of image URLs images, and the view context view.

Note that the view parameter is an instance of ActionView, which all Rails helpers are mixed into. We pass it along to our object instance in order to gain access to Rails’ built-in helper methods such as link_to, content_tag, image_tag, and safe_join, which we will be using to build out the markup within the class. We’ll also add the delegate macro, so we can call those methods directly, without referring to 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

We know that a carousel is comprised of three separate components, so let’s stub out methods which will eventually give us the markup for each, then have the html method join them into a container div tag, applying the necessary Bootstrap classes for the carousel itself.

safe_join is a handy built-in method which concatenates a collection of strings together and calls html_safe on the result. Remember, we have access to those methods via the view parameter, which we passed in when we created the instance.

We’ll build out the indicators first:

    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

The indicators are a simple ordered list ol that has a list item li element for each image in the collection. The currently active image indicator needs the active CSS class, so we’ll make sure that it’s set for the first indicator we create. This is a great example of logic which would normally have to be in the view itself.

Notice that the indicators need to reference the unique id of the containing carousel element (in case there is more than one carousel on the page). We can easily generate this id in the initializer and use it throughout the rest of the class (specifically within the indicators and the controls). Doing this programmatically inside a helper method ensures that the id will be consistent across carousel elements. There are many times when a small typo or changing the id in one place but not the others will cause a carousel to break; that won’t happen here because all the elements automatically reference the same id.

    def initialize(view, images)
      # ...
      @uid = SecureRandom.hex(6)
    end

    attr_accessor :uid

Next up are the image slides:

    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

We simply iterate over each of the images that we passed to the Carousel instance and create the proper markup: an image tag wrapped in a div with the item CSS class, again making sure to add the active class to the first one we create.

Lastly, we need the Previous/Next controls:

    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

We create links that control the carousel’s movement back and forth between images. Note the usage of uid again; no need to worry about not using the right ID in all the different places within the carousel structure, it’s automatically consistent and unique.

The finished product:

With that, our carousel helper is complete. Here it is in its entirety:

# 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

The helper in action:

Finally, to drive home the point, let’s look at a quick example of how this helper can make our lives easier. Say we are building a website for apartment rental listings. Each Apartment object has a list of the image URLs:

class Apartment
  def image_urls
    # ...
  end
end

With our carousel helper, we can render the entire Bootstrap carousel with a single call to carousel_for, completely removing the fairly-complex logic from the view:

<% apartment = Apartment.new %>
# ...
<%= carousel_for(apartment.image_urls) %>
Not sure when to use Rails view helpers? Here's a demonstration.

Conclusion

Using this simple, yet powerful, technique, we’ve moved what would be a significant amount of markup and logic out of the view layer and into a helper function that can be used to render carousel components anywhere with just a carousel_for(some_images) call. This generic helper can be used across all your Rails projects whenever you are using Twitter Bootstrap. Most importantly, you now have a new tool in your toolkit that you can use for project-specific components, too.

So, the next time you find yourself typing and retyping the same kind of markup and embedding conditional logic into your views, see if a helper function is just waiting to be written to make your life easier.

About the author

Carlos Ramirez III, United States
member since October 29, 2014
Carlos is a professional software engineer and web developer specializing in the Ruby on Rails framework. He has been working with tech companies in New York's Silicon Alley for over 5 years, helping to build technology-based businesses from the ground up. He has a Bachelor's degree in Computer Science from Williams College and has been working in the software industry for nearly 10 years. [click to continue...]
Hiring? Meet the Top 10 Freelance Ruby on Rails Developers for Hire in September 2016

Comments

Mazharul Islam Mithu
Just awesome!! Thanks for the concept!!
Petr Mlčoch
This article is short, catchy a excelent! I like it very much.
Carlos Ramirez III
Thanks very much Petr!
Carlos Ramirez III
Glad you liked it! I would love to hear about how you utilize the concept in your own applications.
Túlio Ornelas
I wrote this some time ago https://github.com/tulios/better_helpers it might help
Carlos Ramirez III
Really awesome project Túlio Ornelas! I like the idea of creating namespaces to reduce the possibility of helper collisions and for better organization. In some projects I've experimented with this setting: `config.action_controller.include_all_helpers = false` which will turn off the standard Rails behavior of including all helpers into all views. Instead, only the helper of the corresponding controller will be included (e.g. views from `UsersController` only gets methods from `UsersHelper`). Have you ever tried using that setting? What do you think of it compared to your solution of adding namespaces to the helper methods?
Túlio Ornelas
Hey Carlos, thanks. I've used that setting before, however in this project we have created a UI library which exposes helper methods to generate the components markup and before "better_helpers" we were creating namespaces in the helper name like "library_name_component_x" and so on. And we needed all the methods because we could use any component in any page. We refactored everything with the gem and it ends up much better.
Carlos Ramirez III
Ah that makes perfect sense! The gem fits your use case perfectly. I also have worked on projects which expose components from libraries in that way and your gem would have come in handy. I hope to use in future projects. Thanks again for sharing!
Pepito perez
Me gustaria un video sobre esto, soy principiante.
Pablo Ugas
I seem to be having trouble with linking images that are not an object but are simply located in an asset folder. It seems that it locates the images and scrolls through the amounts but on the "GET" it adds an additional images/ before my app/assets/images.
Othello
I'm having a similar problem to Pablo. This looks like a very elegant solution but I'm quite new to rails so don't understand entirely so hopefully you can help me out. I have a folder of images in my images asset pipeline, how do I define them as the ones needed for the helper when calling <%= carousel_for(images/slider_images) %> ? Thanks so much in advance for your time.
Melanie
I'm having a lot of trouble trying to figure this out. I have projects and gallery models. Gallery attributes are image and image description. Each Project has many galleries and galleries belong to project. I have been trying for weeks to figure this tutorial out. I have not figured out how to add actions to the controllers which are not shown in the tutorial to make this work; I also can't figure out how to add the image description to the carousel image display. I don't understand why a local variable in the final example is not required (is it a typo to omit @ from apartment.image_urls? I'm lost with this. I get so frustrated with tutorials that leave out steps. I've run out of guesses for how to get this to work. I'd really appreciate it if you could show all the steps involved in plugging this in. It doesn't work for me. I have tried adding the following to the projects controller - but just getting more and more mess: @image_urls=[] @project.galleries.each do |gallery| @image_urls.push(gallery.image.url) end
Carlos Ramirez III
Hey @disqus_OdVpqy5owx:disqus , I'm sorry to hear that you've been having so much trouble with this. Hopefully I can help alleviate some of your frustration. The carousel helper method takes an array of image URL strings as an argument. For example, ``` carousel_for ["image/path/file.png", "image/path/file.png"] ``` In your case, if your image URLs are in an array called `@image_urls` then it should be as simple as passing that variable directly to the carousel helper: ``` carousel_for @image_urls ``` To keep things simple in the article example I did not show a controller action rather I declared a local variable within the view itself `<% apartment = Apartment.new %>` and called a method on that variable `apartment.image_urls` to pass along the array of images to the helper. Since it was a local variable and not a variable coming from the controller, that's why the `@` was omitted. In real life (and in your case), that variable would likely be set in the controller and would require the `@` in order to work. Hope that helps!
Carlos Ramirez III
Hey Valéria Almeida! Sorry to hear you were having trouble. Let me see if I can help... The error message "undefined method `count' for nil:NilClass" means that you are trying to call "count" on something that is "nil". In this case, the "nil" object is your "images" collection. This means that when you originally called the helper method, you may not have actually passed any images to it! You should check your original call to "carousel_for" and see what was passed to it. Let me know what you find. Hope that helps :)
Rocio Cardenas
Hello Carlos, i'm new to Rails and i am having trouble because i only can see the first image but they don't move automatically or when i click the links. I appreciate you sharing this.
Rocio Cardenas
Hi Carlos, thank you for your answer, i already enabled the functionality, i can even use the carousel but not using this technique.
Carlos Ramirez III
Hey @disqus_2OUI3xaQhc:disqus-- were you able to get this working? As mentioned, if you add the markup to the Gist I can help you debug it. Hope you were able to get things running!
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering and design posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
You can edit your subscription preferences here.
Trending articles
Relevant technologies
About the author
Carlos Ramirez III
Ruby Developer
Carlos is a professional software engineer and web developer specializing in the Ruby on Rails framework. He has been working with tech companies in New York's Silicon Alley for over 5 years, helping to build technology-based businesses from the ground up. He has a Bachelor's degree in Computer Science from Williams College and has been working in the software industry for nearly 10 years.