First Looks at Rails

ActiveRecord is sexy

It’s time to meet ActiveRecord, the heart of Rails in my opinion. ActiveRecord handles all interactions with your data and effectively turns your database results into workable objects for your code. This is called Object Relational Mapping and it’s very useful. How it works is ActiveRecord takes a look at your database and if certain conventions are met (or you give it enough of a nudge) it can interpret table names, columns and column values as an object for you to manipulate. Say we have a table called shows that contains tv show information and it looks like this:

shows:
id name description day rating_id genre_id
1 Veronica Mars kick-ass, snarky sleuth 2 5 2
2 My Name is Earl karma hurts 4 4 1

It’s important to have an id column as the primary key (with auto increment) for every table that represents and object in your code

We can clue ActiveRecord in about shows with the following:

class Show < ActiveRecord::Base end

Yeah it’s empty, for the moment, but even with just this Active Record can deduce that there is a table called shows and it should map the rows it grabs from it to use an object called Show. These object inherit from ActiveRecord helpful methods that will let us play with these rows to our hearts content. Here’s a taste:

############################################################# ### You *only* need the following for standalone ruby ### ### Not within rails itself; just outside ### require 'rubygems' require 'active_record' ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => 'db/your_database_here.db' ) ### okay, back to your regularly scheduled program ### ############################################################# @shows = Show.find(:all) # => array of Show objects @shows.size # => 2 @shows.first.name # => "Veronica Mars" Show.create :name => 'New Show', :description => 'It will be great' # => new Show object @shows = Show.find(:all) # => array of Show objects @shows.size # => 3 @shows.last.name # => "New Show" @earl = Show.find_by_name "My Name is Earl" # => Show object for My Name is Earl @earl.name # => "My Name is Earl" @earl.name = "My Name Is Earl" # => "My Name Is Earl" @earl.save # saves to database with modification

Validate this!

That’s fun, but there’s more. If we need to ensure that some fields are always present, or maybe that they’re a certain size or any other checks how would we do that? Heading back to our Show object, we can add a number of validates_* options like so:

class Show < ActiveRecord::Base validates_presence_of :name # if no name is specified than fail validates_length_of :name, :maximum => 50 # can't be greater than 50 validates_numericality_of :day # must be a number

If we try to test those boundaries…

show = Show.new :description => 'No Name', :day => 'Monday' show.save # => false (won't save it) show.errors.empty? # => false show.errors.count # => 2 show.each_full { |msg| puts msg } # => "Name can't be blank" + "Day is not a number" show.attributes = { 'name' = 'The Amazing Race', 'day' = 3 } show.save # => true

That deserves a Montgomery Burns “eggscelllent” dance.

Associations & You

You might be wondering when I was going to mention rating_id and genre_id. What’s that all about? Just setting the stage for associations. There’s more to TV shows than name and description and the same is true for most things you’d store in a database. Otherwise it would be simpler to stick with a text file like a CSV or a spreadsheet. Here’s a wider look at the database, including two other tables ratings and genres:

shows:
id name description day rating_id genre_id
1 Veronica Mars kick-ass, snarky sleuth 2 5 2
2 My Name Is Earl karma hurts 4 4 1
3 The Amazing Race No Name 3 null null
genres:
id name
1 Comedy
2 Drama
3 Science Fiction/Fantasy
4 Children’s
5 Sports
6 News/Entertainment
7 Reality
ratings:
id audience
1 Y
2 Y7
3 G
4 PG
5 TV14
6 TVMA

Notice that in the original shows table the column names were the singular form of the association followed by id (eg: rating_id). This convention signals the foreign key relationship; it can be specifically set with the :foreign_key option

To use these in our application, we need to add some code to our Show object and make Rating and Genre objects.

class Show < ActiveRecord::Base belongs_to :rating belongs_to :genre end class Rating < ActiveRecord::Base has_many :shows end class Genre < ActiveRecord::Base has_many :shows end

Use belongs_to for the table that contains the foriegn key (eg. ratings_id)

This setups up a 1-to-many relationship. A show has a rating and ratings hold many shows. There’s a lot to associations, but for now let’s look at this simple has_many relationship.

@earl = Show.find_by_name "My Name Is Earl" @earl.rating.audience # => "PG" @earl.rating.genre # => "Comedy"

With the relationships setup with has_many, ActiveRecord connected the dots from Earl’s rating_id of 4 to the rating PG whose id is 4. Let’s move on to make associations by adding a rating and genre to “The Amazing Race.”

@tar = Show.find 3 # look up by id @tar.rating = Rating.find_by_audience("PG") @tar.genre = Genre.find_by_name("Reality") @tar.description = "race around the world" @tar.save shows:
id name description day rating_id genre_id
1 Veronica Mars kick-ass, snarky sleuth 2 5 2
2 My Name Is Earl karma hurts 4 4 1
3 The Amazing Race No Name 3 4 7

Simple enough eh? We’ll be going over other types of associations in class and later on in the course.

Exercises

Exercise-2-1
require 'rubygems'
require 'active_record'
require 'utils'

# this is init_db from utils.rb
# check out all the source through subversion
#
# http://social.itp.nyu.edu/dedi/svn/rails.itp
#
# username: itp with the usual password
#

#def init_db
#  logfile = '/Users/dedi/Documents/itp/awd/examples/week2/shows.log'#
#
#  # let's warm up the database goodness
#  ActiveRecord::Base.logger = Logger.new(logfile)
#  ActiveRecord::Base.colorize_logging = false
#  ActiveRecord::Base.establish_connection(
#    :adapter => 'sqlite3',
#    :database => 'db/shows.db' )
#end
init_db() # kick off ActiveRecord

class Show < ActiveRecord::Base
  belongs_to :rating
  belongs_to :genre
end

class Rating < ActiveRecord::Base
  has_many :shows
end
class Genre < ActiveRecord::Base
  has_many :shows
end

if __FILE__ == $0
  command = ARGV[0]

  case command
  when 'help'
    usage =  %Q{USAGE: ruby tv.rb command parameters\n\n}
    usage << %Q{migrate\t\t- make db changes with migrations\n}
    usage << %Q{list\t\t- list shows\n}
    usage << %Q{day\t\"day"\t- list shows on day\n}
    usage << %Q{name\t"name"\t- find by name\n}
    usage << %Q{rate\t"name"\t- add rating to show\n}
    usage << %Q{genre\t"name"\t- add genre to show\n}
    usage << %Q{add\t"name" "description" "day"\t- add a show\n}
    print usage

  when 'name'
    @shows = Show.find_by_name ARGV[1]
    list_shows(@shows)

  when 'add'
    @show = Show.create :name => ARGV[1], :description => ARGV[2],
      :day => to_day_id(ARGV[3])

  when 'day'
    @shows = Show.find_by_day to_day_id(ARGV[1])
    list_shows(@shows)

  when 'rate'
    @show = Show.find_by_name ARGV[1]
    @rating = Rating.find_by_audience(ARGV[2])

    @show.rating = @rating
    puts "Rating added" if @show.save

  when 'genre'
    @show = Show.find_by_name ARGV[1]
    @genre = Genre.find_by_name(ARGV[2])

    @show.genre = @genre
    puts "Genre added" if @show.save 

  when 'migrate'
    ActiveRecord::Base.logger = Logger.new(STDERR)
    ActiveRecord::Base.colorize_logging = true
    migrate(ENV["VERSION"] ? ENV["VERSION"].to_i : nil)

  else
    @shows = Show.find :all, :order => 'day asc'
    list_shows(@shows)
  end
end

link download

Homework (Due 2006-05-30)

Reading

Things to Try

Development Journal