- BetterExplained - https://betterexplained.com -

Intermediate Rails: Understanding Models, Views and Controllers

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:

rails mvc

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:

Using ActiveRecord

class User < ActiveRecord::Base
end

The code < ActiveRecord::Base means your lowly User model inherits from class ActiveRecord::Base, and gets Rails magic to query and save to a database.

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.


Defining Class and Instance Methods

 def self.foo
    "Class method"    # User.foo
  end

  def bar
   "instance method"  # user.bar
  end

Class and instance methods can cause confusion.

user (lowercase u) is an object, and you call instance methods like user.save.

User (capital U) is a class method – you don’t need an object to call it (like User.find). ActiveRecord adds both instance and class methods to your model.

As a tip, define class methods like User.find_latest rather than explicitly passing search conditions to User.find (thin controllers are better).


Using Attributes

Regular Ruby objects can define attributes like this:

  # attribute in regular Ruby
  attr_accessor :name        # like @name
  def name=(val)             # custom setter method
    @name = val.capitalize   # clean it up before saving
  end

  def name                   # custom getter
   "Dearest " + @name        # make it nice
  end

Here’s the deal:

In Rails, attributes can be confusing because of the database magic. Here’s the deal:

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.


Making New Models

There’s two ways to create new objects:

joe = User.new( :name => "Sad Joe" )        # not saved
bob = User.create ( :name => "Happy Bob" )  # saved

Notice how the hash is passed. With Ruby’s brace magic, {} is not explicitly needed so

user = User.new( :name => "kalid", :site => "instacalc.com" )

becomes

User.new( {:name => "kalid", :site => "instacalc.com"} )

The arrow (=>) implies that a hash is being passed.


Using Associations

Quick quiz, hotshot: suppose users have a “status”: active, inactive, pensive, etc. What’s the right association?

class User < ActiveRecord::Base
  belongs_to :status  # this?
  has_one :status     # or this?
end

Hrm. Most likely, you want belongs_to :status. Yeah, it sounds weird. Don’t think about the phrase “has_one” and “belongs_to”, consider the meaning:

A mnemonic:

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.


Using Custom Associations

Suppose I need two statuses, primary and secondary? Use this:

belongs_to :primary_status, :model => 'Status', :foreign_key => 'primary_status_id'
belongs_to :secondary_status, :model => 'Status', :foreign_key => 'secondary_status_id'

You define a new field, and explicitly reference the model and foreign key to use for lookups. For example, user.primary_status returns a Status object with the id of “primary_status_id”. Very nice.

Quick Controllers

This section is short, because controllers shouldn’t do much besides boss the model and view around. They typically:

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:

Run code in a view using ERB:

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:

But all in good time my friends — I’ll explain it as I understand it. And if you had any forehead-slapping moments with MVC, drop me a note below.

Other Posts In This Series

  1. Starting Ruby on Rails: What I Wish I Knew
  2. Intermediate Rails: Understanding Models, Views and Controllers
  3. A Simple, Comprehensive Overview of Javascript
  4. Using JSON to Exchange Data
  5. How To Make a Bookmarklet For Your Web Application