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.
-
Step 1 Generate a Model Class and Database Migration. This step uses Rails to generate code for a model, including a model class that can be used (e.g., by a controller) to manage records of the model and a database migration that is used to configure the database so the model class will work correctly.
-
Step 2 Run the Database Migration. This step executes the generated database migration to prepare the database so that the model class can use it to manage records of the model.
-
Step 3 Seed the Database with Records (if Desired). This (optional) step seeds the database with some initial records of the model.
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.
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.
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 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 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
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.