Chapter 16

The Tasks Module: Listing Tasks in Contacts

At this point, it would be useful to show a list of tasks on a contact’s show view. To achieve this, we need to link the Task and Contact models. But what if we decide later to remove the Contacts module? Everything would break!

The good news is that we can easily prevent this: we just have to check if the Contacts module is present. This chapter shows you the best solution we could come up with to handle this kind of situation, and we’re still looking for a better way.

In short, we will create a method in the Core module that checks if a specific module is defined. This method will allow us to check if the engine containing the module Contacts is present, or not. But first, let’s create our chapter branch:

git checkout -b Chapter-16

16.1. Is a module present?

16.1.1. Add the available? method

To check if a module is present, we will create a method called available? and define it in the core.rb file. The method will check if the passed symbol (e.g. :contacts) is defined as a namespace under Blast in the current application. Update the core.rb file as per Listing 1:

Listing 0.1: available? method in core.rb file core/lib/blast/core.rb
require 'devise'
require_relative 'core/engine'
require 'sass-rails'
require 'bootstrap'
require 'jquery-rails'
require 'pundit'

module Blast
  module Core
    def self.available?(engine_name)
      Object.const_defined?("Blast::#{engine_name.to_s.camelize}")
    end
  end
end

16.2. Link the modules

Now that we have the available? method, we can start using it to link our modules.

16.2.1. Update the Task Views

We can update the task form, as shown in Listing 2 to add a contact field, using the available? method as a conditional. Now, we can link a task to a contact!

Listing 0.2: Updated Task form with contacts list app/views/blast/tasks/tasks/_form.html.erb
  <!-- ... -->

    <%- if Blast::Core.available?(:contacts) %>
      <div class="form-group">
        <%= f.label :contact_id, class: "control-label" %>
        <%= f.select :contact_id,
        Blast::Contacts::Contact.all.collect { |p| [ p.email, p.id ] },
        { allow_blank: true }, class: "form-control" %>
      </div>
    <% end %>
  </div>
</div>

Let’s do the same for our show view:

Listing 0.3: Updated Task show view with contact app/views/blast/tasks/tasks/show.html.erb
<!-- ... -->
    <%- if Blast::Core.available?(:contacts) %>
      <strong>Contact:</strong>
      <%= @task.contact.email %>
      <br/>
    <% end %>
    <strong>User:</strong>
    <%= @task.user.email %>
    <br/>
<!-- ... -->

And for the index view:

Listing 0.4: Updated Task index view with contact app/views/blast/tasks/tasks/index.html.erb
<!-- ... -->
      <th>Content</th>
      <%- if Blast::Core.available?(:contacts) %>
        <th>Contact</th>
      <% end %>
      <th>Created At</th>

<!-- ... -->

          <td><%= task.content %></td>
          <%- if Blast::Core.available?(:contacts) %>
            <td><%= task.contact.email if task.contact.present? %></td>
          <% end %>
          <td><%= task.created_at.strftime("%d %b. %Y") %></td>
<!-- ... -->

We’ll see the results in a minute.

16.2.2. Add relations to our models

It’s time to add the relationships between our models.

Step 1: Create the decorator files

First, we need to create our decorator files for the Contact and User model. Run the following code from within the Tasks engine:

touch app/decorators/models/contact_decorator.rb && \
touch app/decorators/models/user_decorator.rb

Step 2: Add the code to create the relations

Note how we check if the Contacts module is present before trying to extend the Contact model.

Listing 0.5: Contents of contact_decorator.rb app/decorators/models/contact_decorator.rb
if Blast::Core.available?(:contacts)
  Blast::Contacts::Contact.class_eval do
    has_many :tasks, class_name: "Blast::Tasks::Task"
  end
end

Of course, there’s nothing special for User. Just a simple decorator:

Listing 0.6: Contents of user_decorator.rb app/decorators/models/user_decorator.rb
Blast::User.class_eval do
  has_many :tasks, class_name: "Blast::Tasks::Task"
end

Step 3: Add the relations to Task

We can now add the other side of the relationships in the Task model. Once again, we use the available? method before linking Task to Contact.

Listing 0.7: Updated contents of Task model app/models/blast/tasks/task.rb
module Blast::Tasks
  class Task < ApplicationRecord
    belongs_to :user

    if Blast::Core.available?(:contacts)
      belongs_to :contact, class_name: "Blast::Contacts::Contact",
                 optional: true
    end
  end
end

16.2.3. See how it looks

Let’s take a look at what we’ve done so far. Restart your server, create a task with a contact and you should see that we have a contact linked to a task, as shown in the figures below:

https://s3.amazonaws.com/devblast-modr-book/images/figures/04_16/new_task_with_contact
Figure 1: New Task
https://s3.amazonaws.com/devblast-modr-book/images/figures/04_16/show_task_with_contact
Figure 2: Show Task
https://s3.amazonaws.com/devblast-modr-book/images/figures/04_16/task_list_with_contacts
Figure 3: Task List

16.3. Show Tasks under Contact

16.3.1. Create a hook in the Contacts show view

Before we add an override, we need a hook in the show view of a contact. Add the following line at the end of the file:

Listing 0.8: Tasks hook in Contacts show view app/views/blast/contacts/contacts/show.html.erb
.
.
.
<span data-blast-hook='contacts_show'></span>

16.3.2. Create the tasks override

You’ve probably become an expert with Deface by now, and you can extend views even with your eyes closed, but here’s the override to add a list of tasks to a contact (this is in the tasks engine):

touch app/overrides/add_tasks_to_contact.rb
Listing 0.9: Add tasks to contact override app/overrides/add_tasks_to_contact.rb
if Blast::Core.available?(:contacts)
  Deface::Override.new(:virtual_path => "blast/contacts/contacts/show",
                       :name => "add_tasks_to_contact",
                       :insert_after => "[data-blast-hook='contacts_show']",
                       :partial => "overrides/contact_tasks_list",
                       :namespaced => true)
end

16.3.3. Add the override view

We don’t think we need to explain further. You should know what to do:

touch app/views/blast/tasks/overrides/_contact_tasks_list.html.erb
Listing 0.10: Add tasks to contact override view app/views/blast/tasks/overrides/_contact_tasks_list.html.erb
<hr>
<div class="panel panel-primary">
  <div class="panel-heading">
    Tasks for this contact
  </div>
  <table class="table">
    <thead>
      <th>ID</th>
      <th>Title</th>
      <th>Content</th>
      <th>Created On</th>
      <th></th>
    </thead>
    <tbody>
      <% @contact.tasks.each do |task| %>
        <tr>
          <td><%= task.id %></td>
          <td><%= task.title %></td>
          <td><%= task.content %></td>
          <td><%= task.created_at.strftime("%d %b. %Y") %></td>
          <td>
            <%= link_to 'Show', [blast, task], class: 'btn btn-primary' %>
            <%= link_to 'Edit', blast.edit_task_path(task),
                        class: 'btn btn-primary' %>
            <%= link_to 'Destroy', [blast, task],
                        class: 'btn btn-primary' , method: :delete,
                        data: { confirm: 'Are you sure?' } %>
          </td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>

16.3.4. Take a look

Let’s take a look at what we’ve done:

https://s3.amazonaws.com/devblast-modr-book/images/figures/04_16/tasks_for_contact
Figure 4: Sweeet!

16.4. Remove engines

16.4.1. Remove the Tasks engine

But what happens if we remove the Tasks engine? Let’s try.

Comment out gem blast_tasks, path: './engines/tasks' in the parent gemfile:

Listing 0.11: Tasks gem commented out blast_crm/Gemfile
gem 'blast_core', path: './engines/core'
gem 'blast_contacts', path: './engines/contacts'
# gem 'blast_tasks', path: './engines/tasks'

Now run bundle install, restart your server and reload the page.

https://s3.amazonaws.com/devblast-modr-book/images/figures/04_16/contact_with_no_tasks_module
Figure 5: Still working and no tasks, great!

16.4.2. Remove the Contacts engine

Now, let’s try to remove the Contacts engine instead.

Listing 0.12: Contacts gem commented out blast_crm/Gemfile
gem 'blast_core', path: './engines/core'
# gem 'blast_contacts', path: './engines/contacts'
gem 'blast_tasks', path: './engines/tasks'

Now run bundle install, restart your server and reload the page.

https://s3.amazonaws.com/devblast-modr-book/images/figures/04_16/tasks_with_no_contacts_module
Figure 6: Still working and no contacts, great!

16.4.3. Put back everything and relax.

Before we continue, let’s uncomment all our modules, re-run bundle install and restart our server.

16.5. Pushing Our Changes

We must not forget to commit our changes:

  1. Check the changes:
    git status
    
  2. Stage them:
    git add .
    
  3. Commit them:
    git commit -m "Listing Tasks for Contacts"
    
  4. Push to your GitHub repo if you’ve configured it:
    git push origin Chapter-16
    

16.6. Wrap Up

In this chapter we extended the Contacts module from the Tasks module, and we add functionality to check if a module exists, so that modules that rely on each other can also work independently.

16.6.1. What did we learn?

How to check if a module exists before we call methods and objects from that module.

16.6.2. Next Step

Next, we’ll be extending the Dashboard to display Tasks.