Practical 8
0. Finish Practical Sheet 7.
1. User Accounts. Allowing users to log in and out requires
another table containing the user names and associated passwords. The
situation is complicated by the fact that passwords must not be stored
literally. What’s stored is a
digest hash
value of a user’s password,
in such a way that the original password cannot be computed from the
stored data
(in case the database gets stolen or otherwise compromised). Rails
provides a data type digest
for this purpose. Generate the scaffold
for a User
model as usual:
User |
|
---|---|
name |
string |
password |
digest |
What is the full command?
2. Find the migration that has been generated as part of the
User
scaffold and check its contents. Does it look different from
what you would have expected?
Then migrate the database (which command?).
3. Model. In the User
class, add validation that ensures for
each user that the name
is present and unique, i.e., each user must
have a name and no two users can have the same name.
What is the ruby
code for this?
Why can’t you test presence: true
on the password
field?
Note the command has_secure_password
in the User
model. It has
been added as part of the scaffolding for the password
component of
type digest
. It’s purpose is to manage a password confirmation
field on the user
form.
4. For the purpose of encrypting the passwords we use the
bcrypt
gem. Uncomment the corresponding line in the Gemfile
.
Then run
bundle install
and restart your server.
5. Controller. Some modifications are necessary in the users controller (which file?).
First, in the index
method, replace
@users = User.all
by @users = User.order(:name)
, to have
user names listed alphabetically.
6. Then, in the create
and in the update
methods, instead of
@user
, redirect to users_url
after successfully creating or
updating the user, and insert #{@user.name}
into the :notice
strings. Note that these strings now need to be delimited by double
quotes, rather than single quotes, to enable string interpolation, a
ruby feature that will replace #{@user.name}
automatically by the
user’s name.
7. Views. There is no notice yet, in the users index view. To display the notice about newly created or updated users, add the lines
to the top of the index
view.
8. Modify the form partial (which file?) that is used to enter new users and to update existing users so that it looks as follows:
Note how <legend>
and <fieldset>
tags have been added to improve
the appearance of the form, and that the entire form is enclosed in a
<div>
tag with the class .depot_form
that is defined in the style
sheet.
9. Navigate to http://localhost:3000/users/new
and create an
account for yourself, and another one for a friend. The users
index will
just show a list of names. To see that password related information
has arrived in the database, enter the following bit of SQL on the
command line:
sqlite3 -line db/development.sqlite3 "select * from users"
Listing the password digests next to user name presumably would not look nice, nor be informative.
10. Test. We need to update the tests to reflect the changes to
validations and redirections. The test "should create user"
should
read like this:
In a similar way, modify the assert_redirected_to
line of the
"should update user"
test.
11. In the users.yml
fixtures, change the names of the two users
to conor
and emma
(in that order).
Note how some values in the fixtures are now computed dynamically in
the form of embedded ruby. This is yet another effect of scaffolding a
password
component of type digest
.
11a. If all works fine, commit the changes to your local git
repository, and push them to the github
cloud.
12. Authentication. In order to support logins of store administrators, the site needs
-
a login page, i.e., a form that allows users to enter their names and passwords,
-
a place to record that a user is logged in,
-
a way to restrict access to the administrative part of the store.
Let’s start by generating some actions in two controllers:
rails generate controller sessions new create destroy
rails generate controller admin index
13.
The create
method in the sessions controller, should record the user id
in the session
hash and redirect
to the admin home page, if the correct password is provided,.
Otherwise, it reloads the login page.
Modify the method to look as follows:
(Here, admin_url
and login_url
are symbolic names for routes
that still have to be defined.)
14.
For the login page, we use a form that (unlike the forms we have used before)
is not directly linked to a model object – it would not make sense here, would it?
This is the sessions/new.html.erb
view:
Note how the form uses the helper methods form_tag
and friends.
Also note how it directly accesses and uses the params
hash
in order to communicate field values between the form and the server.
15.
Logging out is easy: remove the user id from the session
hash
and send the browser back to the catalog page. Use the following two lines
as body of the session controller’s destroy
method:
16.
Prepare the admin/index
view to greet a freshly logged in administrator
with useful information:
17.
For this to work, the instance variable @total_orders
has to be provided by the admin
controller. Add the line
to its index
method.
18.
What’s still missing is routes connecting to the symbolic names like admin_url
and login_url
.
Rails has automatically set up
routes for
admin/index
,
sessions/new
,
sessions/create
and
sessions/destroy
in the routes.rb
config file.
Delete these four, and rename them to the more descriptive
admin
,
login
and
logout
, by inserting
into the file instead.
Now you can use the login feature to prevent non admin visitors of the web site from accessing admin pages. Try and log in as an admin.
19. The functional tests in the admin
and sessions
controllers
need to be updated in order to match the recent changes.
In the admin
controller, change the get
request to
Modify the sessions
controller test file to look like:
19a. Run the tests. If all works well commit and push to github
.
20. Restricting Access. Rails uses filters at various points in
the life cycle of an action. One of these filters is before_action
,
called before the processing of the action has even started. This would
be the right place to check if the current user is logged in as an
admin. Define a function
in the (protected
part of the) application controller,
and as first line in the definition of the ApplicationController
class,
add the command
in order to use the new authorize
function as before_action
for every action in this application.
21. This is not exactly what you want, people should still be
allowed to shop around and order without administrative privileges.
So you need to undo the effect of before_action
on those actions
that should be generally accessible.
Authentication should be disabled for these actions.
As first line in the classes
StoreController
and SessionsController
, add the command
Access to the catalog and to the login page(!) should be unrestricted.
22. Other controllers need a more fine-tuned access control. Buyers are allowed to create, update and delete carts (but not to see a particular cart, or a list of carts). This is expressed by
as first line in the carts
controller class definition.
23. They need to be able to create line items:
(in the line_items
controller) and create orders through the new order form:
(in the orders
controller).
24. These changes make most tests fail, as now most actions
redirect to the login page. This can be rectified in the test/test_helper.rb
file by adding the methods:
Now all the tests should pass again.
24a. If all works fine, commit the changes to your local git
repository, and push them to the github
cloud.
25.
It would be convenient, to have an admin menu to the side bar
(in the application.html.erb
layout file), in such a way that it
only appears when a user is logged in:
For the styling of the class "logged_user_nav"
, add the following to the
end of the application.scss
stylefile:
Now surf through your web site and experience the difference between the buyer’s and the seller’s view …
25a. If all works fine, commit the changes to your local git
repository and push them to the github
cloud.