Model Classes Creating a Model Class and Seeding the Database

In this demonstration, I will show how to create a model for managing database records and how to seed the database with records.

General Steps

In general, the steps for creating a model are as follows.

Creating a Car Model

To demonstrate the steps for creating a model, we will be creating a car model for managing data regarding automobile sales. The model class will be structured as shown in Figure 1.

A UML class diagram depicting the Car model class

Figure 1. The Car model class.

We will start with the main base app, which does not have any models initially, and we will add the car model to the app.

Base App Code

Step 1 Generate a Model Class and Database Migration

To generate the model class and database migration, we will use the rails generate model command, like this:

rails generate model Car make:string model:string color:string year:integer price:decimal

The Car argument specifies that the name of the model class. The subsequent arguments specify the data attributes that each Car object has. Three attributes, make, model, and color, are of type string. One attribute, year, is of type integer, and one attribute, price, is of type decimal (i.e., a floating point number). In addition to these attributes, Rails will also automatically create a unique numeric id attribute and two timestamp attributes, one that stores when a record was first created and one that stores when a record was last modified.

To save a few keystrokes, rails generate model … can be optionally shortened to rails g model ….

Running the above command should produce the following output:

Running via Spring preloader in process 45975
      invoke  active_record
      create    db/migrate/20201230175111_create_cars.rb
      create    app/models/car.rb
      invoke    test_unit
      create      test/models/car_test.rb
      create      test/fixtures/cars.yml

We can see from the output that the command generated a model class in the file app/models/car.rb and a database migration in the file db/migrate/20201230175111_create_cars.rb. The migration filename is prefixed with a timestamp (2020123017255) of when the file was generated. The reason for this timestamp is that a migration can modify the database schema in a variety of ways, including, for example, adding, modifying, and removing tables from the schema. It is therefore important that the migrations be applied in a particular order. Rails uses the timestamps in the migration filenames to determine the order in which they should be run (i.e., from oldest to newest).

Test It!

To confirm that we made this change correctly, we manually inspect the model class file and the database migration file.

The model class file (app/models/car.rb) should look like this:

class Car < ApplicationRecord
end

Note that there is no mention of the class attributes (color, make, etc.). That is because Rails automatically determines the attributes based on the database schema (which will be configured by the migration in the next step).

The database migration file (db/migrate/20201230175111_create_cars.rb) should look like this:

class CreateCars < ActiveRecord::Migration[6.0]
  def change
    create_table :cars do |t|
      t.string :make
      t.string :model
      t.string :color
      t.integer :year
      t.decimal :price

      t.timestamps
    end
  end
end

Without digging into the syntactic details of this code, note that the migration will create a database table named cars (the model class name Car translated into snake_case and pluralized), and the table will have a column for each of the attributes specified in the rails generate model command above. The t.timestamps bit specifies that the table will also have columns for recording timestamps for when a given table row was first created and when it was last modified. The create_table call also implicitly creates an id column by default.

If we discover an error in either of the above files, we can undo the previous rails generate model command by running rails destroy model; however, we will need to specify the name of the model exactly as we did before (i.e., possibly using an incorrect name). One way to accomplish this is to click the up-arrow key to get back to our previous rails generate model command. Then, we edit the command to replace generate with destroy, and run the command.

For example, imagine that we accidentally ran this incorrect rails generate model command:

rails generate model OopsWrongName color:string make:string price:decimal year:integer

We can effectively undo the command by running this command:

rails destroy model OopsWrongName

Step 1 Changeset

Step 2 Run the Database Migration

To apply the generated database migration, thus configuring the database, we execute the migration by running this rails command:

rails db:migrate:reset

This command actually has the effect of fully resetting and reconfiguring the database.

The output of the command should look like this:

Dropped database 'rails_demos_n_deets_2021_app_development'
Dropped database 'rails_demos_n_deets_2021_app_test'
Created database 'rails_demos_n_deets_2021_app_development'
Created database 'rails_demos_n_deets_2021_app_test'
== 20201230175111 CreateCars: migrating =======================================
-- create_table(:cars)
   -> 0.0075s
== 20201230175111 CreateCars: migrated (0.0076s) ==============================

Annotated (4): app/models/car.rb, test/models/car_test.rb, test/fixtures/cars.yml, test/fixtures/cars.yml

Test It!

To confirm that we made this change correctly, we check a couple things.

First, we manually inspect the model class file (app/models/car.rb), which should now look like this:

# == Schema Information
#
# Table name: cars
#
#  id         :bigint           not null, primary key
#  color      :string
#  make       :string
#  model      :string
#  price      :decimal(, )
#  year       :integer
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class Car < ApplicationRecord
end

Note that running the migration caused comments that describe the class attributes to be added to the file. These comments were created by the annotate gem that was added to the base app when it was initially set up.

To check that the model class is functioning properly, we test it using the Rails console. The console is a command-line tool that allows us to run Ruby/Rails code interactively.

To start the console, we run this command:

rails console

As a first test of our Car model class, we check whether it can create and save new car records in the database by entering a call to the create class method, like this:

Car.create(make: 'Tesla', model: 'Model X', color: 'Black', year: 2020, price: 77641.00)

The console output should look like this:

   (0.2ms)  BEGIN
  Car Create (0.9ms)  INSERT INTO "cars" ("make", "model", "color", "year", "price", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["make", "Tesla"], ["model", "Model X"], ["color", "Black"], ["year", 2020], ["price", "77641.0"], ["created_at", "2020-12-30 20:52:56.166374"], ["updated_at", "2020-12-30 20:52:56.166374"]]
   (0.6ms)  COMMIT
 => #<Car id: 1, make: "Tesla", model: "Model X", color: "Black", year: 2020, price: 0.77641e5, created_at: "2020-12-30 20:52:56", updated_at: "2020-12-30 20:52:56">

Note that the output shows that a SQL INSERT command to save the car record to the cars database table ran successfully and that a Car object was returned (indicated by the #<Car … > bit at the end).

As an additional test of our Car model class, we enter a call to the all class method see if it can retrieve all the car records from the database, like this:

Car.all

The console output should look like this:

  Car Load (0.4ms)  SELECT "cars".* FROM "cars" LIMIT $1  [["LIMIT", 11]]
 => #<ActiveRecord::Relation [#<Car id: 1, make: "Tesla", model: "Model X", color: "Black", year: 2020, price: 0.77641e5, created_at: "2020-12-30 20:52:56", updated_at: "2020-12-30 20:52:56">]>

Note that the output shows that a SQL SELECT command to retrieve the rows from the cars database table ran successfully and that an array containing the Car object we previously saved was returned (indicated by the [#<Car … >] bit at the end).

Satisfied that our Car model class is functioning as expected, we quit the Rails console by entering this command:

exit

Step 2 Changeset

Step 3 Seed the Database with Records

To seed the database with some initial car records, we first add code to the seeds script (db/seeds.rb) for creating and saving model objects to the database, and then, we run the seeds script using the rails command.

Substep Create model objects in the seeds script. To add three car records to the database, we add three calls to the Car model class method create! in the db/seeds.rb file, like this:

Car.create!(
  make: 'Dodge',
  model: 'Viper',
  color: 'Blue',
  year: 1994,
  price: 30700.70
)

Car.create!(
  make: 'Chevy',
  model: 'Equinox',
  color: 'Gold',
  year: 2015,
  price: 8327.99
)

Car.create!(
  make: 'Tesla',
  model: 'Model X',
  color: 'Black',
  year: 2020,
  price: 77641.00
)

Note that we use the create! (with an !) form of the method, because it will throw an exception if the record cannot be saved to the database. A common mistake is to use the create method (with no !), which will fail silently, potentially causing confusion over why some seed records are missing from the database.

Substep Run the seeds script. To run the seeds script, and thus, add the seed data to the database, we run the rails command, like this:

rails db:migrate:reset db:seed

Technically this command resets and reconfigures the database in addition to seeding it.

The output of the command should look like this:

Dropped database 'rails_demos_n_deets_2021_app_dev_development'
Dropped database 'rails_demos_n_deets_2021_app_dev_test'
Created database 'rails_demos_n_deets_2021_app_dev_development'
Created database 'rails_demos_n_deets_2021_app_dev_test'
== 20201230204543 CreateCars: migrating =======================================
-- create_table(:cars)
   -> 0.0061s
== 20201230204543 CreateCars: migrated (0.0062s) ==============================

Model files unchanged.

Test It!

To confirm that we made this change correctly, we again use the Rails console, this time to check that the seed data was indeed saved to the database.

As before, to start the console, we run the rails command, like this:

rails console

To confirm that the seed data was saved to the database, we call the Car model class method all, like this:

pp Car.all

The pp method “pretty prints” the object passed as its argument, making it easier to inspect the object. In this case, the argument passed to pp is the array of Car objects retrieved from the database.

The console output should look like this:

  Car Load (0.7ms)  SELECT "cars".* FROM "cars"
[#<Car:0x00007fef8e2178b8
  id: 1,
  make: "Dodge",
  model: "Viper",
  color: "Blue",
  year: 1994,
  price: 0.307007e5,
  created_at: Wed, 30 Dec 2020 21:04:14 UTC +00:00,
  updated_at: Wed, 30 Dec 2020 21:04:14 UTC +00:00>,
 #<Car:0x00007fef8e1c54f0
  id: 2,
  make: "Chevy",
  model: "Equinox",
  color: "Gold",
  year: 2015,
  price: 0.832799e4,
  created_at: Wed, 30 Dec 2020 21:04:14 UTC +00:00,
  updated_at: Wed, 30 Dec 2020 21:04:14 UTC +00:00>,
 #<Car:0x00007fef8e1c5400
  id: 3,
  make: "Tesla",
  model: "Model X",
  color: "Black",
  year: 2020,
  price: 0.77641e5,
  created_at: Wed, 30 Dec 2020 21:04:14 UTC +00:00,
  updated_at: Wed, 30 Dec 2020 21:04:14 UTC +00:00>]
 => #<ActiveRecord::Relation [#<Car id: 1, make: "Dodge", model: "Viper", color: "Blue", year: 1994, price: 0.307007e5, created_at: "2020-12-30 21:04:14", updated_at: "2020-12-30 21:04:14">, #<Car id: 2, make: "Chevy", model: "Equinox", color: "Gold", year: 2015, price: 0.832799e4, created_at: "2020-12-30 21:04:14", updated_at: "2020-12-30 21:04:14">, #<Car id: 3, make: "Tesla", model: "Model X", color: "Black", year: 2020, price: 0.77641e5, created_at: "2020-12-30 21:04:14", updated_at: "2020-12-30 21:04:14">]>

Note that, as we expected, the three cars created in the seeds script were retrieved by the call to Car.all.

To wrap up, we quit the Rails console by entering this command:

exit

Step 3 Changeset

Conclusion

Following the above steps, we have now generated a model class for representing car-sale listings and for saving and managing car-sale listings in a database, and we have created a seeds script for populating the database with some sample car-sale listings.

Demo App Code