Ruby - - By Glenn Goodrich

Understanding the Model-View-Controller (MVC) Architecture in Rails

The following is a short extract from our book, Rails: Novice to Ninja, 3rd Edition, written by Glenn Goodrich and Patrick Lenz. It’s the ultimate beginner’s guide to Rails. SitePoint Premium members get access with their membership, or you can buy a copy in stores worldwide.

The model-view-controller (MVC) architecture that we first encountered in Chapter 1 is not unique to Rails. In fact, it predates both Rails and the Ruby language by many years. Rails, however, really takes the idea of separating an application's data, user interface, and control logic to a whole new level.

Let's take a look at the concepts behind building an application using the MVC architecture. Once we have the theory in place, we'll see how it translates to our Rails code.

MVC in Theory

MVC is a pattern for the architecture of a software application. It separates an application into the following components:

  • Models for handling data and business logic
  • Controllers for handling the user interface and application
  • Views for handling graphical user interface objects and presentation

This separation results in user requests being processed as follows:

  1. The browser (on the client) sends a request for a page to the controller on the server.
  2. The controller retrieves the data it needs from the model in order to respond to the request.
  3. The controller gives the retrieved data to the view.
  4. The view is rendered and sent back to the client for the browser to display.

This process is illustrated in Figure 4-2 below.

MVC in Rails

Separating a software application into these three distinct components is a good idea for a number of reasons, including:

  • improved scalability (the ability for an application to grow)–for example, if your application begins experiencing performance issues because database access is slow, you can upgrade the hardware running the database without other components being affected

  • ease of maintenance—as the components have a low dependency on each other, making changes to one (to fix bugs or change functionality) does not affect another

  • reusability—a model may be reused by multiple views

If you're struggling to get your head around the concept of MVC, don't worry. For now, what's important to remember is that your Rails application is separated into three distinct components. Jump back to the MVC diagram if you need to refer to it later on.

MVC the Rails Way

Rails promotes the concept that models, views, and controllers should be kept separate by storing the code for each element as separate files in separate directories.

This is where the Rails directory structure that we created back in Chapter 2 comes into play. It's time to poke around a bit within that structure. If you take a look inside the app directory, depicted in Figure 4-3, you'll see some folders whose names might start to sound familiar.

As you can see, each component of the model-view-controller architecture has its place within the app subdirectory—the models, views, and controllers subdirectories respectively. (We'll talk about assets in Chapter 7, helpers in Chapter 6, and mailers later on in this chapter. jobs and channels are beyond the scope of this book.)

This separation continues within the code that comprises the framework itself. The classes that form the core functionality of Rails reside within the following modules:

ActiveRecord
ActiveRecord is the module for handling business logic and database communication. It plays the role of model in our MVC architecture.While it might seem odd that ActiveRecord doesn't have the word “model” in its name, there is a reason for this: Active Record is also the name of a famous design pattern—one that this component implements in order to perform its role in the MVC world. Besides, if it had been called ActionModel, it would have sounded more like an overpaid Hollywood star than a software component …
ActionController
ActionController is the component that handles browser requests and facilitates communication between the model and the view. Your controllers will inherit from this class. It forms part of the ActionPack library, a collection of Rails components that we'll explore in depth in Chapter 5.
ActionView
code>ActionView is the component that handles the presentation of pages returned to the client. Views inherit from this class, which is also part of the ActionPack library.

Let's take a closer look at each of these components in turn.

The ActiveRecord Module

ActiveRecord is designed to handle all of an application's tasks that relate to the database, including:

  • establishing a connection to the database server
  • retrieving data from a table
  • storing new data in the database

ActiveRecord has a few other neat tricks up its sleeve. Let's look at some of them now.

Database Abstraction

ActiveRecord ships with database adapters to connect to SQLite, MySQL, and PostgreSQL. A large number of adapters are available for other popular database server packages, such as Oracle, MongoDB, and Microsoft SQL Server, via RubyGems.

The ActiveRecord module is based on the concept of database abstraction. As a refresher from Chapter 1, database abstraction is a way of coding an application so that it isn't dependent upon any one database. Code that's specific to a particular database server is hidden safely in ActiveRecord, and invoked as needed. The result is that a Rails application is not bound to any specific database server software. Should you need to change the underlying database server at a later time, no changes to your application code are required.

Note: The Jury's Out on ActiveRecord

As I said, ActiveRecord is an implementation of the Active Record pattern. There are those that disagree with the approach taken by ActiveRecord, so you'll hear a lot about that, too. For now, I suggest you learn the way ActiveRecord works, then form your judgement of the implementation as you learn.

Some examples of code that differ greatly between vendors, and which ActiveRecord abstracts, include:

  • the process of logging into the database server
  • date calculations
  • handling of Boolean (true/false) data
  • evolution of your database structure

Before I can show you the magic of ActiveRecord in action, though, a little housekeeping is necessary.

Database Tables

Tables are the containers within a relational database that store our data in a structured manner, and they're made up of rows and columns. The rows map to individual objects, and the columns map to the attributes of those objects. The collection of all the tables in a database, and the relationships between those tables, is called the database schema. An example of a table is shown in Figure 4-4.

In Rails, the naming of Ruby classes and database tables follows an intuitive pattern: if we have a table called stories that consists of five rows, this table will store the data for five Story objects. What's nice about the mapping between classes and tables is that there's no need to write code to achieve it; the mapping just happens, because ActiveRecord infers the name of the table from the name of the class.

Note that the name of our class in Ruby is a singular noun (Story), but the name of the table is plural (stories). This relationship makes sense if you think about it: when we refer to a Story object in Ruby, we're dealing with a single story. But the SQL table holds a multitude of stories, so its name should be plural. While you can override these conventions—as is sometimes necessary when dealing with legacy databases—it's much easier to adhere to them.

The close relationship between objects and tables extends even further. If our stories table were to have a link column, as our example in Figure 4-4 does, the data in this column would automatically be mapped to the link attribute in a Story object. And adding a new column to a table would cause an attribute of the same name to become available in all of that table's corresponding objects.

So, let's create some tables to hold the stories we create.

For the time being, we'll create a table using the old-fashioned approach of entering SQL into the SQLite console. You could type out the following SQL commands, although typing out SQL is no fun. Instead, I encourage you to download the following script from the code archive, and copy and paste it straight into your SQLite console that you invoked via the following command in the application directory:

$ sqlite3 db/development.sqlite3
			

Once your SQLite console is up, paste in the following:

CREATE TABLE stories (
  "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  "name" varchar(255) DEFAULT NULL,
  "link" varchar(255) DEFAULT NULL,
  "created_at" datetime DEFAULT NULL,
  "updated_at" datetime DEFAULT NULL
);
			

You don't have to worry about remembering these SQL commands to use in your own projects; instead, take heart in knowing that in Chapter 5 we'll look at migrations. Migrations are special Ruby classes that we can write to create database tables for our application without using any SQL at all.

Note: Seek some SQL Smarts

Even though Rails abstracts away the SQL required to create tables and database objects, you'd be doing yourself a favor if you become familiar with SQL and its syntax. SitePoint has published a book on learning SQL, so check that one out.

Using the Rails Console

Now that we have our stories table in place, let's exit the SQLite console (simply type .quit) and open up a Rails console. A Rails console is just like the interactive Ruby console (irb) that we used in Chapter 2, but with one key difference. In a Rails console, you have access to all the environment variables and classes that are available to your application while it's running. These are not available from within a standard irb console.

To enter a Rails console, change to your readit folder, and enter the command rails console or rails c, as shown in the code that follows. The >> prompt is ready to accept your commands:

$ cd readit
$ rails console
Loading development environment (Rails 5.0.0)
>>
			

Saving an Object

To start using ActiveRecord, simply define a class that inherits from the ActiveRecord::Base. We touched on the :: operator very briefly in Chapter 3, where we mentioned that it was a way to invoke class methods on an object. It can also be used to refer to classes that exist within a module, which is what we're doing here. Flip back to the section on object-oriented programming (OOP) inChapter 3 if you need a refresher on inheritance.

Consider the following code snippet:

class Story < ActiveRecord::Base
end
			

These two lines of code define a seemingly empty class called Story; however, this class is far from empty, as we'll soon see.

From the Rails console, let's create this Story class and an instance of the class called story by entering these commands:

>> class Story < ActiveRecord::Base; end
=> nil
>> story = Story.new
=> #<Story id: nil, name: nil, url: nil, created_at: nil,
  updated_at: nil>
>> story.class
=> Story(id: integer, name: string, link: string,
  created_at: datetime, updated_at: datetime)
			

As you can see, the syntax for creating a new ActiveRecord object is identical to the syntax we used to create other Ruby objects in Chapter 3. At this point, we've created a new Story object; however, this object exists in memory only—we're yet to store it in our database.

We can confirm that our Story object hasn't been saved by checking the return value of the new_record? method:

>> story.new_record?
=> true
			

Since the object is yet to be saved, it will be lost when we exit the Rails console. To save it to the database, we invoke the object's save method:

>> story.save
=> true
			

Now that we've saved our object (a return value of true indicates that the save method was successful), our story is no longer a new record. It's even been assigned a unique ID:

>> story.new_record?
=> false
>> story.id
=> 1
			

Defining Relationships between Objects

As well as the basic functionality that we've just seen, ActiveRecord makes the process of defining relationships (or associations) between objects as easy as it can be. Of course, it's possible with some database servers to define such relationships entirely within the database schema. In order to put ActiveRecord through its paces, let's look at the way it defines these relationships within Rails instead.

Object relationships can be defined in a variety of ways; the main difference between these relationships is the number of records that are specified in the relationship. The primary types of database association are:

  • one-to-one associations
  • one-to-many associations
  • many-to-many associations

Let's look at some examples of each of these associations. Feel free to type them into the Rails console if you like, for the sake of practice. Remember that your class definitions won't be saved, though—I'll show you how to define associations in a file later.

Suppose our application has the following associations:

  • An Author can have one Blog:

    class Author < ActiveRecord::Base
      has_one :weblog
    end
    			
  • An Author can submit many Stories:

    class Author < ActiveRecord::Base
      has_many :stories
    end
    			
  • A Story belongs to an Author:

    class Story < ActiveRecord::Base
      belongs_to :author
    end
    			
  • A Story has, and belongs to, many different Topics:

    class Story < ActiveRecord::Base
      has_and_belongs_to_many :topics
    end
    class Topic < ActiveRecord::Base
      has_and_belongs_to_many :stories
    end
    			

You're no doubt growing tired of typing class definitions into a console, only to have them disappear the moment you exit the console. For this reason, we won't go any further with the associations between our objects for now—instead we'll delve into the Rails ActiveRecord module in more detail in Chapter 5.

The ActionPack Library

ActionPack is the name of the library that contains the view and controller parts of the MVC architecture. Unlike the ActiveRecord module, these modules are more intuitively named: ActionController and ActionView.

Exploring application logic and presentation logic on the command line makes little sense; views and controllers are designed to interact with a web browser, after all! Instead, I'll provide a brief overview of the ActionPack components, and we'll cover the hands-on stuff in Chapter 5.

ActionController (the Controller)

The controller handles the application logic of your program, acting as glue between the application's data, the presentation layer, and the web browser. In this role, a controller performs a number of tasks including:

  • deciding how to handle a particular request (for example, whether to render a full page or just one part of it)
  • retrieving data from the model to be passed to the view
  • gathering information from a browser request and using it to create or update data in the model

When we introduced the MVC diagram in Figure 4-2 earlier in this chapter, it might not have occurred to you that a Rails application can consist of a number of different controllers. Well, it can! Each controller is responsible for a specific part of the application.

For our Readit application, we'll create:

  • one controller for displaying story links, which we'll name StoriesController
  • another controller for handling user authentication, called SessionsController
  • a controller to display user pages, named UsersController
  • a controller to display comment pages, named CommentsController
  • a final controller to handle story voting, called VotesController

Every Rails application comes with an ApplicationController (which lives in app/controllers/application_controller.rb) that inherits from ActionController::Base. All our controllers will inherit from the ApplicationController,There will actually be an intermediate class between this class and the ActionController::Base class; however, this doesn't change the fact that ActionController::Base is the base class from which every controller inherits. We'll cover the creation of the StoriesController class in more detail in Chapter 5. but they'll have different functionality that is implemented as instance methods. Here's a sample class definition for the StoriesController class:

class StoriesController < ApplicationController
  def index
  end

  def show
  end
end
			

This simple class definition sets up our StoriesController with two empty methods: the index method, and the show method. We'll expand upon these methods in later chapters.

Each controller resides in its own Ruby file (with a .rb extension), which lives within the app/controllers directory. The StoriesController class that we just defined, for example, would inhabit the file app/controllers/stories_controller.rb.

Note: Naming Conventions for Classes and Files

You'll have noticed by now that the names of classes and files follow different conventions:

  • Class names are written in CamelCase (each word beginning with a capital letter, with no spaces between words).There are actually two variations of CamelCase: one with an uppercase first letter (also known as PascalCase), and one with a lowercase first letter. The Ruby convention for class names requires an uppercase first letter.

  • Filenames are written in lowercase, with underscores separating each word.

This is an important detail. If this convention is not followed, Rails will have a hard time locating your files. Luckily, you won't need to name your files manually very often, if ever, as you'll see when we look at generated code in Chapter 5.

ActionView (the View)

As discussed earlier, one of the principles of MVC is that a view should contain presentation logic only. This principle holds that the code in a view should only perform actions that relate to displaying pages in the application; none of the code in a view should perform any complicated application logic, nor store or retrieve any data from the database. In Rails, everything that is sent to the web browser is handled by a view.

Predictably, views are stored in the app/views folder of our application.

A view need not actually contain any Ruby code at all—it may be the case that one of your views is a simple HTML file; however, it's more likely that your views will contain a combination of HTML and Ruby code, making the page more dynamic. The Ruby code is embedded in HTML using embedded Ruby (ERb) syntax.

ERb allows server-side code to be scattered throughout an HTML file by wrapping that code in special tags. For example:

<strong><%= 'Hello World from Ruby!' %></strong>
			

There are two forms of the ERb tags pair: one that includes the equals sign, and one without it:

<%= … %>
This tag pair is for regular output. The output of a Ruby expression between these tags will be displayed in the browser.
<% … %>
This tag pair is for execution. The output of a Ruby expression between these tags will not be displayed in the browser.

Here's an example of each ERb tag:

<%= 'This line is displayed in the browser' %>
<% 'This line executes silently, without displaying any output' %>
			

You can place any Ruby code—be it simple or complex—between these tags.

Creating an instance of a view is a little different to that of a model or controller. While ActionView::Base (the parent class for all views) is one of the base classes for views in Rails, the instantiation of a view is handled completely by the ActionView module. The only file a Rails developer needs to modify is the template, which is the file that contains the presentation code for the view. As you might have guessed, these templates are stored in the app/views folder.

As with everything else Rails, a strict convention applies to the naming and storage of template files:

  • A template has one-to-one mapping to the action (method) of a controller. The name of the template file matches the name of the action to which it maps.
  • The folder that stores the template is named after the controller.
  • The extension of the template file is twofold and varies depending on the template's type and the actual language in which a template is written. By default, there are three types of extensions in Rails:

    html.erb
    This is the extension for standard HTML templates that are sprinkled with ERb tags.
    xml.builder
    This extension is used for templates that output XML (for example, to generate RSS feeds for your application).
    json.builder
    This extension is used for templates that output JSON, which is a common data integration for APIs. We'll talk more about JSON in Chapter 9 on advanced topics.

This convention may sound complicated, but it's actually quite intuitive. For example, consider the StoriesController class defined earlier. Invoking the show method for this controller would, by default, attempt to display the ActionView template that lived in the app/views/stories directory. Assuming the page was a standard HTML page (containing some ERb code), the name of this template would be show.html.erb.

Rails also comes with special templates such as layouts and partials. Layouts are templates that control the global layout of an application, such as structures that remain unchanged between pages (the primary navigation menu, for instance). Partials are special subtemplates (the result of a template being split into separate files, such as a secondary navigation menu or a form) that can be used multiple times within the application. We'll cover both layouts and partials in Chapter 7.

Communication between controllers and views occurs via instance variables that are populated from within the controller's action. Let's expand upon our sample StoriesController class to illustrate this point (no need to type any of this out just yet):

class StoriesController < ActionController::Base
  def index
    @variable = 'Value being passed to a view'
  end
end
			

As you can see, the instance variable @variable is being assigned a string value within the controller's action. Through the magic of ActionView, this variable can now be referenced directly from the corresponding view, as shown in this code:

<p>The instance variable @variable contains: <%= @variable %></p>
			

This approach allows more complex computations to be performed outside the view—remember, it should only contain presentational logic—and allow the view to display just the end result of the computation.

Rails also provides access to special containers, such as the params and session hashes. These contain such information as the current page request and the user's session. We'll make use of these hashes in the chapters that follow.

Sponsors