$ git checkout -b account-activation
* I generated an Account Activation controller.
$ rails generate controller AccountActivations
* I added a route for account activation.
config/routes.rb
resources :account_activations, only: [:edit]
* In the Users data model, I added activation_digest, activated, and activated_at fields.
$ rails generate migration add_activation_to_users \
> activation_digest:string activated:boolean activated_at:datetime
* In the DB migration file, I added the default value = false for the activated field.
sample_app/db/migrate
class AddActivationToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :activation_digest, :string
add_column :users, :activated, :boolean, default: false
add_column :users, :activated_at, :datetime
end
end
* Then, I ran DB migrate command.
$ rails db:migrate
* In the User model, I added account activation code.
sample_app/app/models/user.rb
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
.
.
.
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
* In DB Seeds file, I set the status the admin user and first 99 users to activated = true.
sample_app/db/seeds.rb
activated: true,
activated_at: Time.zone.now)
* I set the activated = true status to the test fixture file too.
sample_app/test/fixtures/users.yml
activated: true
activated_at: <%= Time.zone.now %>
* Then, I reset the DB and re-generated the seeds file.
$ rails db:migrate:reset
$ rails db:seed
* The next section was generating the mailer for account_activation and password_reset.
$ rails generate mailer UserMailer account_activation password_reset
* I updated the from address of Application Mailer.
sample_app/app/mailers/application_mailer.rb
default from: "noreply@example.com"
* I updated the mailing template for User Mailer.
sample_app/app/mailers/user_mailer.rb
def account_activation(user)
@user = user
mail to: user.email, subject: "Account activation"
end
* I drafted the email template in Text file for accound activation.
sample_app/app/views/user_mailer/account_activation.text.erb
Hi <%= @user.name %>,
Welcome to the Sample App! Click on the link below to activate your account:
<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
* I did it for HTML version as well.
sample_app/app/views/user_mailer/account_activation.html.erb
<h1>Sample App</h1>
<p>Hi <%= @user.name %>,</p>
<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>
<%= link_to "Activate", edit_account_activation_url(@user.activation_token,
email: @user.email) %>
* I updated the config environment.
sample_app/config/environments/development.rb
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :test
host = 'localhost:3000'
config.action_mailer.default_url_options = { host: host, protocol: 'http' }
config.action_mailer.perform_caching = false
* Then, I re-started my server.
Keyboard: Ctrl + C
$ rail s -b 0.0.0.0 -p 3000
* I updated Mailer preview file.
sample_app/test/mailers/previews/user_mailer_preview.rb
* I previewed the Mailer template. In Firefox, I typed: http://localhost:3000/rails/mailers/user_mailer/account_activation.html
* I copied the Account Activation test case for User Mailer.
sample_app/test/mailers/user_mailer_test.rb
test "account_activation" do
user = users(:michael)
user.activation_token = User.new_token
mail = UserMailer.account_activation(user)
assert_equal "Account activation", mail.subject
assert_equal [user.email], mail.to
assert_equal ["noreply@example.com"], mail.from
assert_match user.name, mail.body.encoded
assert_match user.activation_token, mail.body.encoded
assert_match CGI.escape(user.email), mail.body.encoded
end
* In Test Environment, I added the defaul_url_options to example.com
sample_app/config/environments/test.rb
config.action_mailer.default_url_options = { host: 'example.com' }
* I editted the def create action of Users Controller.
sample_app/app/controllers/users_controller.rb
def create
@user = User.new(user_params)
if @user.save
UserMailer.account_activation(@user).deliver_now
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
* I editted the User Signup Integration Test Suite.
test/integration/users_signup_test.rb
test "invalid signup information" do
assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'
end
test "valid signup information" do
# assert_template 'users/show'
# assert is_logged_in?
end
* I added the def authenticated?(attribute, token) method in User model.
sample_app/app/models/user.rb
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
* I updated a line in Sessions Helper.
sample_app/app/helpers/sessions_helper.rb
if user && user.authenticated?(:remember, cookies[:remember_token])
* I updated the test case "authenticated? should return false for a user with nil digest" in User Model Test Suite.
sample_app/test/models/user_test.rb
assert_not @user.authenticated?(:remember, '')
* I added the edit action for Account Activation Controller.
sample_app/app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.update_attribute(:activated, true)
user.update_attribute(:activated_at, Time.zone.now)
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
end
* I pasted the following URL from the server log file to activate the new user. In Firefox, I pasted this URL:
http://localhost:3000/account_activations/UB_1LRUTcNuhOPiMvF6HvQ/edit?email=jimmyc%40example.com
* The activation was successful.
* I updated the create action in Sessions Controller.
sample_app/app/controllers/sessions_controller.rb
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
message = "Account not activated. "
message += "Check your email for the activation link."
flash[:warning] = message
redirect_to root_url
end
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
* I edited the User SignUp Integration Test.
sample_app/test/integration/users_signup_test.rb
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
end
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, params: { user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end
assert_template 'users/new'
assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'
end
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User",
email: "user@example.com",
password: "password",
password_confirmation: "password" } }
end
assert_equal 1, ActionMailer::Base.deliveries.size
user = assigns(:user)
assert_not user.activated?
# Try to log in before activation.
log_in_as(user)
assert_not is_logged_in?
# Invalid activation token
get edit_account_activation_path("invalid token", email: user.email)
assert_not is_logged_in?
# Valid token, wrong email
get edit_account_activation_path(user.activation_token, email: 'wrong')
assert_not is_logged_in?
# Valid activation token
get edit_account_activation_path(user.activation_token, email: user.email)
assert user.reload.activated?
follow_redirect!
assert_template 'users/show'
assert is_logged_in?
end
end
* I added activate and send_activation_email actions in User model.
sample_app/app/models/user.rb
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
* I updated the action name for sending activationg email in Users Controller.
sample_app/app/controllers/users_controller.rb
@user.send_activation_email
* I updated (refactored) the codes to activate a user.
sample/app/controllers/account_activations_controller.rb
user.activate
* I simplified the two transactions into one transaction in User model.
sample_app/app/models/user.rb
update_columns(activated: true, activated_at: Time.zone.now)
* I edited the index action and show action to show only active users in Users Controller.
def index
@users = User.where(activated: true).paginate(page: params[:page])
end
def show
@user = User.find(params[:id])
redirect_to root_url and return unless @user.activated?
end
* At the end of the chapter 11, I skipped the email in production using Heroku part. I ran a test, added all untracked files, committed the changes, merged back to Master branch, and pushed onto Github.
$ rails test
$ git add -A
$ git commit -m "Add account activation"
$ git checkout master
$ git merge account-activation
* The Github address for Michael Hartl's sample app project was: https://github.com/jimmy2046/sample_app
No comments:
Post a Comment