Active Record Basics
On this page, we will learn new a feature from Ruby on Rails. The Active Record which is responsible for the relationship between the model and the database.
Table of contents
What is Active Record?
Ruby on Rails is based on MVC
(model-view-controller) framework, we will discuss this on the later topics.
The Active Record represents the M
in the MVC
which is the model. Active Record represents business data and logic and also responsible dealings with the database on how to manage your data.
On this page we will look at how Active Record works.
What is Migration?
Migration is like version control for databases. We use this to monitor our current database schema. Writing a migration allows us to describe and transform the database schema according to our needs.
In the next paragraphs we will generate a migration file. You need to know how to do it because migration files are the common files we generate here in Ruby on Rails.
Naming Conventions
Ruby on Rails has a lot of magic, by following the correct naming conventions saves us from writing more code.
Let’s look at how Rails naming conventions for models and tables. Active Record uses naming conventions to map the relationships of models and database tables.
Ruby on Rails maps the models to tables by pluralizing the class names. To find which table it should use.
It makes sense when you think about databases that hold all the records of your models and makes pluralized work.
Models use singular since it’s only defined or describes a single row in a database.
For example:
We have a model Post
since rails pluralize the class names of models and then look for a table that corresponds with it, which is the posts
table.
# posts -> Rails recommends using pluralize nouns for table names.
# app/models/post.rb -> Ruby recommends using `snake_case` for file names
# Post -> # Ruby recommends using `PascalCase` for class names.
class Post < ApplicationRecord
end
Reminder:
If your model’s corresponding database table does not fit this convention, you may manually specify the model’s table name by using the
table_name
on the model.
class Post < ApplicationRecord
self.table_name = 'blog_lists'
end
Active Record Basics
In this part, we will practice some basic features of Active Record.
- Open up your terminal and run
docker-compose up -d
to serve your project containers.
Now that your containers are up and running.
- Let’s open a terminal in your container.
- Run:
docker-compose exec app bash
- Let’s generate a migration file.
- Run:
rails generate migration create_posts
$~/KodaCamp> docker-compose up -d
Creating kodacamp_project_redis_1 ... done
Creating kodacamp_project_db_1 ... done
Creating kodacamp_project_app_1 ... done
$~/KodaCamp> docker-compose exec app bash
root@0122:/usr/src/app# rails generate migration create_posts
invoke active_record
create db/migrate/xxxxxxxxxxxxxx_create_posts.rb
All generated migration files are stored in db/migrate
directory.
We use create_posts
as the name for our migration file and Ruby on Rails is smart enough to know that we want to create posts
table.
The migration files are prefixed with numbers. These are the timestamps offered by Ruby on Rails to help you organize your migration files. It avoids naming conflicts, manages the history of your tables, and makes monitoring new migrations simple.
class CreatePosts < ActiveRecord::Migration[7.0]
def change
create_table :posts do |t|
t.timestamps
end
end
end
When we generate a migration file, the t.timestamps
field is already there. This line adds two new columns, created_at
and updated_at
, Active Record will handle the contents of these fields.
Let’s add a new column called title. This column only takes a small number of characters. We will use
string
as our data type.Let’s also add one more column called content. We know that content could contain a lot of paragraphs, which means we need a data type that can hold it like
text
.
You can read more about migration data types in this link.
# db/migrate/xxxxxxxxxxxxxx_create_posts.rb
class CreatePosts < ActiveRecord::Migration[7.0]
def change
create_table :posts do |t|
+ t.string :title
+ t.text :content
t.timestamps
end
end
end
Read carefully:
rails db:migrate
This will run all the migration files that are not migrated yet. After you run this command do not modify your last migration file.
Reminder:
If you want to do some changes from your last migration you can run
rails db:rollback
but when the migration file you want to change is already behind, please generate a new one for that.
- Now let’s run
rails db:migrate
root@0122:/usr/src/app# rails db:migrate
== xxxxxxxxxxxxxx CreatePosts: migrating ======================================
-- create_table(:posts)
-> 0.0130s
== xxxxxxxxxxxxxx CreatePosts: migrated (0.0133s) =============================
You see it generates a new table called posts
and a new file db/schema.rb
is added in your project.
Read carefully :
Schema only updates when you run a migration command, it only shows the current version of your database. Do not write or update anything to your schema manually because it does not reflect on your database. Generate a new migration file when you want to add or remove columns to your database.
ActiveRecord::Schema[7.0].define(version: xxxx_xx_xx_xxxxxx) do
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Now that we have a posts
table. Next, we need to add a model for our table.
- In the
app/models
, create a new filepost.rb
# app/models/post.rb
class Post < ApplicationRecord
end
With these few lines of code, we can now use the Active Record.
- Let’s back to your terminal and run
rails c
orrails console
Reminder:
The console command lets you interact with your Rails application from the command line. You can use
exit
to close the irb console.
root@0122:/usr/src/app# rails c
Loading development environment (Rails 7.0.4)
irb(main):001:0>
CREATING NEW RECORDS
Read the following methods and try to understand how to use them.
create
This method accepts keyword arguments for your columns and their values.
irb(main):001:0> Post.create(title: 'My first post', content: 'My first post blog')
TRANSACTION (0.7ms) BEGIN
Post Create (1.6ms) INSERT INTO `posts` (`title`, `content`, `created_at`, `updated_at`) VALUES ('My first post', 'My first post blog', 'xx-xx-xx xx:xx:xx.xxxxxx', 'xx-xx-xx xx:xx:xx.xxxxxx')
TRANSACTION (3.1ms) COMMIT
=> #<Post:0x000055ec95bd8698 id: 2, title: "My first post", content: "My first post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>
save
Using this method, we instantiate a record first before sending it to the database.
post = Post.new # Instantiate a model, This is like we create an empty row.
post.title = 'My second post' # Add the values of these columns.
post.content = 'My second post blog' # At this point, the record is still not saved in the database.
post.save # After we call the `save` method, that is the time when the record saves in a database.
irb(main):001:0> post = Post.new
=> #<Post:0x000055ec95fc22e0 id: nil, title: nil, content: nil, created_at: nil, updated_at: nil>
irb(main):002:0> post.title = 'My second post'
=> "My second post"
irb(main):003:0> post.content = 'My second post blog'
=> "My second post blog"
irb(main):004:0> post.save
TRANSACTION (0.5ms) BEGIN
Post Create (0.6ms) INSERT INTO `posts` (`title`, `content`, `created_at`, `updated_at`) VALUES ('My second post', 'My second post blog', 'xx-xx-xx xx:xx:xx.xxxxxx', 'xx-xx-xx xx:xx:xx.xxxxxx')
TRANSACTION (1.8ms) COMMIT
=> true
- Let’s add more records before we proceed.
Post.create(title: 'My third post', content: 'My third post blog')
Post.create(title: 'My fourth post', content: 'My fourth post blog')
Post.create(title: 'My fifth post', content: 'My fifth post blog')
Post.create(title: 'My sixth post', content: 'My sixth post blog')
READING RECORDS FROM A DATABASE
We have a lot of methods for fetching records from the database. Read the following methods and try to understand how to use them.
all
This method fetches all the records from the database and returns an array of model instance.
irb(main):001:0> Post.all
Post Load (0.7ms) SELECT `posts`.* FROM `posts`
=>
[#<Post:0x000055ec95bea000 id: 1, title: "My first post", content: "My first post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x000055ec95be9f38 id: 2, title: "My second post", content: "My second post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x000055ec95be9e70 id: 3, title: "My third post", content: "My third post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x000055ec95be9da8 id: 4, title: "My fourth post", content: "My fourth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x000055ec95be9ce0 id: 5, title: "My fifth post", content: "My fifth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x000055ec95be9c18 id: 6, title: "My sixth post", content: "My sixth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>]
first
This method fetches the first record from the database and only returns a single instance of the model object.
irb(main):001:0> Post.first
Post Load (0.8ms) SELECT `posts`.* FROM `posts` ORDER BY `posts`.`id` ASC LIMIT 1
=> #<Post:0x000055ec9553ff70 id: 1, title: "my first post", content: "My first post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>
Reminder:
In Default, Ruby on Rails use ascending order of primary id when fetching the records.
find
This method accepts an integer as an argument and returns the first record with matching id.
irb(main):001:0> Post.find(1)
Post Load (0.5ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1
=> #<Post:0x00007f4d0c054870 id: 1, title: "my first post", content: "My first post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>
find_by
This method accepts keyword arguments of columns and their values and returns the first matching record from your arguments.
irb(main):001:0> Post.find_by(title: 'my first post')
Post Load (0.6ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`title` = 'my first post' LIMIT 1
=> #<Post:0x00005564bacfdc20 id: 1, title: "my first post", content: "My first post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>
where
This method accepts keyword arguments with columns and their values as arguments and returns a collection of matching records from your arguments.
irb(main):001:0> Post.where(title: 'My sixth post')
Post Load (0.6ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`title` = 'My sixth post'
=> [#<Post:0x00007f3634458fc8 id: 6, title: "My sixth post", content: "My sixth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>]
irb(main):004:0>
In this textbook, we will often encounter these methods. We’ve previously seen that the find
and find_by
methods return a single instance, while the where
function returns an array of instances.
What happens if their records do not match any other records?.
The
find
method raises anActiveRecord::RecordNotFound
exception.The find_by method returns a nil value.
The
where
method returns an empty array.
Think about where or when to use these methods because they look similar but not really
order
This method accepts keyword arguments with columns and values of :asc
or :desc
returns a collection in the order you set.
Post.order(created_at: :desc)
We can use other columns according to your needs and change the direction with the use of :asc
for ascending and :desc
for descending.
irb(main):001:0> Post.order(created_at: :desc)
Post Load (0.7ms) SELECT `posts`.* FROM `posts` ORDER BY `posts`.`created_at` DESC
=>
[#<Post:0x00005564bbf5e568 id: 6, title: "My sixth post", content: "My sixth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x00005564bbf5e4a0 id: 5, title: "My fifth post", content: "My fifth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x00005564bbf5e3d8 id: 4, title: "My fourth post", content: "My fourth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x00005564bbf5e310 id: 3, title: "My third post", content: "My third post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x00005564bbf5e248 id: 2, title: "My second post", content: "My second post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x00005564bbf5e180 id: 1, title: "My first post", content: "My first post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>]
Reminder:
You need to know the difference between the methods that returns an array of model objects and single model object.
Methods that return an array of model instance:
- all
- where
- order
- limit
- take
Methods that return a single model instance:
- find
- find_by
- first
- last
UPDATE EXISTING RECORDS
We need to find the record or records that we want to change.
update
- This method only needs the keyword arguments for your columns and their values.
irb(main):001:0> post = Post.first
irb(main):002:0> post.update(content: 'The first post is updated')
Post Load (1.8ms) SELECT `posts`.* FROM `posts` ORDER BY `posts`.`id` ASC LIMIT 1
TRANSACTION (0.3ms) BEGIN
Post Update (0.6ms) UPDATE `posts` SET `posts`.`content` = 'The first post is updated', `posts`.`updated_at` = 'xx-xx-xx xx:xx:xx.xxxxxx' WHERE `posts`.`id` = 1
TRANSACTION (1.4ms) COMMIT
=> true
save
- This method is also applicable for updating a record. When the instantiated model is in the database, the record is updated instead of adding a new one.
irb(main):001:0> post = Post.second
Post Load (1.2ms) SELECT `posts`.* FROM `posts` ORDER BY `posts`.`id` ASC LIMIT 1 OFFSET 1
=> #<Post:0x00005564bc2bce08 id: 2, title: "test", content: "My first post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>
irb(main):002:0> post.content = 'The second post is updated'
irb(main):003:0> post.save
TRANSACTION (0.4ms) BEGIN
Post Update (0.5ms) UPDATE `posts` SET `posts`.`content` = 'The second post is updated', `posts`.`updated_at` = 'xx-xx-xx xx:xx:xx.xxxxxx' WHERE `posts`.`id` = 2
TRANSACTION (1.0ms) COMMIT
=> true
updating multiple records
From the first two examples we only update single records. Now we will try to update multiple records.
The
update
method can also be used to an array of records.
irb(main):001:0> Post.where(id: [3, 4]).update(content: 'Update multiple contents')
Post Load (0.4ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2)
TRANSACTION (0.2ms) BEGIN
Post Update (2.0ms) UPDATE `posts` SET `posts`.`content` = 'Update multiple contents', `posts`.`updated_at` = 'xx-xx-xx xx:xx:xx.xxxxxx' WHERE `posts`.`id` = 3
TRANSACTION (1.1ms) COMMIT
TRANSACTION (0.3ms) BEGIN
Post Update (0.3ms) UPDATE `posts` SET `posts`.`content` = 'Update multiple contents', `posts`.`updated_at` = 'xx-xx-xx xx:xx:xx.xxxxxx' WHERE `posts`.`id` = 4
TRANSACTION (1.4ms) COMMIT
=>
[#<Post:0x000055960d6d20d8 id: 3, title: "My third post", content: "Update multiple contents", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x000055960d6d1f20 id: 4, title: "My fourth post", content: "Update multiple contents", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>]
- You can also use
update_all
method for updating an array of records.
irb(main):001:0> Post.where(id: [3, 4]).update_all(content: 'Update multiple contents with update_all')
Post Update All (0.6ms) UPDATE `posts` SET `posts`.`content` = 'Update multiple contents with update_all' WHERE `posts`.`id` IN (3, 4)
=> 2
- This method returns the number of updated rows.
When we are using
update
method to update multiple records, Ruby on Rails instantiate the records and sends it to the database. Theupdate_all
use theSQL update
statement and sends the update one time.
DELETING UNWANTED RECORDS
We need to find the record or records that we want to remove.
destroy
After you instantiate the model, you can remove a specific record using this method.
irb(main):001:0> post = Post.last
Post Load (0.8ms) SELECT `posts`.* FROM `posts` ORDER BY `posts`.`id` DESC LIMIT 1
=> #<Post:0x00007f363463db40 id: 6, title: "My sixth post", content: "My sixth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>
irb(main):002:0> post.destroy
TRANSACTION (0.4ms) BEGIN
Post Destroy (0.5ms) DELETE FROM `posts` WHERE `posts`.`id` = 6
TRANSACTION (1.5ms) COMMIT
=> #<Post:0x00007f363463db40 id: 6, title: "My sixth post", content: "My sixth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>
deleting multiple records
The destroy
method does not work on an array of instance, unlike the update
.
We will be using the destroy_all
method to remove an array of instances from our database.
irb(main):001:0> posts = Post.where(id: [3, 4])
irb(main):002:0> posts.destroy_all
TRANSACTION (0.4ms) BEGIN
Post Destroy (0.5ms) DELETE FROM `posts` WHERE `posts`.`id` = 3
TRANSACTION (1.2ms) COMMIT
TRANSACTION (0.4ms) BEGIN
Post Destroy (0.4ms) DELETE FROM `posts` WHERE `posts`.`id` = 4
TRANSACTION (1.4ms) COMMIT
=>
[#<Post:0x00007f36345e8168 id: 3, title: "My third post", content: "My third post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>,
#<Post:0x00007f36345e80a0 id: 4, title: "My fourth post", content: "My fourth post blog", created_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx, updated_at: xxx, xx xxx xxxx xx:xx:xx.xxxxxxxxx UTC +xx:xx>]
Read carefully:
The
destroy_all
andupdate_all
method does not run yourvalidations
orcallbacks
.
That’s all for the Active Record basics.