Creating A User

Creating a user with an email and a password using the Rails console

A Quick Recap

In the previous article we setup devise and created the User model.

You are now ready for the next step: creating a user. Feel free to refer to the part 4 branch of the GitHub repository if needed.

This is the 4th installment of my Developing A Cross-Platform iOS & Android Social Media App series.

Overview

We are now going to be creating User records using the Rails console. This will help us understand how to create the auth endpoints in the next part.

The User Model

Before we create our User records, we need to view the User model. Take a look at the app/models/user.rb file.

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

There isn't much to look at other than we have a model called User and that it has some attributes that devise manages for us automatically. Feel free to remove the comments in this file, we won't need them.

Rails will automatically know that the User model refers to the users table. We will learn more about how to edit these model files in the future.

Creating A User

We will first create a User through the Rails console and then turn it into an endpoint. Open the console using the $ rails console or $ rails c command.

Type in User and press enter to see if Rails recognizes our User model.

2.6.5 :001 > User
 => User (call 'User.connection' to establish a connection)

Now that we verified that we can access the User model, lets create a new User record. We have two common methods: create and new. User.create() will instantiate a new User model, validate it, establish a database connection, then add the record to the database. User.new() will just instantiate it, then allow us to run User.valid? to validate it, and finally User.save to save it.

For our endpoints, we will be using the User.new() method instead of User.create() because it will make it easier to track errors along the way. It also allows us to do some logic before the save such as uploading a profile image.

In the Rails console, run user = User.new(email: '[email protected]') and then user.valid?.

user = User.new(email: '[email protected]')
 => #<User id: nil, email: "[email protected]", created_at: nil, updated_at: nil>
2.6.5 :030 > user.valid?
  User Exists? (1.1ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
 => false

Note: Rails creates SQL queries for us, so instead of a long query, we can just run user.valid? to check if a user is valid. Running these commands return the SQL command that is ran which in this case is:

SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "[email protected]"], ["LIMIT", 1]]

You wouldn't need to memorize this but it is always a good habit to familiarize yourself with these queries. There are times where I use plain SQL queries instead of using Rails since it is far more simpler (A good example would be filtering/searching through columns).

Notice how user.valid? returns false. But why is the model invalid? Rails has a method for models that lets us see why a instantiated model is invalid. Run user.errors:

2.6.5 :037 > user.errors
 => #<ActiveModel::Errors:0x00007fa0b21c9418 @base=#<User id: nil, email: "[email protected]", created_at: nil, updated_at: nil>, @messages={:password=>["can't be blank"]}, @details={:password=>[{:error=>:blank}]}>

We don't need all of this extra information, let's run user.errors.messages

2.6.5 :038 > user.errors.messages
 => {:password=>["can't be blank"]}

The error is pretty self explanatory, to prevent it, we would have needed to ran:

user = User.new(email: '[email protected]', password: '123456')

Devise also handles password confirmations for us automatically. All we would have to do is add a password_confirmation attribute:

user = User.new(email: '[email protected]', password: '123456', password_confirmation: '123456')

But since our variable user is already created, lets practice updating an existing model. Rails provides us with a User.update() method. Lets update user with a password and run valid?:

2.6.5 :050 > user.update(password: '123456', password_confirmation: '123456')
   (0.4ms)  BEGIN
  User Exists? (0.4ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "a@abc,com"], ["LIMIT", 1]]
  User Create (35.5ms)  INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["email", "a@abc,com"], ["encrypted_password", "$2a$11$H4TW0KwHFYPy3QKRVn58zuruh4OBZnaGn2YuVimbVoZDbFgbMSEzC"], ["created_at", "2020-02-08 09:12:13.225899"], ["updated_at", "2020-02-08 09:12:13.225899"]]
   (0.9ms)  COMMIT
 => true
2.6.5 :051 > user.valid?
 => true

Looks like everything is ok! But wait, .update and .new both return if the user is valid or not as it returns true or false when those methods are ran. Why not just use user instead of user.valid??

The problem is that those methods really return the instantiated User model. So if you run if user or if User.new(email: '[email protected]'), it will count as true. Here is an example of where it does not work:

2.6.5 :060 > temp_user = User.new(email: "[email protected]")
 => #<User id: nil, email: "[email protected]", created_at: nil, updated_at: nil>
2.6.5 :061 > if temp_user
2.6.5 :062?>   "not valid but returns true"
2.6.5 :063?>   end
 => "not valid but returns true"

So now that we verified that our user is valid and why User.valid? is important, lets save it! Run user.save:

2.6.5 :064 > user.save
 => true

Success! We have now created our first user!

Finding the User

For this next section, we will cover some ways we can find a user. There are some query methods we can use such as all, where, find, find_by. Here are some brief explanations of each:

all: This returns all records for that query. So User.all() will return all users. If none exists, it returns an empty ActiveRecord relation.

where: This takes in parameters to filter a relation. So User.where(name:'John Doe') will return a relation of all users with the name John Doe. If none exists, it returns an empty ActiveRecord relation.

Note: You can also use a string such as User.where("name = 'John Doe'") but you would have to be careful not to be vulnerable to a SQL injection attack if you are dealing with user input. We will address this later but it is something to keep in mind.

find: This takes in the model's id (User.find(123))as a parameter and retuns the record if it exists, otherwise it throws an ActiveRecord::RecordNotFound exception.

find_by: This is similar to find except that it takes in any column as a param (User.find_by(email: '[email protected]')) and returns the relation if it exists, otherwise it returns nil. You can force an ActiveRecord::RecordNotFound exception instead of nil by using find_by!() instead.

We will cover more query methods later in the series.

Lets try these methods out! Open up the rails console via $ rails c and try some of these methods.

Getting all user records using User.all():

2.6.5 :005 > User.all()
  User Load (0.5ms)  SELECT "users".* FROM "users" LIMIT $1  [["LIMIT", 11]]
 => #<ActiveRecord::Relation [#<User id: "7545d290-962f-45bb-891d-f7e43b4fbf68", email: "[email protected]", created_at: "2020-02-08 19:58:24", updated_at: "2020-02-08 19:58:24">]>

Getting all user records with the email [email protected] using User.where():

2.6.5 :006 > User.where(email: '[email protected]')
  User Load (0.8ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 11]]
 => #<ActiveRecord::Relation [#<User id: "7545d290-962f-45bb-891d-f7e43b4fbf68", email: "[email protected]", created_at: "2020-02-08 19:58:24", updated_at: "2020-02-08 19:58:24">]>

Getting the user record with the uuid 7545d290-962f-45bb-891d-f7e43b4fbf68 using User.find():

2.6.5 :008 > User.find('7545d290-962f-45bb-891d-f7e43b4fbf68')
  User Load (5.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", "7545d290-962f-45bb-891d-f7e43b4fbf68"], ["LIMIT", 1]]
 => #<User id: "7545d290-962f-45bb-891d-f7e43b4fbf68", email: "[email protected]", created_at: "2020-02-08 19:58:24", updated_at: "2020-02-08 19:58:24">

Getting the user record with the email [email protected] using User.find_by():

2.6.5 :009 > User.find_by( email: '[email protected]')
  User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
 => #<User id: "7545d290-962f-45bb-891d-f7e43b4fbf68", email: "[email protected]", created_at: "2020-02-08 19:58:24", updated_at: "2020-02-08 19:58:24">

Testing a blank ActiveRecord relation using User.find_by():

2.6.5 :010 > User.find_by(email: '[email protected]')
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
 => nil

Testing if ActiveRecord::RecordNotFound exception is raised using User.find_by!():

2.6.5 :011 > User.find_by!(email: '[email protected]')
  User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):11
ActiveRecord::RecordNotFound (Couldn't find User)

Validating A User's Password

One last thing: we need to validate a user's password. Devise provides us with a valid_password? method for our User model. Try it out with a valid an invalid password. (In our example above we used [email protected] for the emails and 123456 for the password)`)

2.6.5 :001 > user = User.find_by(email: '[email protected]')
2.6.5 :002 > user.valid_password?('123123')
 => true
2.6.5 :003 > user.valid_password?('1231234')
 => false

What About Tests?

You may have noticed that the Rails automatically created a User test file for us. This is so that we can run our tests to automatically check if the app is working as intended without regressions. Normally, you would write tests now or before merging a feature into production. For this guide, I will have a seperate part to cover tests as a whole, so don't worry!

What's Next?

These methods will help us find records in the database, not only for Users, but for future models as well. We will implement the register and login endpoints in the next part.