The MVC Design Pattern
Let's talk about design patterns. The term sounds so cool and computer-sciencey and can be used to impress your friends and colleagues at parties. But all it really means to implement a design pattern is to write some code in a way that lots of other software developers have done before, in order to solve a common problem. Do a Google search for "software design pattern", and we'll find dozens of patterns that pro developers use every day.
Shockingly! – we are not the first software developers on Earth to be writing a dynamic web application backed by a database. In fact, it's one of the more common types of applications that web developers want to build. And there is a design pattern that's been used time and time again, by millions of developers, in order to do so – the Model-View-Controller (MVC) design pattern.
The MVC pattern, in its most basic form, says that we should separate our application into three layers, with the code for each layer in their own files and folders. These layers are:
- Models – code that talks directly to our database
- Views – code that creates what the user sees in the browser, like HTML and its cousin ERB
- Controllers – code that connects and controls traffic between the models and views
Of course, we've already been introduced to models and views in previous lessons. And we've also seen that Ruby on Rails implements the MVC design pattern right out of the box, with the separate app/models
, app/views
, and app/controllers
directories that come with Rails by default.
So what is the controller and how does it fit into the equation? After all, we've created working applications already, without the controller – what is it and why do we need it? Let's just dive right in and implement one, then we'll discuss why the controller is important – it's actually quite straightforward. Let's begin with our dice resource from last time:
Let's make just a few subtle changes, including moving those first three lines of Ruby out of the view and into the DiceController
, which was automatically created when we did rails generate controller dice
earlier.
There are quite a few new concepts happening here – let's just take it one at a time!
- We've opened up the source code for our
DiceController
, which is a Ruby class. Classes in Ruby are used to encapsulate functionality, as we've previously seen with model classes. Controller classes do a different job than model class though – while model classes are used to communicate with the database, controller classes are used as the glue between views and the rest of our code. - Each method of the controller class, i.e. the code that begins with
def
and concludes withend
, is known as an action. We'll notice that the name of each action corresponds to the name of each view. In this example, we have a view calledindex.html.erb
and an action method calledindex
. - We've moved the code that previously lived at the top of our
index.html.erb
view into this new controller/action – but we need to use variables likedie1
anddie2
that are shared between the two files. In order to share variables between the controller and the view, the variable must be prefixed with the@
symbol. So we've renameddie1
anddie2
to@die1
and@die2
.
But why do this? Why not leave well enough alone? It seems like we're adding more complexity to do the same thing we did before! Seems like MV was just fine; no need for MVC. These are valid points – but there are benefits to fully implementing the MVC pattern, even in this tiny example:
- Our view is now free and clean of extraneous Ruby code. All of the application logic needed to supply the view with data is now in the controller, and there are only tiny sprinkles of ERB – only where absolutely needed to dynamically embed data in our HTML.
- Coupling our Ruby-based logic with the view just gets plain ugly as our applications become more complex. It's only three lines today, but imagine 100 lines of code at the top of our ERB file.
- Web applications may need to produce other types of views. Imagine a time when our application might produce other types of output, like a JSON-based API, a PDF, or an Excel spreadsheet. Or perhaps we want different HTML views for mobile and desktop, each sharing the same logic and data. Moving the code that generates this data to the controller layer allows for this flexibility.