How to Use Concerns in Rails 5
This lesson explains how to properly utilize concerns in the Rails framework. Additionally, it discusses when concerns should be utilized.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

The next task on our PivotalTracker is to integrate concerns. If you've never heard of concerns, don't worry because that's what we're going to address right now, as I am going to explain an appropriate use for concerns in our application.

Interestingly, there is a lot of debate on concerns and whether they should even be included in Rails, but I think concerns should definitely be a part of a comprehensive guide of Rails because there are many times when concerns would come handy.

The Case For Concerns

Go to app/models, and you will see another directory inside of it called concerns. It is currently empty. At their essence concerns are items that have a functionality that doesn't fully belong inside of a model file or is something that should be shared across a couple of different models. So there are times where a concern might be a good fit. I say that with a caveat, as concerns always have a reputation for being abused.

Concerns are abused when individuals build something and implement some functionality that is not truly data-related. What they have created is more of a helper module for the application, but they will put it inside the concerns directory because that is where they think it has to go. The problem with that procedure is that it is not the way software engineering should work, from an architectural standpoint.

So, your concerns should always be data related. They're in the model directory. Anything in the model directory should be data-related. If you have a helper module that's not data-related, then it shouldn't be placed there at all. In a different course, I built an SMS sending module. I know many developers who would put that inside of theconcerns directory, but it should not go there because sending a text message has nothing to do with data. All those types of helper modules should go inside of your lib directory. Later on in the course we will get into examples of when it is appropriate to use the lib directory.

For now, I wanted to give that caveat because I don't want you to put helper files in concerns when you start building Rails applications on your own, as that’s considered bad practice.

That said, let's talk about a situation where it would be a good fit to use this directory. I think I have the perfect scenario.

Scenario for a Concern

Open the portfolio.rb and skill.rb model files. Now, we are going to update our skill so that it could contain an image. It could have a little thumbnail like a small Rails icon or a Ruby logo or perhaps an Angular logo. I think that would be a good thing to have as it would add visual appeal.

So, let's add that image in our skills table by creating a migration in the terminal using the command
rails g migration add_badge_to_skills badge:text

With this, we are adding a badge to the skills table and it will be of data type text. Next, we need to run a migration with rails db:migrate

This is going to give our skills the ability to have images associated with them. For the sake of development, or even in a production environment we may also want to have defaults. I think it makes sense for skills to have some type of image by default.

Let's copy the code from portfolio.rb that we created to add our default images for portfolios, and paste it in skill.rb.

We'll change the value to self.badge and make our placeholder value a nice small square with 250x250

- self.thumb_image ||= "http://placehold.it/350x200"
+ self.badge ||= "http://placehold.it/250X250" 

So your file should look like this:

class Skill < ApplicationRecord
  validates_presence_of :title, :percent_utilized

  after_initialize :set_defaults
  def set_defaults
    self.badge ||= "http://placehold.it/250X250"
  end
end

This code, as we have it, is perfectly fine, however it may also be a good case for concerns. Justly, I don't like to have such long image URLs. I don’t think they are very handy and I also feel it can lead to bugs later on. This may seem trivial, but it can be a nuisance when you are trying to work quickly and you are not able to select the items you want easily so you could end up with typos or bugs.

Implementing a Concern

I want to create a concern that can be shared across the models. Our concern will generate this "http://placehold.it/250X250"URL for us, so we don't have to have a long piece of text. Instead, we can just have a method.

To do that, go to the concerns directory and right click to create a new file called placeholder.rb. This is not going to be a class, but a module.

module Placeholder

end

Inside of the module we need to extend ActiveSupport::Concern. This will load in some helper modules that will allow us to use many more things. I’m using a basic example here where we are adding a method that can be accessed by multiple models. However, you can take concerns much further. For example, you could place an entire modules set of callbacks in the concern, so all of the defaults and everything associated with them, could be included here. I don’t think that is the most prudent thing to do in all cases because it unnecessarily separates your code out in a somewhat inferior way . For now, I am just going to make this a helper module.

module Placeholder
  extend ActiveSupport::Concern
end

The next step is to create a method using self which we will call .image_generator, and this will create a class method, which means we're going to be able to call it as Placeholder.image_generator.

This method will take two parameters, namely a height and a width. I like using the colon after parameters, though you can choose not to have them if you wish. My purpose in using them is to insure we know the order for the height and width.

module Placeholder
 extend ActiveSupport::Concern
 def self.image_generator(height:, width:)

 end
end

Inside this method, we can put the entire URL string we want to replace "http://placehold.it/250X250", and then use string interpolation #{} to dynamically set the width and height.

- “http://placehold.it/250X250”
+ "http://placehold.it/#{height}x#{width}"

So, now we have a helper method that we can simply call, instead of typing the entire URL.

module Placeholder
  extend ActiveSupport::Concern
  def self.image_generator(height:, width:)
    "http://placehold.it/#{height}x#{width}"
  end
end

Next, go to skill.rb and inside of our class, at the very top add
include Placeholder

Now that we have access to Placeholder, we can get rid of the URL, and simply call the method.

- self.badge ||= “http://placehold.it/250X250"
+ self.badge ||= Placeholder.image_generator(height: '250', width: '250')

Your skill.rb file should now look like this:

class Skill < ApplicationRecord
  include Placeholder
  validates_presence_of :title, :percent_utilized

  after_initialize :set_defaults

  def set_defaults
    self.badge ||= Placeholder.image_generator(height: '250', width: '250')
  end
end

Take the same steps in portfolio.rb too. Add include Placeholder to the file. Then replace the URLs with Placeholder.image_generator(height: '250', width: ‘250') just update the parameters so that the main image height and width are 600 and 400, and the thumb image are 350 and 200 respectively.

class Portfolio < ApplicationRecord
  include Placeholder
  validates_presence_of :title, :body, :main_image, :thumb_image

  def self.angular
    where(subtitle: 'Angular')
  end

  scope :ruby_on_rails_portfolio_items, -> { where(subtitle: 'Ruby on Rails') }

  after_initialize :set_defaults

  def set_defaults
    self.main_image ||= Placeholder.image_generator(height: '600', width: '400')
    self.thumb_image ||= Placeholder.image_generator(height: '350', width: '200')
  end
end

All of our changes should work the way we are anticipating. Start the rails server.

With the server running, go to the browser and test the code by creating a new portfolio item. Make sure you go to show page, so you can verify the image is working there. Good, all the changes we made are operative.

You can also test your changes in the console. Go to the terminal and use rails c Create a new skill with a title Skill.create!(title: “Some Skill”, percent_utilized: 80)

You can see that it created the skill and included the badge [“badge”, “http://placehold.it/250X250”] So our code has been tested and is working perfectly.

Clearly, I think this is a good example of how we could take and abstract out a helper method. Instead of having to pass URL strings all over the place, we now have modules and methods.

Modules Can Be Multi Purpose

I named the modulePlaceholder, and we can make it even more efficient, as we can even add other items. For example if there where other placeholder types of attributes or data that you want to have, you would be able to add those as methods in the placeholder.rb file, and call them whenever you need to. Remember, concerns can also be shared across any of the models.

Let’s run a git status to see the files we have been working on. Next use git add . For our commit we will run git commit -m ‘Implemented concern for managing image data’. Last we will do a git push origin data-feature

As a final step, check this task as done in your Pivotal Tracker.

Resources