advertisement

Print

Understanding ActiveRecord: A Gentle Introduction to the Heart of Rails (Part 1)
Pages: 1, 2

Not Just CRUD: Validations and Simple Domain Logic

Though you can accomplish a lot with the four main data manipulation operations, you'll often need more than that for real applications. Validating data and adding domain-specific functionality are part of developing any real application.



We've worked with migrations already, but we haven't actually worked with our model definition yet. When we ran the script to create our model, it dropped a file in app/models called entry.rb

By default, it's pretty uninspiring:

class Entry < ActiveRecord::Base
end

Validation Basics

Let's start by adding a simple validation to our project. We'll need to move this later when we have multiple users, but for now, let's prevent entries from having duplicate URIs.

ActiveRecord makes this easy for us with a built-in validation:

class Entry < ActiveRecord::Base
  validates_uniqueness_of :url
end

Starting with an empty entries table, we'll create an entry and then try to create another, and then ensure that the validation captures the error.

>> Entry.find(:all)
=> []
>> wont_break = Entry.create(:url => "http://google.com", 
?>              :short_description => "google", 
?>              :long_description => "A Search Engine")
>> will_break = Entry.create(:url => "http://google.com", 
?>              :short_description=> "also google",
?>              :long_description => "A Search Engine...")
>> wont_break.valid?
=> true
>> will_break.valid?
=> false
>> will_break.errors["url"]
=> "has already been taken" 
>> Entry.find(:all).length
=> 1

Notice that only one record has been saved, and that you can get the errors for the url field by just inspecting the errors attribute.

Adding Custom Validations

There are a whole score of standard validations, but you can also create custom validations. The following code ensures that our short description does not include references to Rube Goldberg, for example:

  class Entry < ActiveRecord::Base
    validates_uniqueness_of :url

    protected

    def validate
      if short_description =~ /rube goldberg/i
        errors.add("short_description", "can't include references to Rube")
      end
    end
  end

Here is an example that triggers the validation:

>> evil_goldberg = Entry.create(:url => "http://google.com",
?>   :short_description => "Rube Goldberg's favorite page",
?>   :long_description => "This is another failing create" )
>> evil_goldberg.valid?
=> false
>> evil_goldberg.errors.full_messages
=> ["Short description can't include references to Rube", "Url has already been taken"]

You can see that all errors are captured, not just the first one encountered. We'll add some more realistic validations once we start hooking everything up. The key thing to take away here is that you can add bits of code that can inspect data and look for potential errors before it is committed to your database. In practice, this feature set is essential.

Simple Domain Logic

You often will need to add custom domain logic in your models. Though this sounds fancy, it really just means Ruby methods that do something with your model.

For example, say we wanted a simplified representation of our created_at and updated_at fields, showing only the date, in the form of yyyy.mm.dd.

This is easy with Ruby's Time#strftime.

The following code is our model definition as it stands so far, with created_date and updated_date being our new methods.

class Entry < ActiveRecord::Base

  validates_uniqueness_of :url

  def created_date
    created_at.strftime("%Y.%m.%d")
  end

  def updated_date
    updated_at.strftime("%Y.%m.%d")
  end

  protected

  def validate
    if short_description =~ /rube goldberg/i
      errors.add("short_description", "can't include references to Rube")
    end
  end

end

Now if you fire script/console back up, you can see how this works on your data.

>> Entry.find(1).created_date
=> "2007.04.10" 
>> Entry.find(1).created_at  
=> Tue Apr 10 09:07:02 -0400 2007
>> Entry.find(1).updated_date 
=> "2007.04.10" 
>> Entry.find(1).updated_at  
=> Tue Apr 10 09:07:02 -0400 2007

The possibilities here are really only limited to the concepts you are modeling and the functionality you'll need. The general idea is that the model should handle as much of its own data manipulation as possible, as it is the object that knows best what kinds of data it has to deal with.

This pretty much sums up the bare minimum of what you need to know to begin using ActiveRecord. For the visually oriented, your eyes might be glazing over looking at all this stuff on the command line. Let's work to get a simple view attached to your entries so you can see something, and then we'll build on this simple base in the next part of this article.

Get Ready to Fire Up Your Web Browser

You may have noticed that we've done all of our explorations so far through the Rails console, and we haven't even touched our browser once. As you become more experienced with Ruby and Rails development, this becomes a Ninja super power. However, I'm sure at least some of you are skeptical that we're actually building something here.

In the next part of this article, we'll actually tie this stuff together to build something you can use, adding some new functionality where needed.

For now, let's just get Rails to give us a simple list of entries.

We're going to create a controller/view combination now. What this will do is hook up our model to something that can actually be viewed in a browser. I won't go into detail about this here, but there are several tutorials that can help fill in background information.

Our controller name will be Entries, and we're just going to create a simple index page:

  $ script/generate controller entries

This will create a number of files and folders. The one we'll need to edit is app/controllers/entries_controller.rb.

For now, all we need it to do is grab the list of entries. The code looks like this:

class EntriesController < ApplicationController
  def index
    @entries = Entry.find(:all)
  end
end

Now, we'll need to throw together a little HTML to actually display our entries.

Under Rails conventions, the controller method name maps to the file name, so we'll want to put our view in app/views/entries/index.rhtml.

<html>
  <body>
    <h2> List of all entries </h2>
    <ul>
      <% @entries.each do |e| %>
        <li><%= e.short_description %> ( <%= e.url %> )</li>
      <% end %>
    </ul>
  </body>
</html>

The above code just goes through each entry in the database, and outputs the short description and the URL for that entry.

Go ahead and fire up the Rails server now.

$ script/server

Now open up your browser, and point it to http://localhost:3000/entries.

If all goes well, you should see something like this:


List of all entries

  • google (http://google.com)

If you want, you can play around with adding, removing, and updating Entry objects using the previous CRUD examples in script/console.

Notice that you can just reload your browser to see the changes, and that there is no need to reboot script/server while in development mode.

That's All For Now

This is a lot of information to swallow, so it's time to take a break. You should have gained a basic understanding of how CRUD operations work in Rails, as well as how to perform simple validations and add domain logic to your models. You've probably also become pretty comfortable with script/console, which will be an indispensable tool in your Rails toolbelt. You even have something that loads up in your web browser that's able to talk to your database and display your data properly.

We don't quite have a social bookmarking application yet. We're still missing some of the pieces of that puzzle, which include relationships between tables, complex find operations, and some other fun ActiveRecord tricks. The next part of this article will fill in that information while we develop Tasty into something actually usable. Until then, you probably already have enough knowledge to do some more exploration of Rails, and I can't recommend enough the value of experimentation as a way to learn new systems.

Happy hacking!

Gregory Brown is a New Haven, CT based Rubyist who spends most of his time on free software projects in Ruby. He is the original author of Ruby Reports.


Return to Ruby.