Let’s start by creating a model and database table to hold our administrators’ usernames and passwords. Rather than store passwords in plain text, we’ll store a digest hash value of the password. By doing so, we ensure that even if our database is compromised, the hash won’t reveal the original password, so it can’t be used to log in as this user using the forms:
| depot> bin/rails generate scaffold User name:string password:digest |
We declare the password as a digest type, which is another one of the nice extra touches that Rails provides. Now run the migration as usual:
| depot> bin/rails db:migrate |
Next we have to flesh out the user model:
| class User < ApplicationRecord |
» | validates :name, presence: true, uniqueness: true |
| has_secure_password |
| end |
We check that the name is present and unique (that is, no two users can have the same name in the database).
Then there’s the mysterious has_secure_password.
You know those forms that prompt you to enter a password and then make you reenter it in a separate field so they can validate that you typed what you thought you typed? That’s exactly what has_secure_password does for you: it tells Rails to validate that the two passwords match. This line was added for you because you specified password:digest when you generated your scaffold.
The next step is to uncomment the bcrypt-ruby gem in your Gemfile:
| # Use Active Model has_secure_password |
» | gem 'bcrypt', '~> 3.1.7' |
Next, you need to install the gem:
| depot> bundle install |
Finally, you need to restart your server.
With this code in place, we have the ability to present both a password and a password confirmation field in a form, as well as the ability to authenticate a user, given a name and a password.
In addition to the model and table we set up, we already have some scaffolding generated to administer the model. Let’s go through it and make some tweaks as necessary.
We start with the controller. It defines the standard methods: index, show, new, edit, create, update, and delete. By default, Rails omits the unintelligible password hash from the view. This means that in the case of users, there isn’t much to show, except a name. So, let’s avoid the redirect to showing the user after a create operation. Instead, let’s redirect to the user’s index and add the username to the flash notice:
| def create |
| @user = User.new(user_params) |
| |
| respond_to do |format| |
| if @user.save |
» | format.html { redirect_to users_url, |
» | notice: "User #{@user.name} was successfully created." } |
| format.json { render :show, status: :created, location: @user } |
| else |
| format.html { render :new } |
| format.json { render json: @user.errors, |
| status: :unprocessable_entity } |
| end |
| end |
| end |
Let’s do the same for an update operation:
| def update |
| respond_to do |format| |
| if @user.update(user_params) |
» | format.html { redirect_to users_url, |
» | notice: "User #{@user.name} was successfully updated." } |
| format.json { render :show, status: :ok, location: @user } |
| else |
| format.html { render :edit } |
| format.json { render json: @user.errors, |
| status: :unprocessable_entity } |
| end |
| end |
| end |
While we are here, let’s also order the users returned in the index by name:
| def index |
» | @users = User.order(:name) |
| end |
Now that the controller changes are done, let’s attend to the view. We need to update the form used both to create a new user and to update an existing user. Note this form is already set up to show the password and password confirmation fields. We’ll make a few aesthetic changes so the form looks nice and matches the look and feel of the site.
» | <div class="depot_form"> |
» | |
| <%= form_with(model: user, local: true) do |form| %> |
| <% if user.errors.any? %> |
| <div id="error_explanation"> |
| <h2><%= pluralize(user.errors.count, "error") %> |
| prohibited this user from being saved:</h2> |
| |
| <ul> |
| <% user.errors.full_messages.each do |message| %> |
| <li><%= message %></li> |
| <% end %> |
| </ul> |
| </div> |
| <% end %> |
| |
» | <h2>Enter User Details</h2> |
» | |
| <div class="field"> |
» | <%= form.label :name, 'Name:' %> |
» | <%= form.text_field :name, size: 40 %> |
| </div> |
| |
| <div class="field"> |
» | <%= form.label :password, 'Password:' %> |
» | <%= form.password_field :password, size: 40 %> |
| </div> |
| |
| <div class="field"> |
» | <%= form.label :password_confirmation, 'Confirm:' %> |
| |
» | <%= form.password_field :password_confirmation, |
» | id: :user_password_confirmation, |
» | size: 40 %> |
| |
| </div> |
| |
| <div class="actions"> |
| <%= form.submit %> |
| </div> |
| <% end %> |
» | |
» | </div> |
Let’s try it. Navigate to http://localhost:3000/users/new. For a stunning example of page design, see the following screenshot.
After Create User is clicked, the index is redisplayed with a cheery flash notice. If we look in our database, you’ll see that we’ve stored the user details:
| depot> sqlite3 -line db/development.sqlite3 "select * from users" |
| id = 1 |
| name = dave |
| password_digest = $2a$10$lki6/oAcOW4AWg4A0e0T8uxtri2Zx5g9taBXrd4mDSDVl3rQRWRNi |
| created_at = 2016-01-29 14:40:06.230622 |
| updated_at = 2016-01-29 14:40:06.230622 |
As we’ve done before, we need to update our tests to reflect the validation and redirection changes we’ve made. First we update the test for the create method:
| test "should create user" do |
| assert_difference('User.count') do |
» | post users_url, params: { user: { name: 'sam', |
» | password: 'secret', password_confirmation: 'secret' } } |
| end |
| |
» | assert_redirected_to users_url |
| end |
Because the redirect on the update method changed too, the update test also needs to change:
| test "should update user" do |
| patch user_url(@user), params: { user: { name: @user.name, |
| password: 'secret', password_confirmation: 'secret' } } |
» | assert_redirected_to users_url |
| end |
We need to update the test fixtures to ensure there are no duplicate names:
| # Read about fixtures at |
| # https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html |
| |
| one: |
» | name: dave |
| password_digest: <%= BCrypt::Password.create('secret') %> |
| |
| two: |
» | name: susannah |
| password_digest: <%= BCrypt::Password.create('secret') %> |
Note the use of dynamically computed values in the fixture, specifically for the value of password_digest. This code was also inserted by the scaffolding command and uses the same function that Rails uses to compute the password.[78]
At this point, we can administer our users; we need to first authenticate users and then restrict administrative functions so they’ll be accessible only by administrators.
13.59.130.130