Model, View, Controller. If you’ve ever tried Ruby on Rails, those words have probably been drilled into your head a thousand times over. On the other hand, if this is new to you, there’s plenty of resources on Nettuts+ for beginners - but this is not one of them.
If you’ve made it this far, then It’s safe to assume you’re at least an intermediate user of Rails. If so, I congratulate you! The hardest learning curve is behind you. Now, you can start mastering the really cool stuff. In this post, I want to focus on my favorite third of MVC - the one that I think Rails does best: Models.
Object-Orientation
By far the best thing about ActiveRecord (in my opinion) is how rows in a database correspond so directly to classes in your code. But don’t make the mistake of assuming that your Model classes are the database rows! Rather, you should realize that the Model classes are simply a way to interface with the database. The brilliant part is that Rails makes this process so frictionless that you might think that you’re manipulating the information directly. Do not make this mistake.
The key to mastery of Models is using them as “regular” classes, with instance methods, class methods, attributes and the like. You should treat the columns in the database just like parts of your Model, accessing them from outside, but also using them from inside within other constructs like instance and private methods. Learn to integrate the two for a truly object-oriented system.
All that the database does is provide the information. You’re the one who should provide the logic.
Think of Model objects just like any other object; if there’s logic in another part of your code that relates to this object, it’s always better to integrate it within the class. All the database does is provide the information. You’re the one that should provide the logic.
Methods
Let’s say you have a Model that corresponds to a post in a blog. You decide to write all your blog posts with Markdown and store just the Markdown versions in the database. Now, you have to parse this content into HTML whenever you want to display it.
<article> <h1><%= @post.title %<</h1> <%= format_markdown(@post.content) # Just an example, not a real method %> </article>
This basic view file would run the post’s content through a method every time it is displayed. Okay. How can we make it better?
# ./app/models/post.rb class Post < ActiveRecord::Base ... def formatted_content format_markdown(self.content) end end
<article> <h1><%= @post.title %<</h1> <%= @post.formatted_content # much better! %> </article>
I’ve consolidated the content formatting into an instance method. While this doesn’t have any noticeable effect yet, imagine if later you decided to add some more logic to the formatting, for instance checking to see if the content is already in HTML form. This way would make a lot easier to adapt the code later.
It also has the added benefit of making your code more organized and easier to read. If I were looking at your project, the very first place I would look for a method like this would be in your Post class. This kind of organization directly mimics the kind already built into Rails: small, lightweight methods attached to classes.
Class Methods
Class methods allow you consolidate methods that are related to specific functionality into a consistent location. It’s really not a crazy idea. In fact, as an intermediate Ruby developer, it’s one that you should be very comfortable with. Many don’t realize that you can also build class methods in a Model, but Models are classes like any other, why shouldn’t we!?
Instead of putting helper methods in a separate helper file, just add them on to the Model.
A perfect example of this is helper methods, or helper code grouped by its purpose. Instead of putting them in a separate helper file/module (which should only be used for controller and view helpers), just add them on to the Model. For instance, if you wanted a method that will validate the authenticity of a given username and password, you could implement a User.authenticate()
method. Now, if you later want to alter the function for authentication, it’ll be easy to find among the rest of the User-related code.
That last one is an important point. One of the mantras of Rails is “Convention over Configuration.” If all projects stick to similar file and code structure, it’s easy for another developer to come in and start making changes to a project. One of these conventions is grouping model-related helper methods as class methods.
Helper methods that are only used internally in the Model can also exist as class methods. In this case they would be private or protected methods, but the basic idea is the same. If the method needs information specific to an instance, it’s a private instance method. If not, it’s a class method. It’s as simple as that, really.
Virtual Attributes
A Model is not the record in the database. They are created not when data is written to the database, but when you, the programmer, initialize them.
In order to properly understand Models, you should understand their object lifestyle. As I said before, a Model is not the record in the database. They are created not when data is written to the database, but when you, the programmer, initialize them. Since the Model is only an interface to the database’s raw data, you can create a new object from the class to correspond with existing data.
Likewise, they can be deleted by calling their destroy method. This removes the row from the database, and also deletes the Model object.
But you can delete the Model object (not the database row!) by setting the variable to nil, or just letting it go out of scope.
Once you comprehend this, it’s trivial to realize that a Model can have attributes that don’t correspond to a database column. Think about a User Model. This User has a username and password, but because you are a security-conscious programmer, you only want to store a hashed password in the database, not the plain text password.
To accomplish this, you can implement a “virtual attribute,” or an instance variable that isn’t a column in the database. To keep things simple, the user won’t have to do any sort of password confirmation.
# ./app/models/user.rb class User < ActiveRecord::Base attr_accessor :password # database has encrypted_password column ... end
This is a simple, pure Ruby construct that adds getter and setter methods to an object. Now you can encrypt the password before saving it to the database, so the plain-text password is only ever stored in memory, not written to any files.
user = User.new(params[:user]) user.password = params[:user][:password] # redundant, since the above call does this already user.encrypt_password # sets the encrypted password attribute to the virtual attribute encrypted user.save!
While this is far from a perfect implementation (I’ll show you how to improve it later on in the post) it demonstrates the power of Virtual Attributes. You can now go on to implement the encrypt_password
method in any way you like, probably hashing the password stored in memory along with some kind of salt based on the current date or something like that. In this case, it’s important to call that method before saving the user, so the hashed password is generated
Scopes
Consider this:
Post.where("created_at > ?", 1.week.ago).order("created_at ASC")
These objects contain all the information necessary to get data from the database. The object responds to the methods above, so you can incrementally build up a database query by chaining the methods.
Easy enough. We’re just building up a database query by chaining methods together. Have you ever thought, though: where do do those methods come from? It’s actually pretty simple. Certain methods like .where()
, .order()
and .select()
are all used to generate queries. They do this by all returning objects of the class ActiveRecord::Relation. These objects contain all the information necessary to get data from the database. The object responds to the methods above, so you can incrementally build up a database query by chaining the methods.
These methods are called scopes. As you can imagine, they are an incredibly powerful tool, made doubly so by the fact that you can create scopes of your very own!
Imagine again our Post Model. For displaying posts, you often want to have them appear in the order in which they were created. However, it becomes cumbersome to tack on .order("created_at ASC")
to every single query you write. You could implement it in a class method of Post, like this:
# ./app/models/post.rb class Post < ActiveRecord::Base ... def self.chronological self.order("created_at ASC") end end
At first glance, this would seem like a good solution. We’ve bundled relevant code into our object, like the good object-oriented developers that we are, so what’s the problem. The problem comes is if you try to build up a query like before:
Post.where("created_at > ?", 1.week.ago).chronological
Since it’s only Post
that has this method, you’ll see an error like this:
NoMethodError: undefined method `chronological' for #<ActiveRecord::Relation:0x000001011324d0>
So what’s a guy to do. Simple.
# ./app/models/post.rb class Post < ActiveRecord::Base scope :chronological, : order => 'created_at ASC' ... end
Great! Now the example from above works perfectly, plus we still have the code in the right place, in our Post Model.
Scopes are very flexible. If you wanted, you could write the example above like this:
scope :chronological, order('created_at ASC')
The second argument to the scope
method can be a hash (like the first example), an ActiveRecord::Relation object (like the second example), or even a lambda (anonymous function) like this:
scope :chronological, lambda { order('created_at ASC') }
With this technique, you could even pass an argument into .chronological
. Maybe a boolean for ordering ascending or descending?
scope :chronological, lambda { |ascending| ascending ? order('created_at ASC') : order('created_at DESC') }
And call the method like this:
Post.where("created_at > ?", 1.week.ago).chronological(false)
You can do more than just ordering, though.
scope :chronological, : order => 'created_at ASC', :where => 'published = true'
scope :chronological, where(:published => true).order('created_at ASC')
scope :chronological, lambda { |ascending| where(:published => true).order("created_at #{ascending ? 'ASC' : 'DESC'}") }
The last thing I want to show you about scopes is that you can supply what’s called a default_scope
. This is a scope that will be applied to every query based off of this Model. If you only wanted to ever display them in chronological order, you could do:
# ./app/models/post.rb class Post < ActiveRecord::Base default_scope order("created_at ASC") ... end
Isn’t this fun?
Callbacks
If you remember my password encryption example from above, you’ll recall that I mentioned that how we call the encrypt_password
method could be improved. Here’s the original snippet for reference:
user = User.new(params[:user]) user.password = params[:user][:password] user.encrypt_password user.save!
As you can see, if we encrypt the password in this way, we’ll have to call that encryption method every single time we initialize a new User. If we forget to call it, the user won’t have a hashed password. Far from an ideal solution.
Fortunately, in typical Rails fashion, there’s a simple way to fix it.
We can use what’s called a callback, or a piece of code that is called when a certain phase of an object’s lifecycle is reached. For instance:
- Before saving
- After saving
- Before validation
- After validation
# ./app/models/user.rb class User < ActiveRecord::Base ... before_save :encrypt_password def encrypt_password # do something end end
It’s really as simple as that. Whenever a user object is about to be saved, a method is called. In fact, you don’t even have to use a method!
# ./app/models/user.rb class User < ActiveRecord::Base ... before_save do # do something end end
Instead of passing a symbol into the before_save
call, you can just give it a code block. This is ideal for situations where the code you’re calling is only a line or two long, or if it’s only ever used before saving the record.
The full list of callback methods is available in the API documentation. They include ones like before_validation
, after_validation
, after_save
, and others. There’s a lot of them, but they all work pretty much exactly like you’d expect after seeing before_save
in action.
The information above speaks for itself, so I’ll keep this brief. Hopefully you’ve learned something, maybe even a few things. If you already knew this stuff, what tips would you give to others about Rails Models? Post them in comments and keep the learning going!
Comments