app/models/user.rb
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 }
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
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
# コストパラメータはテストでは最小にする
BCrypt::Password.create(string, cost: cost)
end
# ランダムなトークンを返す
def User.new_token
SecureRandom.urlsafe_base64
end
# 永続セッションのためにユーザーをデータベースに記憶する
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# トークンがダイジェストと一致したらtrueを返す
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
# 属性のダイジェストの値を取り出す
return false if digest.nil?
# ダイジェストが空ならばfalseを返す
BCrypt::Password.new(digest).is_password?(token)
# 渡されたトークンがダイジェストと一致したらtrueを返す
# トークンとダイジェストの関係性が理解できればこれもわかるのかな?
end
# ユーザーのログイン情報を破棄する
def forget
update_attribute(:remember_digest, nil)
end
def activate
# アカウントを有効にする
update_attribute(:activated, true)
# 有効にする
# モデル.update_attribute(属性名, 値)
# 条件に一致するモデルオブジェクトを更新、バリデーションはスキップ
update_attribute(:activated_at, Time.zone.now)
# 有効化された時間
end
def send_activation_email
# 有効化用のメールを送信する
UserMailer.account_activation(self).deliver_now
# メイラーを送る
# account_activation(self)
# subject: メールのタイトル
end
private
# メールアドレスをすべて小文字にする
def downcase_email
self.email = email.downcase
end
# 有効化トークンとダイジェストを作成および代入する
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
app/helpers/sessions_helper.rb
app/helpers/sessions_helper.rb
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
# ユーザーのセッションを永続的にする
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
# 記憶トークンcookieに対応するユーザーを返す
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(:remember, cookies[:remember_token])
# 有効且つユーザーの:remember属性の値にremeber_tokenの値は有るか? あれば次
log_in user
# ユーザーがログイン
@current_user = user
# ユーザーをログイン中のユーザーとする
end
end
end
def current_user?(user)
user && user == current_user
end
# ユーザーがログインしていればtrue、その他ならfalseを返す
def logged_in?
!current_user.nil?
end
# 現在のユーザーをログアウトする
def log_out
session.delete(:user_id)
@current_user = nil
end
# 永続的セッションを破棄する
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# 現在のユーザーをログアウトする
def log_out
forget(current_user)
session.delete(:user_id)
@current_user = nil
end
# 記憶したURL(もしくはデフォルト値)にリダイレクト
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
# アクセスしようとしたURLを覚えておく
def store_location
session[:forwarding_url] = request.original_url if request.get?
end
end
test/models/user_test.rb
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
test "should be valid" do
assert @user.valid?
end
test "name should be present" do
@user.name = ""
assert_not @user.valid?
end
test "email should be present" do
@user.email = " "
assert_not @user.valid?
end
test "name should not be too long" do
@user.name = "a" * 51
assert_not @user.valid?
end
test "email should not be too long" do
@user.email = "a" * 244 + "@example.com"
assert_not @user.valid?
end
test "email validation should accept valid addresses" do
valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
first.last@foo.jp alice+bob@baz.cn]
valid_addresses.each do |valid_address|
@user.email = valid_address
assert @user.valid?, "#{valid_address.inspect} should be valid"
end
end
test "email validation should reject invalid addresses" do
invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
foo@bar_baz.com foo@bar+baz.com]
invalid_addresses.each do |invalid_address|
@user.email = invalid_address
assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
end
end
test "email addresses should be unique" do
duplicate_user = @user.dup
duplicate_user.email = @user.email.upcase
@user.save
assert_not duplicate_user.valid?
end
test "password should be present (nonblank)" do
@user.password = @user.password_confirmation = " " * 6
assert_not @user.valid?
end
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5
assert_not @user.valid?
end
test "authenticated? should return false for a user with nil digest" do
# わからない
assert_not @user.authenticated?(:remember, '')
# remember属性が空じゃないか?(中身があるか?)
end
end
app/controllers/account_activations_controller.rb
app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationControllerclass AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
# メアドを基にdbから探す
if user && !user.activated? && user.authenticated?(:activation, params[:id])
# 存在性が有る且つ 有効状態ではない且つ
user.activate
# activateメソッドを使い、有効化、その時刻を更新する
log_in user
# ユーザーをログインさせる
flash[:success] = "Account activated!"
redirect_to user
# 自動でユーザーページに移動
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
# ホーム画面に自動で移動
end
end
end
end
app/controllers/sessions_controller.rb
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
# 新しいセッションのページ(ログイン)
end
def create
user = User.find_by(email: params[:session][:email].downcase)
# メアド(小文字)を基にdbから探し代入
if user && user.authenticate(params[:session][:password])
# 有効性且つパスワードが認められるか?
if user.activated?
# 有効化されているか?
log_in user
# ログインさせる
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
# remember_meが1であれば remember(user) 0だとforget(user)が作動する
redirect_back_or user
# ユーザーページへ
else
message = "アカウントが有効ではありません。 "
message += "アクティベーションリンクについてはメールを確認してください。"
flash[:warning] = message
redirect_to root_url
end
else
flash.now[:danger] = 'Eメールまたはパスワードが間違っています。'
render 'new'
end
end
def destroy
# セッションの削除(ログアウト)
log_out if logged_in?
redirect_to root_url
end
end
test/integration/users_signup_test.rb
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'
# htmlに書き込まれているか?
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
# 送信されたのは1件か
user = assigns(:user)
# assigns() コントローラで定義したインスタンス変数にテストの内部からアクセス
assert_not user.activated?
# 有効化されていないか?(重複を防ぐため)
# activated?は論理値らしい
log_in_as(user)
# 有効化していない状態でログインしてみる
assert_not is_logged_in?
# ログインされていないか確かめる
# テストユーザーがログイン中の場合にtrueを返す
get edit_account_activation_path("invalid token", email: user.email)
# 有効化トークンが不正な場合
assert_not is_logged_in?
# ログインされていないか確かめる
get edit_account_activation_path(user.activation_token, email: 'wrong')
# トークンは正しいがメールアドレスが無効な場合
assert_not is_logged_in?
get edit_account_activation_path(user.activation_token, email: user.email)
# 有効化トークンが正しい場合
assert user.reload.activated?
# 有効化されているか確かめる
# モデル.reload(オプション=nil)
# レコードを再取得
follow_redirect!
# リダイレクト実行後に続いて別のリクエストを行うため
assert_template 'users/show'
# showアクションが表示されているか?
assert is_logged_in?
# テストユーザーがログインされているか?
end
end
app/controllers/users_controller.rb
app/controllers/users_controller.rb
class 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
def index
@users = User.paginate(page: params[:page])
end
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
@user.send_activation_email
# メソッドを使ってメールを送信する
flash[:info] = "メールをチェックしてアカウントを有効にしてください。"
redirect_to root_url
else
render 'new'
end
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update(user_params)
flash[:success] = "プロフィールを更新しました。"
redirect_to @user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "ログアウトされました。"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "ログインしてください。"
redirect_to login_url
end
end
# 正しいユーザーかどうか確認
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless @user == current_user
end
# 管理者かどうか確認
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
config/environments/production.rb
config/environments/production.rb
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress CSS using a preprocessor.
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
# Use the lowest log level to ensure availability of diagnostic information
# when problems arise.
config.log_level = :debug
# Prepend all log lines with the following tags.
config.log_tags = [ :request_id ]
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "my_app_production"
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
host = '<あなたのHerokuサブドメイン名>.herokuapp.com'
config.action_mailer.default_url_options = { host: host }
ActionMailer::Base.smtp_settings = {
:port => ENV['MAILGUN_SMTP_PORT'],
:address => ENV['MAILGUN_SMTP_SERVER'],
:user_name => ENV['MAILGUN_SMTP_LOGIN'],
:password => ENV['MAILGUN_SMTP_PASSWORD'],
:domain => host,
:authentication => :plain,
}
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Use a different logger for distributed setups.
# require 'syslog/logger'
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
if ENV["RAILS_LOG_TO_STDOUT"].present?
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Inserts middleware to perform automatic connection switching.
# The `database_selector` hash is used to pass options to the DatabaseSelector
# middleware. The `delay` is used to determine how long to wait after a write
# to send a subsequent read to the primary.
#
# The `database_resolver` class is used by the middleware to determine which
# database is appropriate to use based on the time delay.
#
# The `database_resolver_context` class is used by the middleware to set
# timestamps for the last write to the primary. The resolver uses the context
# class timestamps to determine how long to wait before reading from the
# replica.
#
# By default Rails will store a last write timestamp in the session. The
# DatabaseSelector middleware is designed as such you can define your own
# strategy for connection switching and pass that into the middleware through
# these configuration options.
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end