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 December 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 @pablougas:disqus You may just have a bad URL to your image. What does the "src" attribute of the "img" tag show for your carousel images?
Carlos Ramirez III
Hey @disqus_pZ8YXm0nPu:disqus, Thank you for the kind words! The carousel helper simply takes an array of image URLs, so if you have those images in your assets folder, then you'll just want to pass them along in an array like this <pre><code> <%= carousel_for ["image_one.png", "image_two.png"] %> </code></pre> NOTE: When you use Rails's built-in "image_tag" helper (which we are within the CarouselHelper) then Rails will automatically look within your "assets/images" directory for files. Therefore you need only pass the filename (with the extension) to the helper method since Rails knows where to look to find it. Let me know if that helps!
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!
Valéria Almeida
I'm having trouble: undefined method `count' for nil:NilClass def indicators items = images.count.times.map { |index| indicator_tag(index) } content_tag(:ol, safe_join(items), class: 'carousel-indicators') end Help me please?
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 :)
Guest290981
Hey there Carlos! is there a way to include text under reach image and have a fade action between the transition of each slide? For example: There is a collection of cars(entries from a table), with one value being the image and another its respected text?
Carlos Ramirez III
Hey there, great question! What you're describing is a sort of v2 enhanced version of the carousel which was out of the scope of this post, but definitely the right direction for making the helper more robust. Twitter Bootstrap Carousel has the ability to add a "caption" text to each slide. You simply need to add the following HTML underneath each image for each slide: <div class="carousel-caption">...</div> Generating that HTML from within the helper is easy enough, but how do we actually pass the captions (and associated images) to the helper? One way is to modify the helper method to take an array of pairs as an argument, one for the image, one for the caption. For example: https://gist.github.com/carlosramireziii/8430dd7a906c4a2637bc38c976ad9a7e#file-option1-rb This requires consumers of the `carousel_for` helper to make sure they always pass an array of arrays to the method. Alternatively, maybe you pass the images as one argument and the captions as another: https://gist.github.com/carlosramireziii/8430dd7a906c4a2637bc38c976ad9a7e#file-option2-rb The key here is to make sure the images and captions are ordered properly and both arrays are of equal size. The last option is to create a custom class that represents a "Slide": https://gist.github.com/carlosramireziii/8430dd7a906c4a2637bc38c976ad9a7e#file-option3-rb For each option, you modify the `Carousel` class to accept the images along with the captions and generate the appropriate HTML. Hope that helps! Let me know which option you go with and link a Gist to your final code so we can see.
Guest290981
Hi Carlos, Thanks for the help! I'm at the final stages but running into the issue of where to define the caption under the slides and slide_tag method in the carousel helper: 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 I'm close but am confused at how to implement that last piece
Carlos Ramirez III
Yes, you are so close! :) So as mentioned above, the caption goes right below the <img> tag within each slide. That <img> tag is generated in the "slide_tag" method, so that would be as good a place to put it as any. First we can modify the "slide_tag" method to accept not only an image, but also it's corresponding caption. How you do that depends on how you are storing the captions. I'll leave that part up to you. def slide_tag(image, caption, is_active) # ... end Within that method, we'll need to generate the markup for the image AND the caption, then join them together underneath the containing <div>. So something like image_tag = image_tag(image) caption_tag = content_tag(:div, caption, class: "carousel-caption") Then join them together and put them into the wrapper <div> return content_tag(:div, safe_join( [image_tag, caption_tag] ), options). If you want, you can offload that into another helper method, e.g. "slide_content_tag(image, caption)". The power of using an object to help build out the markup is that we can continue to create smaller helper methods where appropriate to keep things semantic and organized. LMK if that helps!
Carlos Ramirez III
Hi Rocio Cardenas! Welcome to Rails :) You'll want to make sure that you include the Twitter Bootstrap Carousel JavaScript on your page, as that's what actually provides the functionality. Here's a link to the documentation: http://getbootstrap.com/javascript/#carousel If you've added Bootstrap and enabled the JavaScript functionality and it still doesn't work, let me know. Good luck!
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.
Carlos Ramirez III
Gotcha. Send me a Gist of your code, and I'll take a look.
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.
Rocio Cardenas
Hi, I basically copied your code in a little test app: Carousel helper: https://gist.github.com/anonymous/748b1682eca706a65e228d0e88c350a0 And i'm calling it with the code: <%= carousel_for ["image1.jpg", "image2.jpg", "image3.jpg"] %> Maybe it needs some more configuration?
Carlos Ramirez III
I see. So the helper is simply generating markup for you-- actually enabling the carousel behavior is still up to you (the developer). Here's a test to try: since you said you can use the carousel without issue when you *don't* use the helper, why not create two carousels side-by-side, one using the helper and the other without. Then compare the markup to see what's different. That will help you isolate what is actually allowing the carousel to work. Like I said, the helper is merely generating the markup. How you hook it in to the JS is up to you. Let me know what you find. Feel free to add your markup output for both into the Gist above and I'll take a look.
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!
Sammy Dallal
How do you added a vertical to the carousel and still keep the horizontal proportions? e.g. I want the vertical to scale down to fit inside the horizontal proportion. In my model I have a column name vertical with a true false value. I want the true value to scale down. Thanks for your help.
Carlos Ramirez III
Hey Sammy Dallal, It sounds like you are trying to maintain the aspect ratio of the image. Have you tried adding the `img-responsive` class to your images within the carousel? To do that in the `CarouselHelper` above, modify the last line in the `slide_tag` method to be the following OLD: content_tag(:div, image_tag(image), options) NEW: content_tag(:div, image_tag(image, class: "img-responsive"), options) Let me know if that helps. Good luck!
jvssvarma
How do I refer to versions of image like if I have medium, thumb and default versions. This technique works for default. How do I make it work for other versions of images?
jvssvarma
More details - I'm using carrierwave's multi upload feature, where images are stored in array and I'm able to make the carousel work with the helper. It works like magic. But It works only for default versions. I want the same for 2 other versions (medium and thumb). Can you suggest how do I make it work?
Carlos Ramirez III
Hey @jvssvarma:disqus, Thanks for the kind words! Glad you found the article helpful :) Regarding your question, since the carousel helper simply takes an array of image URLs you simply need to create an array with the version of the images that you want (e.g. thumb) and pass it along to the helper method. It will just work. That being said, it seems like your question is more about how to build that array using CarrierWave's multi-upload feature. I'm not 100% sure (as I've never used this feature myself), but I believe you can do something like this: image_urls = @model.images.map { |image| image.thumb.url } Where "@model" is an instance of an object which has an uploader mounted on it, "images" is the name of the attribute which stores the uploads array, and "thumb" is the name of the version that you want. The "images" call will return an array of uploader and then you iterate over them calling the version you want and finally asking for the "url". The resulting array "image_urls" should be a collection of image URLs for the "thumb" version of each image. Hope that helps!
jvssvarma
Hey @carlosramireziii:disqus Thanks for the detailed explanation. It worked perfectly and is really good.
Carlos Ramirez III
Excellent! Glad we got it working :)
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.