Ch 10: Updating, Showing, Deleting Users Flashcards

You may prefer our related Brainscape-certified flashcards:
1
Q

Create new branch for updating users

A

​git checkout -b updating-users /

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Retrieving appropriate user

A

app/controllers/users_controller.rbclass UsersController < ApplicationController..def edit @user = User.find(params[:id]) end

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Create edit view which is very similar to new view

A

Create edit view which is very similar to new view

<% provide(:title, 'Edit User') %><%= render :layout => 'shared/card_container', :locals => {:title => 'Sign Up'} do %> <%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Save Changes", class: "btn btn-primary" %> <% end %> ^div class="gravatar_edit"> <%= gravatar_for @user %> ^a href="http://gravatar.com/emails" target="_blank">change^/a> ^/div><% end %>

  • The “Name” and “Email” fields in Figure 10.2 also shows how Rails automatically pre-fills the Name and Email fields using the attributes of the existing @user variable.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

target=”_blank”

A

target=”_blank”

  • neat trick to get the browser to open the page in a new window or tab, which is sometimes convenient behavior when linking to third-party sites.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

hidden input field

A

hidden input field

^input name=""_method"" type=""hidden"" value=""patch"" />

  • Examining the HTML of the page shows the use of hidden values
  • Since web browsers can’t natively send PATCH requests as required by the REST conventions, Rails fakes it with a POST request and a hidden input field.
  • how does Rails know to use a POST request for new users and a PATCH for editing users? The answer is that it is possible to tell whether a user is new or already exists in the database via Active Record’s new_record? boolean method:”

$ rails console >> User.new.new_record? => true >> User.first.new_record? => false

* When constructing a form using form\_for(@user), Rails uses POST if @user.new\_record? is true and PATCH if it is false.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

settings link

A

settings link

<%= link_to "Settings", edit_user_path(current_user) %>

  • This is easy using the named route edit_user_path from Table 7.1, together with the handy current_user helper method
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

factoring forms

A

factoring forms

  • to consolidate the similar form for new and edit user you just create a _form.html.erb file for the user then pass the variable to the appropriate path to submit for each form
  • creating _form file”

app/views/users/_form.html.erb <%= form_for(@user) do |f| %> <%= render 'shared/error_messages', object: @user %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit yield(:button_text), class: "btn btn-primary" %> <% end %> /

  • factoring new”

app/views/users/new.html.erb <% provide(:title, 'Sign Up') %> <% provide(:button_text, 'Create my account') %> <%= render :layout => 'shared/card_container', :locals => {:title => 'Sign Up'} do %> <%= render 'form' %> <% end %> /

  • factoring edit

app/views/users/edit.html.erb<% provide(:title, 'Edit User') %><% provide(:button_text, 'Save changes') %><%= render :layout => 'shared/card_container', :locals => {:title => 'Sign Up'} do %> <%= render 'form' %> ^div class="gravatar_edit"> <%= gravatar_for @user %> ^a href="http://gravatar.com/emails" target="_blank">change^/a> ^/div><% end %>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Create update action for users_controller.rb

A

Create update action for users_controller.rb

app/controllers/users_controller.rb..def update @user = User.find(params[:id]) if @user.update_attributes(user_params) # Handle a successful update. else render 'edit' end end

  • Note the use of user_params in the call to update_attributes, which uses strong parameters to prevent mass assignment vulnerability
  • update_attributes: updates the user based on the submitted params hash
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

create an integration test to catch any regressions of edit form

A

create an integration test to catch any regressions of edit form

rails generate integration_test users_edit /

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

test code

A

test code

test/integration/users_edit_test.rbrequire 'test_helper'class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:stephen) end test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } assert_template 'users/edit' endend

  • Note the use of the patch method to issue a PATCH request, which follows the same pattern as get, post, and delete.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

successful edit code

A

successful edit code

test/integration/users_edit_test.rbrequire 'test_helper'class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . .test "successful edit" do get edit_user_path(@user) assert_template 'users/edit' name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end

  • Check for a nonempty flash message and a successful redirect to the profile page
  • Verify that the user’s information correctly changed in the database.
  • The password and confirmation are blank, which is convenient for users who don’t want to update their passwords every time they update their names or email addresses.
  • @user.reload reloads the user’s values from the database and confirm that they were successfully updated.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Update the update action so it will get the tests to pass is similar to the final form of the create action

A

Update the update action so it will get the tests to pass is similar to the final form of the create action

app/controllers/users_controller.rbclass UsersController < ApplicationController . . .def update @user = User.find(params[:id]) if @user.update_attributes(user_params) flash[:success] = "Profile updated" redirect_to @user else render 'edit' end end /

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

allow_nil: true so users can input empty password on update

A

allow_nil: true so users can input empty password on update

class User < ApplicationRecord attr_accessor :remember_token before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 }, allow_nil: true . . .end

  • make an exception to the password validation if the password is empty. We can do this by passing the allow_nil: true option to validates
  • In case you’re worried that Listing 10.13 might allow new users to sign up with empty passwords, recall hat has_secure_password includes a separate presence validation that specifically catches nil passwords.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

authentication vs. authoraization

A

authentication vs. authoraization

  • authentication allows us to identify users of our site, while authorization lets us control what they can do.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

before_action

A

before_action

app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:edit, :update] . . . private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end # Before filters # Confirms a logged-in user. def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end endend

  • arrange for a particular method to be called before the given actions.
  • To require users to be logged in, we define a logged_in_user method and invoke it using before_action :logged_in_user,
  • By default, before filters apply to every action in a controller, so here we restrict the filter to act only on the :edit and :update actions by passing the appropriate only: options hash.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

fixing test to have user logged in first

A

fixing test to have user logged in first

test/integration/users_edit_test.rbrequire 'test_helper'class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "unsuccessful edit" do log_in_as(@user) get edit_user_path(@user) . . . end test "successful edit" do log_in_as(@user) get edit_user_path(@user) . . . endend

  • add log_in_as(@user) to unsuccessful edit and successful edit
  • start creating a successful edit test with log_in_as as well
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Add test for checking if the before action is doing its job

A

Add test for checking if the before action is doing its job

test/controllers/users_controller_test.rbrequire 'test_helper'class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "should redirect edit when not logged in" do get edit_user_path(@user) assert_not flash.empty? assert_redirected_to login_url end test "should redirect update when not logged in" do patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } assert_not flash.empty? assert_redirected_to login_url endend

  • Because the before filter operates on a per-action basis, we’ll put the corresponding tests in the Users controller test.
  • The plan is to hit the edit and update actions with the right kinds of requests and verify that the flash is set and that the user is redirected to the login path.
  • The second test involves using the patch method to send a PATCH request to user_path(@user). Such a request gets routed to the update action in the Users controller, as required.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Create a test that check if users are only be allowed to edit their own information.

A

Create a test that check if users are only be allowed to edit their own information.

  • Create another use in the fixture file to test if one user can edit the other

test/fixtures/users.ymlmichael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %>archer: name: Sterling Archer email: duchess@example.gov password_digest: <%= User.digest('password') %>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

Add tests for redirection when trying to edit wrong user

A

Add tests for redirection when trying to edit wrong user

test/controllers/users_controller_test.rbrequire 'test_helper'class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end . . . test "should redirect edit when logged in as wrong user" do log_in_as(@other_user) get edit_user_path(@user) assert flash.empty? assert_redirected_to root_url end test "should redirect update when logged in as wrong user" do log_in_as(@other_user) patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } assert flash.empty? assert_redirected_to root_url endend

  • Add other user to setup
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Define and add a before action for correct user in the controller

A

Define and add a before action for correct user in the controller

before_action :correct_user, only: [:edit, :update]...# Confirms the correct user. def correct_user @user = User.find(params[:id]) redirect_to(root_url) unless @user == current_user end /

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

refactor some by adding a current_user? boolean method for use in the correct_user before filter

A

refactor some by adding a current_user? boolean method for use in the correct_user before filter

  • add it to the sessions helper

app/helpers/sessions_helper.rbmodule SessionsHelper . . . # Returns true if the given user is the current user. def current_user?(user) user == current_user end # Returns the user corresponding to the remember token cookie. def current_user . . . end . . .end /

  • add your new boolean to your new correct_user method

def correct_user @user = User.find(params[:id]) redirect_to(root_url) unless current_user?(@user) end

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

friendly forwarding

A

friendly forwarding

  • sending the user to their own intended page instead of somebody else’s
  • e.g. with an unfriendly forward, if a non-logged-in user tries to visit the edit page, after logging in the user will be redirected to /users/1 instead of /users/1/edit.
  • It would be much friendlier to redirect them to their intended destination instead.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

Change successful edit test in users_edit_test to add friendly forwarding

A

Change successful edit test in users_edit_test to add friendly forwarding

test "successful edit with friendly forwarding" do get edit_user_path(@user) log_in_as(@user) assert_redirected_to edit_user_url(@user) name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end

  • the resulting test tries to visit the edit page, then logs in, and then checks that the user is redirected to the edit page instead of the default profile page.
  • also removes the test for rendering the edit template since that’s no longer the expected behavior.
  • Now that we have a failing test, we’re ready to implement friendly forwarding.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

store the location the user requested through a method in the sessions_helper then redirect to that location instead of to the default.

A

store the location the user requested through a method in the sessions_helper then redirect to that location instead of to the default.

app/helpers/sessions_helper.rbmodule SessionsHelper . . .# Stores the URL trying to be accessed. def store_location session[:forwarding_url] = request.original_url if request.get? end

  • the store location method also uses the request object (via request.original_url) to get the URL of the requested page.
  • The store_location method puts the requested URL in the session variable under the key :forwarding_url, but only for a GET request. This prevents storing the forwarding URL if a user, say, submits a form when not logged in (which is an edge case but could happen if, e.g., a user deleted the session cookies by hand before submitting the form). In such a case, the resulting redirect would issue a GET request to a URL expecting POST, PATCH, or DELETE, thereby causing an error. Including if request.get? prevents this from happening
  • To make use of store_location, we need to add it to the logged_in_user before filter in the users_controller.rb file.

` # Confirms a logged-in user. def logged_in_user unless logged_in? store_location flash[:danger] = “Please log in.” redirect_to login_url end end`

25
Q

Create method to redirect them in the sessions_helper

A

Create method to redirect them in the sessions_helper

# Redirects to stored location (or to the default). def redirect_back_or(default) redirect_to(session[:forwarding_url] || default) session.delete(:forwarding_url) end

  • To implement the forwarding itself, we use the redirect_back_or method to redirect to the requested URL if it exists, or some default URL otherwise, which we add to the Sessions controller create action to redirect after successful login. The redirect_back_or method uses the or operator ||. This evaluates to session[:forwarding_url] unless it’s nil, in which case it evaluates to the given default URL.
  • session.delete(:forwarding_url)” >
    • Note that were careful to remove the forwarding URL (via session.delete(:forwarding_url)); otherwise, subsequent login attempts would forward to the protected page until the user closed their browser.
  • Note that the session deletion occurs even though the line with the redirect appears first; redirects don’t happen until an explicit return or the end of the method, so any code appearing after the redirect is still executed.
26
Q

add redirect_back_or user to the sessions controller create action

A

add redirect_back_or user to the sessions controller create action

app/controllers/sessions_controller.rbclass SessionsController < ApplicationController . . . def create user = User.find_by(email: params[:session][:email].downcase) if user &amp;&amp; user.authenticate(params[:session][:password]) log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_back_or user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end . . .end

27
Q

first implement a security model

A

first implement a security model

  • so the user index will be restricted to logged-in users so that there’s a limit to how much unregistered users can see by default.
28
Q

add a short test to verify that the index action is redirected properly

A

add a short test to verify that the index action is redirected properly

test "should redirect index when not logged in" do get users_path assert_redirected_to login_url end /

29
Q

add index to the before action that restricts login pages

A

add index to the before action that restricts login pages

before_action :logged_in_user, only: [:index, :edit, :update] /

30
Q

add a variable that holds all the users in the index action

A

add a variable that holds all the users in the index action

app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update] . . . def index @users = User.all end . . .end /

31
Q

Create an index page to display all the users

A

Create an index page to display all the users

app/views/users/index.html.erb<% provide(:title, 'All users') %>≤h1>All users≤/h1>≤ul class="users"> <% @users.each do |user| %> ≤li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> ≤/li> <% end %>≤/ul>

  • To make the actual index page, we’ll make a view (whose file you’ll have to create) that iterates through the users and wraps each one in an li tag.
  • We do this with the eachmethod, displaying each user’s Gravatar and name, while wrapping the whole thing in a ul tag
  • add option of the gravatar_for “

app/helpers/users_helper.rb module UsersHelper # Returns the Gravatar for the given user. def gravatar_for(user, options = { size: 80 }) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) size = options[:size] gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end

  • add SCSS for style

/* Users index */.users { list-style: none; margin: 0; li { overflow: auto; padding: 10px 0; border-bottom: 1px solid $gray-lighter; }}

32
Q

add the URL to the users link in the site’s navigation header using users_path

A

add the URL to the users link in the site’s navigation header using users_path

app/views/layouts/_header.html.erb≤header class="navbar navbar-fixed-top navbar-inverse"> ≤div class="container"> <%= link_to "sample app", root_path, id: "logo" %> ≤nav> ≤ul class="nav navbar-nav navbar-right"> ≤li><%= link_to "Home", root_path %>≤/li> ≤li><%= link_to "Help", help_path %>≤/li> <% if logged_in? %> ≤li><%= link_to "Users", users_path %>≤/li> ≤li class="dropdown"> ≤a href="#" class="dropdown-toggle" data-toggle="dropdown"> Account ≤b class="caret">≤/b> ≤/a> ≤ul class="dropdown-menu"> ≤li><%= link_to "Profile", current_user %>≤/li> ≤li><%= link_to "Settings", edit_user_path(current_user) %>≤/li> ≤li class="divider">≤/li> ≤li> <%= link_to "Log out", logout_path, method: :delete %> ≤/li> ≤/ul> ≤/li> <% else %> ≤li><%= link_to "Log in", login_path %>≤/li> <% end %> ≤/ul> ≤/nav> ≤/div>≤/header>

33
Q

To start adding tons of sample users add the faker gem and bundle

A

To start adding tons of sample users add the faker gem and bundle

gem 'faker', '1.7.3'

34
Q

Next, we’ll add a Ruby program to seed the database with sample users, for which Rails uses the standard file db/seeds.rb.

A

Next, we’ll add a Ruby program to seed the database with sample users, for which Rails uses the standard file db/seeds.rb.

db/seeds.rbUser.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", hire_date: Date.today)99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" hire_date = Faker::Time.between((365*4).days.ago, Time.now, :midnight).to_date User.create!(name: name, email: email, password: password, password_confirmation: password, hire_date: hire_date)end

  • The create! method is just like the createmethod, except it raises an exception (Section 6.1.4) for an invalid user rather than returning false. This behavior makes debugging easier by avoiding silent errors.
35
Q

Reset the database and then invoke the Rake task using db:seed:

A

Reset the database and then invoke the Rake task using db:seed:

$ rails db:migrate:reset$ rails db:seed

  • some are unable to run the reset command if the Rails server is running, so you may have to stop the server first before proceeding
36
Q

add the will paginate gem and boostrap will paginate gem

A

add the will paginate gem and boostrap will paginate gem

gem 'will_paginate', '3.1.6'gem 'bootstrap-will_paginate', '1.0.0'

restart the server to be safe” />

37
Q

replace User.all in the index action with an object that knows about pagination.

A

replace User.all in the index action with an object that knows about pagination.

  • start by adding the special will_paginate method in the view before and after the loop

app/views/users/index.html.erb<% provide(:title, 'All users') %>≤h1>All users≤/h1><%= will_paginate @user,:class => 'digg_pagination mt-4' %>≤ul class="users"> <% @users.each do |user| %> ≤li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> ≤/li> <% end %>≤/ul><%= will_paginate @user,:class => 'digg_pagination my-4' %>

  • The will_paginate method is a little magical; inside a users view, it automatically looks for an @users object, and then displays pagination links to access other pages. The view doesn’t work yet, though, because currently @users contains the results of User.all (Listing 10.36), whereas will_paginate requires that we paginate the results explicitly using the paginate method:
  • Using the paginate method, we can paginate the users in the sample application by using paginate in place of all in the index action. Here the page parameter comes from params[:page], which is generated automatically by will_paginate.”

def index @users = User.paginate(page: params[:page]) end

  • paginate takes a hash argument with key :page and value equal to the page requested.
  • Here the page parameter comes from params[:page], which is generated automatically by will_paginate.
  • Because we included will_paginate both above and below the user list, the pagination links appear in both places.
38
Q

Create a ton of users for the fixtures file

A

Create a ton of users for the fixtures file

test/fixtures/users.ymlmichael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %>archer: name: Sterling Archer email: duchess@example.gov password_digest: <%= User.digest('password') %>lana: name: Lana Kane email: hands@example.gov password_digest: <%= User.digest('password') %>malory: name: Malory Archer email: boss@example.gov password_digest: <%= User.digest('password') %><% 30.times do |n| %>user_<%= n %>: name: <%= "User #{n}" %> email: <%= "user-#{n}@example.com" %> password_digest: <%= User.digest('password') %><% end %>

39
Q

create test for user index to test users and pagination

A

create test for user index to test users and pagination

rails generate integration_test users_index

40
Q

write test for user index to test users and pagination

A

write test for user index to test users and pagination

test/integration/users_index_test.rbrequire 'test_helper'class UsersIndexTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "index including pagination" do log_in_as(@user) get users_path assert_template 'users/index' assert_select 'div.pagination' User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end endend

  • write a lightweight test for users to log in, visit the index path, verify the first page of users is present, and then confirm that pagination is present on the page.
  • For these last two steps to work, we need to have enough users in the test database to invoke pagination, i.e., more than 30.
  • The test itself involves checking for a div with the required pagination class and verifying that the first page of users is present.
41
Q

refactor by replacing user the html with a render call to user variable

A

refactor by replacing user the html with a render call to user variable

app/views/users/index.html.erb<% provide(:title, 'All users') %>≤h1>All users≤/h1><%= will_paginate %>≤ul class="users"> <% @users.each do |user| %> <%= render user %> <% end %>≤/ul><%= will_paginate %>

  • Here we call render not on a string with the name of a partial, but rather on a user variable of class User. In this context, Rails automatically looks for a partial called _user.html.erb, which we must create
42
Q

Create user partial

A

Create user partial

app/views/users/_user.html.erb≤li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %>≤/li>

43
Q

Refactor more by calling render directly on the @users variable

A

Refactor more by calling render directly on the @users variable

app/views/users/index.html.erb<% provide(:title, 'All users') %>≤h1>All users≤/h1><%= will_paginate %>≤ul class="users"> <%= render @users %>≤/ul><%= will_paginate %>

  • Here Rails infers that @users is a list of User objects; moreover, when called with a collection of users, Rails automatically iterates through them and renders each one with the _user.html.erb partial (inferring the name of the partial from the name of the class).
44
Q

making admin users

A

making admin users

  • We will identify privileged administrative users with a boolean admin attribute in the User model, which will lead automatically to an admin? boolean method to test for admin status.
45
Q

add the admin attribute with a migration, indicating the boolean type on the command line:

A

add the admin attribute with a migration, indicating the boolean type on the command line:

rails generate migration add_admin_to_users admin:boolean

  • add the argument default: false to add_column in Listing 10.54, which means that users will not be administrators by default.

db/migrate/[timestamp]_add_admin_to_users.rbclass AddAdminToUsers < ActiveRecord::Migration[5.0] def change add_column :users, :admin, :boolean, default: false endend

  • Next, we migrate as usual:

rails db:migrate

46
Q

You can switch users to an admin over the console if you want using toggle!

A

You can switch users to an admin over the console if you want using toggle!

$ rails console --sandbox>> user = User.first>> user.admin?=> false>> user.toggle!(:admin)=> true>> user.admin?=> true

47
Q

add admin: true to the first user in your seed data

A

add admin: true to the first user in your seed data

db/seeds.rbUser.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true)99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password)end

  • then you need to reset the database so you can re-add everything

$ rails db:migrate:reset$ rails db:seed

Do not add administrator to your strong params because making it editable through the browser will make you vulnerable to somebody else changing the administrator” />

48
Q

Write a test for your attributes that aren’t editable (eg: administrator)

A

Write a test for your attributes that aren’t editable (eg: administrator)

test/controllers/users_controller_test.rbtest "should not allow the admin attribute to be edited via the web" do log_in_as(@other_user) assert_not @other_user.admin? patch user_path(@other_user), params: { user: { password: "password", password_confirmation: "password", admin: true } } assert_not @other_user.reload.admin? end

  • test it by adding admin to the strong params but make sure you delete it afterward so it’s not editible through the browser!

def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation, :admin) end

49
Q

adding a delete link for each user on the users index page, restricting access to administrative users. The resulting “delete” links will be displayed only if the current user is an admin

A

adding a delete link for each user on the users index page, restricting access to administrative users. The resulting “delete” links will be displayed only if the current user is an admin

app/views/users/_user.html.erb≤li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> <% if current_user.admin? &amp;&amp; !current_user?(user) %> | <%= link_to "delete", user, method: :delete, data: { confirm: "You sure?" } %> <% end %>≤/li>

  • Note the method: :delete argument, which arranges for the link to issue the necessary DELETE request.
50
Q

Add :destroy to the logged_in_user before filter because users have to be logged in to delete users.

A

Add :destroy to the logged_in_user before filter because users have to be logged in to delete users.

before_action :logged_in_user, only: [:index, :edit, :update, :destroy]

51
Q

add destroy action

A

add destroy action

def destroy User.find(params[:id]).destroy flash[:success] = "User deleted" redirect_to users_url end

  • Note that the destroy action uses method chaining to combine the find and destroy into one line:

User.find(params[:id]).destroy

52
Q

limit user deletions to admins

A

limit user deletions to admins

  • keep people from deleting users in the console by adding access control on the destroy action

app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update, :destroy] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: :destroy

  • Then add the method test for an admin user in the private section of the controller

# Confirms an admin user. def admin_user redirect_to(root_url) unless current_user.admin? end

53
Q

Make one of the fixtures an admin

A

Make one of the fixtures an admin

test/fixtures/users.ymlmichael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %> admin: true

54
Q

write test to redirect users when not loggin in

A

write test to redirect users when not loggin in

test "should redirect destroy when not logged in" do assert_no_difference 'User.count' do delete user_path(@user) end assert_redirected_to login_url end

  • Use delete to issue a DELETE request directly to the destroy action.
  • make sure that the user count doesn’t change using the assert_no_difference method
55
Q

write test so that users who are logged in but who aren’t admins should be redirected to the Home page.

A

write test so that users who are logged in but who aren’t admins should be redirected to the Home page.

test "should redirect destroy when logged in as a non-admin" do log_in_as(@other_user) assert_no_difference 'User.count' do delete user_path(@user) end assert_redirected_to root_url end

56
Q

add successful delete for admins on the users index test since that’s where it happens

A

add successful delete for admins on the users index test since that’s where it happens

assert_difference 'User.count', -1 do delete user_path(@other_user)end

  • The only really tricky part is verifying that a user gets deleted when an admin clicks on a delete link
  • This uses the assert_difference method first seen in Listing 7.33 when creating a user, this time verifying that a user is destroyed by checking that User.count changes by −1−1when issuing a delete request to the corresponding user path.
57
Q

add a test for when non-admins try to delete from the index page

A

add a test for when non-admins try to delete from the index page

test/integration/users_index_test.rbrequire 'test_helper'class UsersIndexTest < ActionDispatch::IntegrationTest def setup @admin = users(:michael) @non_admin = users(:archer) end test "index as admin including pagination and delete links" do log_in_as(@admin) get users_path assert_template 'users/index' assert_select 'div.pagination' first_page_of_users = User.paginate(page: 1) first_page_of_users.each do |user| assert_select 'a[href=?]', user_path(user), text: user.name unless user == @admin assert_select 'a[href=?]', user_path(user), text: 'delete' end end assert_difference 'User.count', -1 do delete user_path(@non_admin) end end test "index as non-admin" do log_in_as(@non_admin) get users_path assert_select 'a', text: 'delete', count: 0 endend

  • This also invovles changing a lot of the previous test, which he doesn’t go over at all
58
Q

Populate the heroku app with sample users >

A

Populate the heroku app with sample users >

$ rails test"$ git push heroku"$ heroku pg:reset DATABASE"$ heroku run rails db:migrate"$ heroku run rails db:seed"$ heroku restart"

59
Q

10.5.1 What we learned in this chapter”

A

10.5.1 What we learned in this chapter”

  • Users can be updated using an edit form, which sends a PATCH request to the update action.”
  • Safe updating through the web is enforced using strong parameters.”
  • Before filters give a standard way to run methods before particular controller actions.”
  • We implement an authorization using before filters.”
  • Authorization tests use both low-level commands to submit particular HTTP requests directly to controller actions and high-level integration tests.”
  • Friendly forwarding redirects users where they wanted to go after logging in.”
  • The users index page shows all users, one page at a time.”
  • Rails uses the standard file db/seeds.rb to seed the database with sample data using rails db:seed.”
  • Running render @users automatically calls the _user.html.erb partial on each user in the collection.”
  • A boolean attribute called admin on the User model automatically creates an admin?boolean method on user objects.”
  • Admins can delete users through the web by clicking on delete links that issue DELETErequests to the Users controller destroy action.”
  • We can create a large number of test users using embedded Ruby inside fixtures.”