Why ActiveRecord is Awesome
ActiveRecord is a Ruby gem that serves up an object relational mapper (ORM) for relational database management systems (RDBMSs). In this post, we'll explore how to use it outside of the Rails ecosystem in the service of fleshing out a simple domain model.
ActiveRecord provides a Ruby interface for creating, reading, updating, and deleting a database and its constituent tables and data. This interface comes without requiring you to rewrite more than a configuration file when switching from SQLite to PostgreSQL. Databases are a useful addition to any Ruby application because they give us a place to save data--Ruby "flushes" data after each execution session.
A lot of people encounter ActiveRecord for the first time when crossing the tracks into Rails territory, but ActiveRecord can be leveraged without the trappings of the entire Rails framework. Any Ruby project that involves a database can benefit from the features ActiveRecord provides!
What You Need
File Directory Structure and Gems
Let's start by laying out a basic file directory structure for our application. You may have you own ideas and preferences--that's more than alright. The structure I'm about to present is a stripped-down version of what you might see in a Rails project. Go to town laying this out with mkdir
and touch
in your directory or start clicking away in your file browser.
app/
» models/
» planet.rb
config/
» environment.rb
» database.yml
db/
» migrate/
» seeds.rb
Gemfile
Rakefile
README.md
It takes a village to raise an application! We'll start by filling in the Gemfile
in our project directory and calling out the gems we want and need.
# where Bundler will look for gems if they're not already installed
source 'https://rubygems.org'
# Ruby's quintessential ORM
gem 'activerecord'
# generates fake data, mostly strings
gem 'faker'
# allows us to create and run tasks from the command line
gem 'rake'
# enables us to import a whole folder's worth of code into a file
gem 'require_all'
# gives us useful Rake tasks for managing a database
gem 'sinatra-activerecord'
# provides us with a primitive Ruby-SQLite3 database
gem 'sqlite3'
If you don't have Bundler installed, now's a good time to hop on the wagon. gem install bundler
ought to do the trick in your terminal.
With Bundler installed, installing our project's gems is as easy as bundle install
(or bundle
, if you're feeling lazy). Bundler scans the gem file for our projects dependencies and downloads and installs whatever is not already on our system. Bundler makes it quick and easy to get any system up to speed with a Ruby application.
Ruby Environment and Database Configuration
Let's take a little trip into the config/
directory. In environment.rb
we will centralize importing gems and local Ruby files so that we need only require environment.rb
in our run files to get things done.
require 'bundler'
Bundler.require
require_all 'app'
That wasn't so hard, was it? require 'bundler'
makes it so that we can execute the following line Bundler.require
and essentially require
everything in the Gemfile
. require_all 'app'
requires all local .rb
files in the app/
folder.
Now comes the weird part. In database.yml
, copy-paste the following code [source]:
# SQLite version 3.x
# gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
Here we specify what kind of RDBMS we are interfacing with, the name and location of the database file (if it doesn't exist it will be generated), then maximum number of open connections (pools) to the database, and the time limit for query operations. sinatra-activerecord
will refer to this file by default to configure the connection between ActiveRecord and the SQLite database.
Rake Tasks
Rake is a Ruby gem that allows us to run tasks, or snippets of Ruby code, from the command line. It also gives us the ability to tidily define these within a file known as the Rakefile
. In our Rakefile
, let's write the following:
# imports everything (gems and local files) specified in environment.rb
require_relative 'config/environment'
# gives us an arsenal of rake tasks for managing our database
require 'sinatra/activerecord/rake'
# describes the task
desc 'starts a console'
# establishes the name of the rake option: console
task :console do
# turns on logging of SQL queries while in the task
ActiveRecord::Base.logger = Logger.new(STDOUT)
# starts a Ruby REPL session
Pry.start
end
Now, by executing rake console
in our terminal while in our project directory, we will be able to start a REPL session and experiment with methods to our hearts content, all with access to the contents of our project and without the fuss and muss of managing a run file.
While ActiveRecord and Rake are separately invaluable gems, their power and usefulness is compounded with the inclusion of sinatra-activerecord
. Right now, while in your project directory in your terminal, run rake -T
in the command line to see your available tasks. See all the ones with the db:
prefix? Sinatra gave us those.
Breathing Life into the Domain Model
Making/Mapping a Class/Model
One of the advantages of using ActiveRecord is that it is an object relational mapper. It gives us the ability to map the structure of our database to the structure of our codebase. Here's a quick refresher on the Active Record pattern of ORMs (for which the ActiveRecord gem is named) with the database element on the left and equivalent codebase element on the right:
- Database = Domain
- Table = Model
- Column = Attribute
- Row = Instance
With ActiveRecord, making our model participate in this scheme is as simple as dirt (or at least inheritance).
In app/models/planet.rb
, park the following lines:
class Planet < ActiveRecord::Base
end
class Planet < ActiveRecord::Base
is our golden goose, as it endows our Planet class (aka our Planet model) with all of the rights and privileges and methods of ActiveRecord therein.
I've stuck a class method in there to print all instances of our Planet class with a little flavor. Yet we've no instances to speak of! Let's fix that.
Creating the Database
ActiveRecord will look specifically for a db/
folder at the top of your project directory, along with a migrate/
subdirectory. ActiveRecord builds out a database with Ruby files called migrations, which specify the action to take on the database and the schema being added or altered.
Thanks to sinatra-activerecord
, we have access to a Rake task that will generate a migration file for us. Run rake db:create_migration NAME=create_planets
in your terminal.
You should now have a file that looks like TIMESTAMP_create_planets.rb
. Inside it, input the following:
class CreatePlanets < ActiveRecord::Migration[6.0]
def change
create_table :planets do |t|
t.string :name
t.boolean :inhabitable
t.timestamps
end
end
end
Here is the table that corresponds to our model, along with specifications for the tables columns (aka the model's attributes). Note that the table name is plural while the model name was singular (see: ActiveRecord Naming Conventions).
To create this table in our database, we need only lean on another rake task sung by Sinatra: rake db:migrate
. If the migration was successful, we will have a schema.rb
file in the db/
directory. Check it! Don't edit it ever! It's created and updated with each migration. It's contents should reflect the contents of our migration.
We've come a long way. It's high time we paused to test the makings of our project.
Test Driving an ActiveRecord Project
Seeding the Database
It is critical we test the functionality of our model and database before further fleshing out our project. While we could always require the formal rspec
gem and write formal tests, we aren't going to do that during this foray. Instead, we'll populate our database with seed data and poke at it while in our rake console
.
In the seeds.rb
file, we are encouraged to write ActiveRecord methods that populate our database (hence the name "seeds"). We will do just that:
boolean_array = [true, false]
5.times do
Planet.create(
name: Faker::Movies::StarWars.planet,
inhabitable: boolean_array.sample
)
end
These 7 lines generate 5 planets with random names and inhabitability booleans. To seed our database with this data, we simply run rake db:seed
in our terminal while in the project directory. We know our seeding was successful if we get no immediate feedback.
Poking at the Database
With our database seeded, let's see if we can do some damage. Pop into the rake console by executing rake console
in your terminal. This command should start a Pry session.
Let's flex some ActiveRecord methods. Our Planet model inherited from ActiveRecord::Base
, so it should have gotten something for it's trouble! Here are a few things you can try:
-
Planet.create(name: STRING, inhabitable: BOOLEAN)
creates a new instance of the Planet class with the specified parameters and saves it in the database -
Planet.all
returns all instances of Planet -
Planet.find(2)
returns the Planet instance withid == 2
-
planet.update(name: STRING)
updates the instance of Planet (planet) with the given attribute-value pair -
Planet.all.last.delete
deletes the last instance of Planet in the database
Full CRUD, just like that! These are but a few of the methods ActiveRecord provides for you. Read the docs to get the full scoop.
Where To Go From Here
Once you grasp domain modeling, CRUD, and the Active Record pattern, ActiveRecord is as powerful as it is intuitive. We've said nothing about relationships between models or validating model attributes.
ActiveRecord empowers us to create and manage RDBMS in sweet, sweet Ruby. We don't need to fret about what flavor of SQL we're using or translating a sorted polymorphic has-many select into a specific query. We can focus on adhering to a rock-solid object-oriented programming pattern to build a readable and maintainable application.
That isn't to say we should simply skip over SQL (in fact, if you don't know SQL, take an hour or two the next chance you get and work through SQLBolt). A basic understanding SQL and RDBMS will help you understand what's going on under the hood of ActiveRecord so you can fix things when they inevitably break down.
When you build out your next (or current) project, think of it in terms of the data flowing through it and how that data can be encapsulated and described. Draw out a domain model and think of the models that make it up and the relationships between them. Think about the attributes that describe those models. Then make your models, write your migrations, seed your database, and play!
Top comments (1)
Extremely helpful article! Thank you, Levi!