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
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.
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:
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!
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
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!
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 []>
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:
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!
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.
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.
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.
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
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)
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
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:
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!
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
.
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
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
Now we can change the override view that we created a few steps back and use @contacts
instead of current_user.contacts
.
app/views/blast/contacts/overrides/_contacts_list.html.erb
<div class="col-md-6">
<!-- ... -->
<%- @contacts.each do |contact| %>
<!-- ... -->
</div>
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!
It’s now time to push our changes to GitHub. You should know the steps by now:
git status
git add .
git commit -m "Extending Contacts Models & Controllers"
git push origin Chapter-12
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.
decorators
so extend our Models and Controllers
.class_eval
method in our decorators
Next, we’ll be creating our Tasks
module, but first, we have an exercise for you!