Skip to Content
Course content

27: ActiveRecord Basics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've been handling data in memory. That's fine for a script that runs for ten seconds, but it's useless for a real application because everything vanishes the moment the process exits. This is where ActiveRecord comes in. It's an ORM—Object-Relational Mapper—which is a fancy way of saying it lets us treat database rows as if they were regular Ruby objects.

I've always found ActiveRecord to be one of the most "magical" parts of the Ruby ecosystem. It does a lot behind the scenes, which is great until it isn't. To get a handle on it, let's build a simple reading list tracker. We want to keep track of books, their authors, and whether we've actually finished reading them.

Setting up the Book model

First, we need a class that inherits from ActiveRecord::Base. This inheritance is what gives our class all the power to talk to the database without us writing a single line of SQL.

require 'active_record'

# For this example, we'll use an in-memory SQLite database so you don't 
# have to set up a database server on your machine.
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')

class Book < ActiveRecord::Base
end

Now, the model is just a Ruby class, but it doesn't know what the database table looks like yet. In a full Rails app, we'd use migration files. Since we're keeping this lean, I'll define the schema directly in the script.

ActiveRecord::Schema.define do
  create_table :books do |t|
    t.string :title
    t.string :author
    t.boolean :read, default: false
  end
end

Adding and finding books

Now that the table exists, we can start playing with data. The .create method is the quickest way to instantiate an object and save it to the database in one go.

Book.create(title: "The Hobbit", author: "J.R.R. Tolkien")
Book.create(title: "Foundation", author: "Isaac Asimov")
Book.create(title: "Neuromancer", author: "William Gibson")

# Let's grab a specific book by its ID
book = Book.find(1)
puts "Found: #{book.title}" # Output: Found: The Hobbit

I personally prefer find_by over find when I don't have the ID handy. find raises an exception if the record isn't found, which can crash your app if you aren't careful. find_by just returns nil.

The "Collection vs. Object" trip-up

Here is where I almost tripped up when I first started with ActiveRecord. I wanted to mark "Foundation" as read. I tried this:

# This is my mistake:
Book.where(title: "Foundation").update(read: true)
# NoMethodError: undefined method `update' for #<ActiveRecord::Relation...>

I forgot a fundamental rule: .where always returns an ActiveRecord::Relation—which is essentially a collection (like an array)—even if only one record matches. You can't call update on a collection; you have to call it on a single instance of a model.

To fix this, I need to tell ActiveRecord I want the first matching record from that collection:

# The correct way:
Book.where(title: "Foundation").first.update(read: true)

Filtering and querying

The real power of ActiveRecord is how it handles queries. Instead of writing SELECT * FROM books WHERE read = false, we can use Ruby methods. I find that chaining these methods makes the code read almost like a sentence.

# Get all books we haven't read yet
unread_books = Book.where(read: false)

unread_books.each do |book|
  puts "#{book.title} is still waiting to be read!"
end

If you ever wonder what ActiveRecord is actually doing under the hood, you can look at your logs or use .to_sql on a query. It's a great way to make sure you aren't accidentally writing a query that will destroy your database performance once you have a million rows.




📋 Practical Task

Building a Book Archive Manager

Your task is to extend the reading list example into a small archive manager. Create a script that does the following:

  • Sets up an in-memory SQLite database and a Book model with title, author, and read (boolean) columns.
  • Seeds the database with at least four different books.
  • Implements a "Mark as Read" feature: Use find_by to locate a specific book by its title and update its read status to true.
  • Implements a "Cleanup" feature: Use destroy_all to remove all books from the database that have already been read.
  • Finally, print the titles of the remaining books in the database to verify the cleanup worked.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.