Intermediate Rails: Understanding Models, Views and Controllers

by Kalid Azad · 85 comments

I’m glad people liked the introduction to Rails; now you scallawags get to avoid my headaches with the model-view-controller (MVC) pattern. This isn’t quite an intro to MVC, it’s a list of gotchas as you plod through MVC the first few times.

Here’s the big picture as I understand it:

The browser makes a request, such as http://mysite.com/video/show/15

The web server (mongrel, WEBrick, etc.) receives the request. It uses routes to find out which controller to use: the default route pattern is “/controller/action/id” as defined in config/routes.rb. In our case, it’s the “video” controller, method “show”, id “15″. The web server then uses the dispatcher to create a new controller, call the action and pass the parameters.

Controllers do the work of parsing user requests, data submissions, cookies, sessions and the “browser stuff”. They’re the pointy-haired manager that orders employees around. The best controller is Dilbert-esque: It gives orders without knowing (or caring) how it gets done. In our case, the show method in the video controller knows it needs to lookup a video. It asks the model to get video 15, and will eventually display it to the user.

Models are Ruby classes. They talk to the database, store and validate data, perform the business logic and otherwise do the heavy lifting. They’re the chubby guy in the back room crunching the numbers. In this case, the model retrieves video 15 from the database.

Views are what the user sees: HTML, CSS, XML, Javascript, JSON. They’re the sales rep putting up flyers and collecting surveys, at the manager’s direction. Views are merely puppets reading what the controller gives them. They don’t know what happens in the back room. In our example, the controller gives video 15 to the “show” view. The show view generates the HTML: divs, tables, text, descriptions, footers, etc.

The controller returns the response body (HTML, XML, etc.) & metadata (caching headers, redirects) to the server. The server combines the raw data into a proper HTTP response and sends it to the user.

It’s more fun to imagine a story with “fat model, skinny controller” instead of a sterile “3-tiered architecture”. Models do the grunt work, views are the happy face, and controllers are the masterminds behind it all.

Many MVC discussions ignore the role of the web server. However, it’s important to mention how the controller magically gets created and passed user information. The web server is the invisible gateway, shuttling data back and forth: users never interact with the controller directly.

SuperModels

Models are fat in Railsville: they do the heavy lifting so the controller stays lean, mean, and ignorant of the details. Here’s a few model tips:

Ruby can also handle “undefined” methods with ease. ActiveRecord allows methods like “find_by_login”, which don’t actually exist. When you call “find_by_login”, Rails handles the “undefined method” call and searches for the “login” field. Assuming the field is in your database, the model will do a query based on the “login” field. There’s no configuration glue required.

ActiveRecord defines a “[]” method to access the raw attributes (wraps the write_attribute and read_attribute). This is how you change the raw data. You can’t redefine length using

def length # this is bad
length / 60
end

because it’s an infinite loop (and that’s no fun). So self[] it is. This was a particularly frustrating Rails headache of mine – when in doubt, use self[:field].

Never forget you’re using a database

Rails is clean. So clean, you forget you’re using a database. Don’t.

Save your models. If you make a change, save it. It’s very easy to forget this critical step. You can also use update_attributes(params) and pass a hash of key -> value pairs.

Reload your models after changes. Suppose a user has_many videos. You create a new video, point it at the right user, and call user.videos to get a list. Will it work?

Probably not. If you already queried for videos, user.videos may have stale data. You need to call user.reload to get a fresh query. Be careful — the model in memory acts like a cache that can get stale.

has_one: linked_from another table. A status is linked_from a user. In fact, statuses don’t even know about users – there’s no mention of a “user” in the statuses table at all. Inside class Status we’d write has_many :users (has_one and has_many are the same thing – has_one only returns 1 object that links_to this one).

A mnemonic:

“belongs_to” rhymes with “links_to”

“has_one” rhymes with “linked_from”

Well, they sort of rhyme. Work with me here, I’m trying to help.

These associations actually define methods used to lookup items of the other class. For example, “user belongs_to status” means that user.status queries the Status for the proper status_id. Also, “status has_many :users” means that status.users queries the user table for everyone with the current status_id. ActiveRecord handles the magic once we declare the relationship.

Quick Controllers

Have default methods (added by ActionController). Visiting http://localhost:3000/user/show will attempt to call the “show” action if there is one, or automatically render show.rhtml if the action is not defined.

Pass instance variables like @user get passed to the view. Local variables (those without @) don’t get passed.

Are hard to debug. Use render :text => "Error found" and return to do printf-style debugging in your page. This is another good reason to put code in models, which are easy to debug from the console.

Use sessions to store data between requests: session[:variable] = “data”.

I’ll say it again because it’s burned me before: use @foo (not “foo”) to pass data to the view.

Using Views

Views are straightforward. The basics:

Controller actions use views with the same name (method show loads show.rhtml by default)

Controller instance variables (@foo) are available in all views and partials (wow!)

Run code in a view using ERB:

<% ... %>: Run the code, but don’t print anything. Used for if/then/else/end and array.each loops. You can comment out sections of HTML using Hi there . You get a free blank line, since you probably have a newline after the closing %>.

<%- ... %>: Run the code, and don’t print the trailing newline. Use this when generating XML or JSON when breaking up .rhtml code blocks for your readability, but don’t want newlines in the output.

<%= ... %>: Run the code and print the return value, for example: (You did remember the @ sign for controller variables passed to the view, right?). Don’t put if statements inside the <%=, you’ll get an error.

<%= h ... %>: Print the code and html escape the output: > becomes >. h() is actually a Ruby function, but called without parens, as Rubyists are apt to do.

It’s a bit confusing when you start out — run some experiments in a dummy view page.

Take a breather

The MVC pattern is a lot to digest in one sitting. As you become familiar with it, any Rails program becomes easy to dissect: it’s clear how the pieces fit together. MVC keeps your code nice and modular, great for debugging and maintenance.

In future articles I’ll discuss the inner details of MVC and how Rails forms work (another headache of mine). If you want a jumpstart on the nitty gritty, browse the Rails source and try to follow the path of a request:

Thanks again Kalid…I am working with Code Igniter (a very nice PHP framework) at the moment and much of what you mention here is relevant as CI is similar to Rails framework.
Your articles are always informative but very easy to follow…keep on blogging

A small clarification: actually the guys designing Rails made a mistake. The design pattern in the diagram is not MVC, but MVP (Model-View-Presenter), a derivative of MVC. In real MVC, views are supposed to communicate directly with models, and the controller only selects what model and what view to use. This does not happen neither in RoR nor in 99% of web frameworks that cloned the same mistake. And most of all, templates != views; database != models. They should lie under them.

Thank you so very very much for this article. I’ve been beating my head against a wall for the last three months trying to find info that reads more clearly than gibberease (meaning that it’s supposed to be educational…as long as you’ve been doing web design since the beginning and are completely familiar…no, intimate with all the principles that lead to RoR) and this is one certain gem amongst the rubble. I’ve only gotten thru ~1/4 of the doc so far, but I can understand the way you think.

Here I’m trying to learn the Rails way, but I also have daily work I have to get done to keep our fledgling site running and resolving outstanding issues the original dev left undone…I just wish I had found this months ago. Almost as much as I wish I had gotten a ream of paper for my printer today while I was out so I could print a copy right now… 😉 so, thank you thank you thank you. This is going to help so much!

@Andrew: Awesome, really glad it helped! Exactly, so many articles make gigantic assumptions about what you already know, it can be hard to see it from the beginning. I’m thrilled this was able to help :).

Holy Cow, this article is the best! I looked everywhere for an explanation to the MVC model and after reading this article, I feel like i can teach a class on how to explain it! Awesome, thank you so much for sharing!

Thanks so much for this article. Your write ups have been invaluable to me as I learn ruby and rails for the first time. I’m blogging about my learning process and will be sure to direct some of my readers to your content. Thanks and please keep it up!

“There’s no mention of a “user” in the statuses table at all. Inside class Status we’d write has_many :users “. If you use has_many :users, can you query to see how many users have a specific status? For example, if we wanted to show “active” users. Just not seeing how this would work if there is no column in the status table for users associated with each status.

This article is timesaving!, iwould’ve loved to read just a week ago, this isthe way to learn telling tips and reasons behind them, that isexperiencie about, why some things are relevants giving the knowledge a structure. thanks so much for your article.