Intro
As a beginning programmer, normally when creating a domain model you code out your model like this:
class Cat
attr_accessor :owner
attr_reader :name
@@all = []
def initialize(name, owner)
@name = name
@owner = owner
@@all << self
end
def self.all
@@all
end
end
What if I told you, you could create the same class with the following lines of code:
class Cat < ActiveRecord::Base
belongs_to :owner
end
class CreateCats < ActiveRecord::Migration[5.2]
def change
create_table :haunted_houses do |t|
t.string :name
end
end
end
Not only is the above code is easier to write/read, but it also will allow you to easily create associations between classes in the future.
In this article I will give a step by step guide on how to use Active Record in Ruby to build out a simple domain model and associate classes in that model.
For this article we will use the example creating a shoe store.
Step 1 Adding the Active Record Gem
For the following example you will need the Active Record gem installed in your gemfile. sqlite3 and pry are also necessary for this lab. Here is an example of a gemfile with active record.
source "https://rubygems.org"
gem "activerecord", "~> 6.0.0", :require => 'active_record'
gem "sinatra-activerecord"
gem "sqlite3", '~>1.4'
gem "rake"
gem "database_cleaner"
gem "pry"
gem "require_all"
Run bundle install to install your gems
Step 2 Connect to DB
After we add Active Record, we need to tell ActiveRecord where the database is located that it will be working with.
We do this by running ActiveRecord::Base.establish_connection. Once establish_connection is run, ActiveRecord::Base keeps it stored as a class variable at ActiveRecord::Base.connection.
#config/environment.rb
require 'bundler'
Bundler.require
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'db/development.db')
require_all 'lib'
require_all 'app'
Step 3 Create model classes
For our shoe store example we will have 3 models: Shoe, Shoe_Store, and Brand.
First let's set up our shoe model class in our lb/models folder:
# lb/app/models/shoe.rb
class Shoe < ActiveRecord::Base
end
By adding < ActiveRecord::Base to our shoe class, our class is now a gateway for talking to the shoes table in the database.
Next we do the same for our brand and shoe store class.
# lb/app/models/brand.rb
class Brand < ActiveRecord::Base
end
# lb/app/models/shoe_store.rb
class ShoeStore < ActiveRecord::Base
end
Step 4 Create Our Migrations folders/files
In your project create the following folders: db/migrate.
Inside db/migrate create the following files:
01_create_shoes.rb
02_create_brands.rb
03_create_shoe_stores.rb
Step 5 Create Tables
Open your newly created 01_create_shoes.rb file and 02_create_brands.rb file and add the following code:
#01_create_shoes.rb
class CreateShoes < ActiveRecord::Migration[5.2]
def change
create_table :shoes do |t|
t.string :name
t.integer :size
t.float :price
end
end
end
#02_create_brands.rb
class CreateBrands < ActiveRecord::Migration[5.2]
def change
create_table :brands do |t|
t.string name
end
end
end
With the following code we are able to create a table in the shoe class and brand class with columns representing the attributes of those classes.
Next add the following code to our 03_create_shoe_stores.rb file:
class CreateShoeStores < ActiveRecord::Migration[5.2]
def change
create_table :shoe_stores do |t|
t.integer :shoe_id
t.integer :brand_id
t.string :name
t.string :location
t.time :open_time
t.time :close_time
end
end
end
Now in the terminal type rake db:migrate and you should get the following:
== 1 CreateShoes: migrating ===================================================
-- create_table(:shoes)
-> 0.0007s
== 1 CreateShoes: migrated (0.0007s) ==========================================
== 2 CreateBrands: migrating ==================================================
-- create_table(:brands)
-> 0.0015s
== 2 CreateBrands: migrated (0.0016s) =========================================
== 3 CreateShoeStores: migrating ==============================================
-- create_table(:shoe_stores)
-> 0.0010s
== 3 CreateShoeStores: migrated (0.0010s) =====================================
Now if you take a look at your db/schema.rb page, you will see that all your tables with the proper columns have been created:
ActiveRecord::Schema.define(version: 3) do
create_table "brands", force: :cascade do |t|
t.string "name"
end
create_table "shoes", force: :cascade do |t|
t.string "name"
t.integer "size"
t.float "price"
end
create_table "shoe_stores", force: :cascade do |t|
t.integer "shoe_id"
t.integer "brand_id"
t.string "name"
t.string "location"
t.time "open_time"
t.time "close_time"
end
end
Step 5 Rollback mistakes
What if you realized you want to rename a few of the tables in your CreateShoeStore file? What do you do? This is where rake:db:rollback is handy. Type in rake db:rollback in your terminal:
== 3 CreateShoeStores: reverting ==============================================
-- drop_table(:shoe_stores)
-> 0.0012s
== 3 CreateShoeStores: reverted (0.0040s) =====================================
Now you can make any changes to your CreateShoeStores tables. Lets change your open_time and close_time to opening_time and closing_time:
class CreateShoeStores < ActiveRecord::Migration[5.2]
def change
create_table :shoe_stores do |t|
t.integer :shoe_id
t.integer :brand_id
t.string :name
t.string :location
t.time :opening_time
t.time :closing_time
end
end
end
Now type in rake db:migrate into your console and check your schema:
ActiveRecord::Schema.define(version: 3) do
create_table "brands", force: :cascade do |t|
t.string "name"
end
create_table "shoes", force: :cascade do |t|
t.string "name"
t.integer "size"
t.float "price"
end
create_table "shoe_stores", force: :cascade do |t|
t.integer "shoe_id"
t.integer "brand_id"
t.string "name"
t.string "location"
t.time "opening_time" #<--- changed
t.time "closing_time" #<--- changed
end
end
As you can see our changes have been implemented.
Before we start our next step lets rollback our created tables. Type rake db:rollback STEP=3 in your console:
= 3 CreateShoeStores: reverting ==============================================
-- drop_table(:shoe_stores)
-> 0.0008s
== 3 CreateShoeStores: reverted (0.0169s) =====================================
== 2 CreateBrands: reverting ==================================================
-- drop_table(:brands)
-> 0.0005s
== 2 CreateBrands: reverted (0.0005s) =========================================
== 1 CreateShoes: reverting ===================================================
-- drop_table(:shoes)
-> 0.0004s
== 1 CreateShoes: reverted (0.0005s) ==========================================
by adding the STEP = 3 we were able to rollback all three tables with one line of code.
Step 6 Create Associations between classes.
Not only does Active Record make it create and change tables, you can also create associations between classes with only a few lines of code. Add the following to your three model pages:
class Shoe < ActiveRecord::Base
has_many :shoe_stores
has_many :brands, through: :shoe_stores
end
class Brand < ActiveRecord::Base
has_many :shoe_stores
has_many :shoes, through: :shoe_stores
end
class ShoeStore < ActiveRecord::Base
belongs_to :shoe
belongs_to :brand
end
By using the has_many or belongs_to keywords we are able to easily associate our classes.
Step 7 See our classes in action
Type rake console to get into pry where we can make some new instances.
Let's start by making a new shoe store
footlocker = ShoeStore.new(name:"Footlocker")
=> #<ShoeStore:0x00007fc841f8ce90
id: nil,
shoe_id: nil,
brand_id: nil,
name: "Footlocker",
location: nil,
opening_time: nil,
closing_time: nil>
Just like that we created a new instance of ShoeShore with the name Footlocker.
Now lets make a new shoe
Jordanxii = Shoe.new(name:"Jordanxii")
=> #<Shoe:0x00007fc845098160 id: nil, name: "Jordanxii", size: nil, price: nil>
Now we have a shoe and a shoe store, lets make a association by typing the following
footlocker.shoe = Jordanxii.
=> #<Shoe:0x00007fc845098160 id: nil, name: "Jordanxii", size: nil, price: nil>
Now we can ask footlocker what shoes they have by typing
footlocker.shoe
=> #<Shoe:0x00007fc845098160 id: nil, name: "Jordanxii", size: nil, price: nil>
As you can see we easily made an association using Active Record. Now just type the following to save it
Jordanxii.save
If you get the following code below you have just saved your Shoe instance.
D, [2020-02-12T10:33:00.787066 #27721] DEBUG -- : (0.1ms) begin transaction
D, [2020-02-12T10:33:00.799889 #27721] DEBUG -- : Shoe Create (1.2ms) INSERT INTO "shoes" ("name") VALUES (?) [["name", "Jordanxii"]]
D, [2020-02-12T10:33:00.801589 #27721] DEBUG -- : (1.4ms) commit transaction
=> true
Conclusion
As you can see with just a just a few lines of code you can create and build models with associations between them thanks to Active Record.
Top comments (1)
This is a good introduction to migrations and schema, though it's a little strange to be creating a table
:haunted_houses
in a migration titledCreateCats
. When you get to associations, things get confusing. Because you've made ShoeStore the join model between a Shoe and a Brand, it's a child of both a Shoe instance and a Brand instance, i.e. a particular store cannot exist independently of a particular shoe and brand. This relationship bears no logical relationship to the real-world domain it is modeling and is thus confusing. A more logical relationship using these models might look like: ShoeStore -< InventoryItem >- Shoe >- Brand