Active Record Migration

Jason Kuffler
6 min readOct 5, 2021

The following material is intended for readers with a basic understanding of Ruby, SQL(ite), and using a Framework — in this case Active Record (AR)is the framework we will use to demonstrate the database build process.

The Rails Guide on Migrations clarifies the definition of database migrations:

“Migrations are a convenient way to alter your database schema over time in a consistent way. They use a Ruby DSL so that you don’t have to write SQL by hand, allowing your schema and changes to be database independent.

You can think of each migration as being a new ‘version’ of the database. A schema starts off with nothing in it, and each migration modifies it to add or remove tables, columns, or entries. Active Record knows how to update your schema along this timeline, bringing it from whatever point it is in the history to the latest version. Active Record will also update your db/schema.rb file to match the up-to-date structure of your database.”

To expand on that a bit: AR will act as a local (to the app’s directory) version control system. When used correctly - the conventions for Ruby code take precedence in building out the database. Following the naming conventions for tables, classes, and their relations will save huge amounts of time compared to hard-coding SQL inside our files. AR, when used properly, allows many of our building database steps to feel more automated. This meta-programming is where we save our time in building an app.

meta is mo’ betta

Connecting the database

With AR convention is key. To give AR the required details w/ regard to connecting a database we use the following dir/file:

config/database.yml

For testing purposes SQLite will handle the job of ensuring proper function for an app in the early development phase. The database.yml code will look like this

default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
<<: *default
database: db/development.sqlite3
test:
<<: *default
database: db/test.sqlite3
production:
<<: *default
database: db/production.sqlite3

Common Rake tasks come with the sinatra-activerecord gem. You can view which of the tasks are available in the console by typing:

rake -T
when you forget about rake you won’t go far

These tasks are available through

require 'sinatra/activerecord/rake

The config/environment.rb file also fulfills the Rakefile requirements. Code will resemble something like:

# config/environment.rb #environment variable included used by sinatra-activerecord #connected to development-level database
ENV["RACK_ENV"] ||= "development"

require 'bundler/setup'
Bundler.require(:default, ENV["RACK_ENV"])

Now the program has access to the gems from Gemfile. We’re able see what Rake tasks can help with in developing a program in sinatra-activerecord.

Using Rake Tasks to Create Migrations

The following command will setup a table with the assigned name:

$ bundle exec rake db:create_migration NAME=create_developers

This will create the appropriate ruby file in db/migrations. It is important to leave the file names unchanged. The time-stamp which is auto-generated helps you to track the evolution of your development and will also tell sinatra-activerecord which order to run the migrations. Opening the file you’ll find that the Rake task comes with starter code:

# db/migrate/20210716095220_create_developers.rbclass CreateDevelopers < ActiveRecord::Migration[6.1]  def change
end
end

The CreateDevelopers class is inheriting from AR’s migrations. We’ll go ahead and fill out the method in this class so that a developers table is created. Notice the table names are plural when built using ActiveRecord. The CreateDevelopers table may look something like this:

# db/migrate/20210716095220_create_developers.rb

class CreateDevelopers < ActiveRecord::Migration[6.1]
def change
create_table :developers do |t|
t.string :name
t.string :prime_tech
t.integer :age
t.string :hometown
# the id column is auto-generated for tables!
end
end
end

Notice the familiar ruby code inside create table. This block is setting up the appropriate data type with the passed in ‘t’ variable which has a corresponding column name. I was taught to consider the passed in ‘t’ as representing data-type for the column. With my limited experience I have yet to see this variable name changed to anything else.

Run Migration RUN!

Now we have a migration ready to run. In the console and the appropriate directory we execute the following command and see it’s standard output indicating a successful, error-free migration:

bundle exec rake db:migrate== 20210716095220 CreateDevelopers: migrating ====================================
-- create_table(:developers)
-> 0.0008s
== 20210716095220 CreateDevelopers: migrated (0.0009s) ===========================

Based on the configuration in database.yml we will notice that AR creates a new database file if there isn’t one present already. (NOTE: The SQL code to make all this happen is automated by AR since our environment is configured correctly and the migrations have followed the sensible conventions of plural table names and singular class name.) So we’ll get a very handy db/schema.rb file to ensure our tables/database are set up to our liking.

ActiveRecord::Schema.define(version: 2021_07_16_095220) do
create_table "developers", force: :cascade do |t|
t.string "name"
t.string "prime_tech"
t.integer "age"
t.string "hometown"
end
end

Above: the version number matches the timestamp to the migration file, and we see the table defined! Still need further confirmation this migration ran? Rake has a task for you

$ bundle exec rake db:migrate:status

database: db/development.sqlite3

Status Migration ID Migration Name
--------------------------------------------------
up 20210716095220 Create developers

Status = up so we’re good to go on testing the Database.

Using Ruby Methods to Interact with DB

Inside the app/models directory is where we find our Ruby class for the given table. It is inheriting from ActiveRecord’s base commands. Again take notice we’re using a singular class name to correspond with the plural table name. This is part of the aforementioned conventions for satisfying AR’s expectations of us as developers. Failure to comply with these conventions will cause AR to not work and no doubt confuse your teammates.

# app/models/developer.rb
class Developer < ActiveRecord::Base
end

Simple methods can be called on this class from within your Rake console by running

$ bundle exec rake console

Ensure the class exists by typing in “Developer” into the console which will return the following:

# => Developer (call 'Developer.connection' to establish a connection)

Another quick example of a method to use: Developer.column_names will return the Developers table’s column names.

From inside the rake console we can create a new Developer on the fly:

mind-boggling powers
thanos = Developer.new(name: 'Thanos')thanos.hometown = Titanthanos.save

Return values from these commands will verify the information you’re entering and return ‘nil’ for the unassigned attributes. We want to make sure the variable we create in the console will also find it’s home in the DB so we call .save on the newly created developer. You can simultaneously instantiate and save to database with the .create method and save some time typing.

A wide cross-section of CRUD methods are available to use in the database. Be sure to check ’em out.

Migrations Reformat Existing Tables

With a migration that’s already up and running we can not add new code. Rerunning a migration won’t work either. None the less we may be required to expand the developers table.

A new migration will do the trick! We’re gonna set that up very similar to the previous create_developers migration format

#inside terminal
$ rake db:create_migration NAME=add_favorite_hobby_to_developers
#inside the file created by new migration
class AddFavoriteHobbyToDevelopers < ActiveRecord::Migration[6.1]
def change
add_column :developers, :favorite_hobby, :string
end
end
Photo by Patrick Fore on Unsplash

Reminder: AR will execute your migrations in alpha-numerical order. This is where having the timestamps ensures expected outcome from our development flow. Use the same rake command as earlier to run the migration. Personally I follow this up with a good look at the schema to ensure the additions were successful.

ActiveRecord::Schema.define(version: 2021_07_16_101748) do
create_table "developers", force: :cascade do |t|
t.string "name"
t.string "prime_tech"
t.integer "age"
t.string "hometown"
t.string "favorite_hobby"
end
end

Practicing these steps will allow your developer-mind to reinforce what’s happening inside Ruby, Active-Record, and Sinatra. Having this under-the-hood-level understanding makes us better at debugging our own code as well as others’ code. As a student I have found that these more involved approaches to full-stack development help create clearer understanding when moving onto more popular technologies. In this case I’m referring to what comes next in my program: Ruby on Rails.

--

--

Jason Kuffler

Creative. Researcher. Coder. Rad Dad. Sharing easy-to-follow progress covering my tech ed