Lesson Weekend

The university our development agency is working with want to add additional functionality to our API. They love the quotations we've added so much that they'd like these in their application, too.

We won't actually build out this functionality. However, we will use this hypothetical additional functionality to move our json_response method to a location where it will be accessible to other controllers.

We could just put the code in application_controller.rb and that would work. However, let's explore another Ruby concept that we haven't covered since section one of Ruby: Ruby modules.

In the process, we'll touch on an important Rails concept as well: concerns. You might've noticed the concerns folders nested inside our controllers and models folders. Perhaps they're even making you a little concerned — why are they empty? What should we do with them?

It's time to answer that question. Remember that it's important to separate our concerns in a Rails application (and not just Rails — you should do the same for all coding projects). For instance, when we created a small API service in the last course section and moved it into a plain old Ruby object (PORO), we realized it didn't belong in the model so we created a services folder.

Rails Concerns

The concerns folder is just a Rails way of organizing our code. Sometimes we might use the organization Rails provides for us and sometimes we might customize on our own, such as when we add services. To some extent, how we do this is a matter of preference. Regardless, we should still separate our concerns.

So what should go in controllers/concerns? Code related to controllers, of course! Let's put the json_response method in there.

We could just create a class but then we'd need to initialize an instance of that class every time we use the method, and that's not what we want here.

We'll create a module instead. We covered modules in section 1 but we didn't explore them in depth. Now we'll get a chance to actually add one to our code.

Ruby Modules

Remember that a module is a way of encapsulating code so it's only available where it's needed. If the module is named Hey, we'll need to include Hey in any files where we need to use the methods contained in the module.

Let's remove the code from our controller and move it into our new module:

module Response
  def json_response(object, status = :ok)
    render json: object, status: status

That's it. We define a module and name it. Then we put the method in there.

However, it won't work in our controllers yet because it hasn't been included. Remember, a module contains encapsulated code, which means it's only accessible to parts of an application where we include it.

We'll need to add the following to application_controller.rb:

class ApplicationController < ActionController::API
  include Response

Now our module is available to our application controller and hence all other controllers.

Why Use Modules?

There are several important uses for modules. We covered these in section one but let's go over some of their advantages again:

  • Modules are a great place to put code that doesn't fit in a class. For instance, you might want to group related pieces of code that don't naturally fit into a class together.

  • Encapsulation allows developers to keep different parts of their codebase separate. Modules limit encapsulated code to only where it's needed. This can help prevent conflicts in larger applications and limits scope to where it's necessary, which is always a good practice.

  • Ruby doesn't allow multiple inheritance like some other languages. In other words, a class in Ruby can only inherit from one other class instead of inheriting from many classes. Modules provide a way around this with the concept of mixins, which means they can be "mixed in" to other classes. You can include as many modules as you want in a class; it's almost like having multiple inheritance after all. Mixins also allow us to provide reusable code to many different classes. After all, we might have methods that would be useful to multiple classes but don't necessarily belong in a parent class.

  • Modules also follow an important coding best practice: favor composition over inheritance. Composition is the process of composing an object's behaviors from different sources such as modules. It can often give us much more fine-tuned control over objects and classes than inheritance can. This is a more advanced programming concept and we will address it further when learning React.

That's modules in a nutshell — not quite literally. You won't need to include modules in your code review but you're encouraged to experiment with them and consider use cases where they might come in handy.

Sample module

A module encapsulates code so it's only available when needed. You'll include module_name in any files where the module's methods are used.

module Response
  def json_response(object, status = :ok)
    render json: object, status: status

Lesson 5 of 19
Last updated August 7, 2022