Chapter 12

The Contacts Module: Extending Models & Controllers

Extending models is much easier than extending views. Thanks to Ruby open classes, we can always add more methods to an existing class.

Let’s see how.

Don’t forget to create our branch for the chapter:

git checkout -b Chapter-12

12.1. Extending Contacts

Step 1: The Decorator folder

To keep it organized, we’ll extend models and controllers in files called decorators.

By using the modular_engine generator, you should have a decorators/ folder in your engine’s app/ folder. In there, you will find two more folders: models/ and controllers/. We’re going to create one decorator for each class that we want to extend. But first, let’s make sure our decorators will be loaded by Rails automatically.

Step 2: Load the decorators

To load the content of your decorators, we need to tell Rails to load them. Actually, with the gem we used to generate the engine, the piece of code to do it should already be there, but let’s take a look.

Open the engine.rb file in the Contacts engine (located in lib/blast/contacts). You should see the contents of Listing 1 somewhere inside the engine file:

Listing 0.1: Decorators in Contacts engine.rb file contacts/lib/blast/contacts/engine.rb
# ...
  config.to_prepare do
    Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
      Rails.configuration.cache_classes ? require(c) : load(c)
    end
  end
# ...

And that’s all the magic we need to load our decorators!

Step 3: Create a decorator for the User model

Now, let’s try it. Create a decorator for User from the Contacts engine folder, and put the code from Listing 2 inside:

touch app/decorators/models/user_decorator.rb
Listing 0.2: Contents of userdecorator.rb contacts/app/decorators/models/user_decorator.rb
Blast::User.class_eval do
  has_many :contacts, class_name: "Blast::Contacts::Contact"
end

And that’s it! You can put anything you’d like to add to the User model in the class_eval block. Note however, that we need to provide the namespaced name of the module we’re linking, otherwise it will try to find the Contact model in the Blast::User scope, instead of the Blast::Contacts scope!

Step 4: A quick test in the console

Let’s see if what we just did is working. Fire up the rails console and type Blast::User.first.contacts (Obviously, you need a user, but we have already created some).

You should get a CollectionProxy object showing up in your terminal:

Loading development environment (Rails 5.2.3)
2.6.3 :001 > Blast::User.first.contacts
  ... [output] ...
 => #<ActiveRecord::Associations::CollectionProxy []> 

Step 5: Update the Contacts controller

A few steps back, we added the possibility to show all the contacts to a user. But that’s not the behavior we want. We want to show a user only his contacts.

To do this, we need to change the value we give to @contacts in the index action of the ContactsController, as shown in Listing 3:

Listing 0.3: Updated index action in Contacts Controller contacts/app/controllers/blast/contacts/contacts_controller.rb
# ...
  def index
    @contacts = current_user.contacts
  end
# ...

Thanks to the has_many relationship that we added, it’s working! Time to extend the dashboard!

12.2. Extending the Dashboard

So you’ve just learned how to extend anything in any way you might want. But to learn something, you need to do, do, do and do again. That’s what we’re going to do now! The dashboard is so white it’s blinding! It would be nice to see a table on this page showing the last contacts a user created.

Step 1: Add a hook

You know the drill already… let’s add a hook to the dashboard view, as shown in Listing 4, so we can easily extend it in the Contacts engine. We’re going to call this hook dashboard.

Listing 0.4: Adding hook to Dashboard view core/app/views/blast/dashboard/index.html.erb
<h2>Dashboard</h2>
<hr>

<div class="row">

  <!-- The Dashboard Hook -->
  <span data-blast-hook='dashboard'></span>

</div>

A simple span is enough for the dashboard. We’re going to insert code right after it with Deface.

Step 2: Add an override

Time to create a new override that will be responsible for adding a list of contacts on the dashboard:

touch app/overrides/add_contacts_list_to_dashboard.rb
Listing 0.5: Add Contacts List override app/overrides/add_contacts_list_to_dashboard.rb
Deface::Override.new(:virtual_path => "blast/dashboard/index",
                     :name => "add_contacts_list_to_dashboard",
                     :insert_after => "[data-blast-hook='dashboard']",
                     :partial => "overrides/contacts_list",
                     :namespaced => true)

Step 3: Add an override view

And finally, the list of contacts. This is a simple view with a table showing a user’s contacts.

touch app/views/blast/contacts/overrides/_contacts_list.html.erb
Listing 0.6: Contacts List view override app/views/blast/contacts/overrides/_contacts_list.html.erb
<div class="col-md-6">
  <div class="panel panel-primary">
    <div class="panel-heading">
      Last 3 Contacts
    </div>
    <table class="table">
      <thead>
        <th>ID</th>
        <th>Name</th>
        <th>Email</th>
        <th>Created On</th>
      </thead>
      <tbody>
        <%- current_user.contacts.each do |contact| %>
          <tr>
            <td><%= contact.id %></td>
            <td><%= contact.first_name %> <%= contact.last_name %></td>
            <td><%= contact.email %></td>
            <td><%= contact.created_at.strftime("%d %b. %Y") %></td>
          </tr>
        <% end %>
      </tbody>
    </table>

    <div class="panel-body text-center">
      <%= link_to '...', contacts_path %>
    </div>
  </div>
</div>

Did you see how we got the list of contacts? We used current_user.contacts directly in the view. That’s not really “good practice” so we’ll move it to the controller in a bit. First, let’s take a look at Figure 1 to see what we’ve accomplished:

https://s3.amazonaws.com/devblast-modr-book/images/figures/03_12/dashboard_contacts
Figure 1
Box 12.1. Add some contacts

Of course, by navigating to “Contacts -> New Contact” you can add multiple contacts so that you have a populated list like ours.

Now let’s change how we get the list of contacts!

12.2.1. Part 7: Extending the Core controllers

To fix the current_user.contacts code in our view, and replace it with an @contacts assignment in the controller, we need to override the DashboardController.

Step 1: Create a decorator for the Dashboard controller

Just like when we extended a model, we’re going to create a decorator. A controller decorator to be precise. We cannot override the index action because that would mean overriding it again in the next chapter. A better option is to use a before_action to assign the @contacts value, as you can see in Listing 7:

touch app/decorators/controllers/dashboard_decorator.rb
Listing 0.7: Adding before_action to the index action app/decorators/controllers/dashboard_decorator.rb
Blast::DashboardController.class_eval do
  before_action :set_contacts, only: :index

  private

    def set_contacts
      @contacts = current_user.contacts
    end
end

Step 2: Update the override view

Now we can change the override view that we created a few steps back and use @contacts instead of current_user.contacts.

Listing 0.8: Updated override view app/views/blast/contacts/overrides/_contacts_list.html.erb
<div class="col-md-6">
  <!-- ... -->
        <%- @contacts.each do |contact| %>
  <!-- ... -->  
</div>

Step 3: Take a look

It should still be working! So now you know how to extend models, controllers and views! Is there anything else we have to extend? The answer is yes!

12.3. Pushing Our Changes

It’s now time to push our changes to GitHub. You should know the steps by now:

  1. Check the changes:
    git status
    
  2. Stage them:
    git add .
    
  3. Commit them:
    git commit -m "Extending Contacts Models & Controllers"
    
  4. Push to your GitHub repo if you’ve configured it:
    git push origin Chapter-12
    

12.4. Wrap Up

In this chapter we extended the Core module models, so that we can add the relationships that are only relevant if the Contacts module is included. We did the same for the controllers.

12.4.1. What did we learn?

  • How to use decorators so extend our Models and Controllers
  • How to user the .class_eval method in our decorators

12.4.2. Next Step

Next, we’ll be creating our Tasks module, but first, we have an exercise for you!