DEV Community

SCDan0624
SCDan0624

Posted on

Beginners guide to Active Record

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"


Enter fullscreen mode Exit fullscreen mode

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'


Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# lb/app/models/shoe_store.rb

class ShoeStore < ActiveRecord::Base

end
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode
#02_create_brands.rb
class CreateBrands < ActiveRecord::Migration[5.2]
    def change
        create_table :brands do |t|
            t.string name
        end
    end
end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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) =====================================
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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) =====================================
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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) ==========================================
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
class Brand < ActiveRecord::Base
    has_many :shoe_stores
    has_many :shoes, through: :shoe_stores
end
Enter fullscreen mode Exit fullscreen mode
class ShoeStore < ActiveRecord::Base
    belongs_to :shoe
    belongs_to :brand
end
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Now we can ask footlocker what shoes they have by typing
footlocker.shoe

=> #<Shoe:0x00007fc845098160 id: nil, name: "Jordanxii", size: nil, price: nil>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
betalantz profile image
Lantz Warrick

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 titled CreateCats. 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