Now that you know how to get up and running with Ruby (and Rails) from the article 4.5 Ways to Install Ruby in Userspace, that means you are ready to start dealing with Rails and its features.
ActiveRecord is a gem that comes with Rails and is responsible for storing, updating, deleting, validating, and querying the database.
Out of the box Rails is already prepared to use it, so if you start a project like:
% rails new my-blog
It will be completely prepared to use ActiveRecord. Generate the project with the command line above and let’s get started.
Generating the models
There are many ways to generate ActiveRecord models and here’s one. Create a model called Post using the rails generate command:
% rails generate model Post title body:text published_at:datetime
This command will create:
- One model in app/models/post.rb;
One migration in db/migrate/xxxxxxxxxxxxxx_create_posts.rb (xxxxxxxxxxxxxx is the timestamp I will talk about it later);
Some tests within the tests directory;
In Rails 5 the model is like:
class Post < ApplicationRecord
end
I’m using Rails 5 for this post, but if you are using Rails 4:
class Post < ActiveRecord::Base
end
Playing with the model
ActiveRecord::Base is responsible for adding some methods to your model. I will show some of them:
1.The first one is table_name:
% rails c
Running via Spring preloader in process 5751
Loading development environment (Rails 5.0.0.1)
>> Post.table_name
=> "posts"
Look at the file db/migrate/xxxxxxxxxxxxxx_create_posts.rb:
class CreatePosts < ActiveRecord::Migration[5.0]
def change
create_table :posts do |t|
t.string :title
t.text :body
t.datetime :published_at
t.timestamps
end
end
end
And look at the model again:
class Post < ApplicationRecord
end
You will notice that there is not any, ANY configuration in the model about what table it uses, it is all about convention over configuration. You will see a lot of things like that.
2. new_record?, persisted?, and save.
Exit the previous rails console with exit and run:
% rake db:migrate
== 20160916004714 CreatePosts: migrating ===========================
-- create_table(:posts)
-> 0.0064s
== 20160916004714 CreatePosts: migrated (0.0066s) ==================
And in rails console again:
% rails console
Running via Spring preloader in process 31021
Loading development environment (Rails 5.0.0.1)
>> post = Post.new(title: 'Testing', body: 'The body here', published_at: Time.new(2015, 12, 31, 1, 2))
=> #<Post id: nil, title: "Testing", body: "The body here", published_at: "2015-12-31 04:02:00", created_at: nil, updated_at: nil>
>> post.new_record?
=> true
>> post.persisted?
=> false
>> post.save
(0.2ms) begin transaction
SQL (0.6ms) INSERT INTO "posts" ("title", "body", "published_at", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["title", "Testing"], ["body", "The body here"], ["published_at", 2015-12-31 04:02:00 UTC], ["created_at", 2016-09-16 01:02:48 UTC], ["updated_at", 2016-09-16 01:02:48 UTC]]
(128.8ms) commit transaction
=> true
>> post.new_record?
=> false
>> post.persisted?
=> true
>> post.id
=> 1
When an object is not stored in the database, new_record? returns true and when the object is stored in the database persisted? returns true.
And I can update the model too!
% rails c
Running via Spring preloader in process 2565
Loading development environment (Rails 5.0.0.1)
>> p = Post.find(1)
Post Load (0.4ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> #<Post id: 1, title: "Testing", body: "The body here", published_at: "2015-12-31 04:02:00", created_at: "2016-09-16 01:02:48", updated_at: "2016-09-16 01:02:48">
>> p.published_at = '2015–11–30 02:03'
=> "2015–11–30 02:03"
>> p.save
(0.2ms) begin transaction
SQL (0.6ms) UPDATE "posts" SET "published_at" = ?, "updated_at" = ? WHERE "posts"."id" = ? [["published_at", nil], ["updated_at", 2016-09-16 01:06:48 UTC], ["id", 1]]
(159.7ms) commit transaction
=> true
>> Post.find(1).published_at
Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> nil
I recommend that you play a lot with rails console because for me it is one of the most powerful tools I use to try some code snippets, to debug, or to check anything I need to.
As an exercise try to learn how to use update in rails console by reading update (ActiveRecord::Persistence) — APIdock.
Validation
Sometimes you want your user to always fill some attributes, in this section we will see how to require them.
Create a model only with the body:
% rails console
Running via Spring preloader in process 4609
Loading development environment (Rails 5.0.0.1)
>> Post.create(body: 'This is the body')
(0.2ms) begin transaction
SQL (0.9ms) INSERT INTO "posts" ("body", "created_at", "updated_at") VALUES (?, ?, ?) [["body", "This is the body"], ["created_at", 2016-09-16 01:08:25 UTC], ["updated_at", 2016-09-16 01:08:25 UTC]]
(141.7ms) commit transaction
=> #<Post id: 2, title: nil, body: "This is the body", published_at: nil, created_at: "2016-09-16 01:08:25", updated_at: "2016-09-16 01:08:25">
And now update your model so it will look like:
class Post < ApplicationRecord
validates :title, presence: true
end
In order to verify what behavior this line yields in the model open the rails console.
Before it:
% rails c
Running via Spring preloader in process 5606
Loading development environment (Rails 5.0.0.1)
>> post2 = Post.find(2)
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
=> #<Post id: 2, title: nil, body: "This is the body", published_at: nil, created_at: "2016-09-16 01:08:25", updated_at: "2016-09-16 01:08:25">
>> post2.valid?
=> true
And reloading our console (#protip here :):
>> reload!
Reloading…
=> true
>> post2 = Post.find(2)
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
=> #<Post id: 2, title: nil, body: "This is the body", published_at: nil, created_at: "2016-09-16 01:08:25", updated_at: "2016-09-16 01:08:25">
>> post2.valid?
=> false
I recommend that you read Active Record Validations — Ruby on Rails Guides to learn more about the validations.
Relations
Create another model called Author:
% rails generate model Author name
Running via Spring preloader in process 5165
invoke active_record
create db/migrate/20160919233111_create_authors.rb
create app/models/author.rb
invoke test_unit
create test/models/author_test.rb
create test/fixtures/authors.yml
If you wonder what is that number in the migration (like 20160919233111), you can see IT as “the order to run the migration”.
To figure it out we can recreate our database:
% rake db:drop && rake db:create
Dropped database 'db/development.sqlite3'
Database 'db/test.sqlite3' does not exist
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
And, if you run the migrations again you will notice that they follow the timestamp sequence in which they originally got created:
% rake db:migrate
== 20160916010011 CreatePosts: migrating ===========================
-- create_table(:posts)
-> 0.0037s
== 20160916010011 CreatePosts: migrated (0.0039s) ==================
== 20160919233111 CreateAuthors: migrating =========================
-- create_table(:authors)
-> 0.0015s
== 20160919233111 CreateAuthors: migrated (0.0016s) ================
Ok, you got the idea about migrations, now it is time to start associating these two models.
The first thing to do is to create another migration to connect them:
% rails generate migration add_author_id_to_posts
Running via Spring preloader in process 12220
invoke active_record
create db/migrate/20160919233814_add_author_id_to_posts.rb
Of course you can choose another name for the migration (add_author_id_to_posts).
The content should be like:
class AddAuthorIdToPosts < ActiveRecord::Migration[5.0]
def change
add_column :posts, :author_id, :integer, index: true
add_foreign_key :posts, :authors
end
end
Running the migration:
% rake db:migrate
== 20160919233814 AddAuthorIdToPosts: migrating ====================
-- add_column(:posts, :author_id, :integer, {:index=>true})
-> 0.0153s
-- add_foreign_key(:posts, :authors)
-> 0.0001s
== 20160919233814 AddAuthorIdToPosts: migrated (0.0157s) ===========
Ok, we have the migration, now add the relation to the model:
In app/models/post.rb:
class Post < ApplicationRecord
validates :title, presence: true
belongs_to :author, required: false
end
In app/models/author.rb:
class Author < ApplicationRecord
has_many :posts, dependent: :restrict
end
In console:
% rails c
Running via Spring preloader in process 24090
Loading development environment (Rails 5.0.0.1)
>> Author.create(name: 'John Doe')
(0.2ms) begin transaction
SQL (0.7ms) INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "John Doe"], ["created_at", 2016-09-19 23:53:49 UTC], ["updated_at", 2016-09-19 23:53:49 UTC]]
(158.4ms) commit transaction
=> #<Author id: 1, name: "John Doe", created_at: "2016-09-19 23:53:49", updated_at: "2016-09-19 23:53:49">
There are a lots of ways to use console to add a relation between two models, but I will use the simplest one:
% rails c
Running via Spring preloader in process 30780
Loading development environment (Rails 5.0.0.1)
>> post = Post.first
Post Load (0.3ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<Post id: 1, title: "Testing", body: "The body here", published_at: "2015-12-31 04:02:00", created_at: "2016-09-19 23:53:28", updated_at: "2016-09-19 23:53:28", author_id: nil>
>> post.author = Author.find(1)
Author Load (0.3ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> #<Author id: 1, name: "John Doe", created_at: "2016-09-19 23:53:49", updated_at: "2016-09-19 23:53:49">
>> post.save
(0.2ms) begin transaction
SQL (1.8ms) UPDATE "posts" SET "updated_at" = ?, "author_id" = ? WHERE "posts"."id" = ? [["updated_at", 2016-09-19 23:56:40 UTC], ["author_id", 1], ["id", 1]]
(224.5ms) commit transaction
=> true
>> Post.find(1).author.name
Post Load (0.6ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Author Load (0.2ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> "John Doe"
To get more information
Whenever I have questions I go to the Ruby on Rails Guides. These two help me out a lot:
Well, this post got bigger than I expected, but I’m very excited to show you a bunch of other features you may like. Let me know in the comments below all the questions you have ;).
We want to work with you. Check out our "What We Do" section!