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
available?
methodTo 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:
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
Now that we have the available?
method, we can start using it to link our modules.
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!
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:
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:
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.
It’s time to add the relationships between our models.
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
Note how we check if the Contacts
module is present before trying to extend the Contact
model.
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:
app/decorators/models/user_decorator.rb
Blast::User.class_eval do
has_many :tasks, class_name: "Blast::Tasks::Task"
end
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
.
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
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:
Contacts
show viewBefore 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:
app/views/blast/contacts/contacts/show.html.erb
.
.
.
<span data-blast-hook='contacts_show'></span>
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
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
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
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>
But what happens if we remove the Tasks
engine? Let’s try.
Comment out gem blast_tasks, path: './engines/tasks'
in the parent gemfile:
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.
Now, let’s try to remove the Contacts
engine instead.
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.
Before we continue, let’s uncomment all our modules, re-run bundle install
and restart our server.
We must not forget to commit our changes:
git status
git add .
git commit -m "Listing Tasks for Contacts"
git push origin Chapter-16
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.
How to check if a module exists before we call methods and objects from that module.
Next, we’ll be extending the Dashboard to display Tasks.