RailsTutrial11章
紹介
アカウント有効化
トークンとメールアドレスの情報の2つをURLに含める。 (URLパラメータとか) 色々含んだURLを盛り込む。
activation
したのかしてないのかのカラムも必要
editアクションがちょうどよい。/:id が入れられるから showアクションはリソースを変更しないから普通。 Restfulの規則的にはeditの方がふさわしいので。
sessionのときと同じかんじ
def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end
attr_accessorに追加 before_create : userがsign_upされる直前に必要。
ActionMailerについて
View → Mail Controller → Mailer
メールの中に何を含めるか
actionと違って引数がとれるメソッドと呼ぶべき コントローラを継承しているけど
=> return: mail object (text/html) example: mail.deliver
アカウント有効化のプレビューメソッド
def account_activation user = User.first user.activation_token = User.new_token UserMailer.account_activation(user) end
signup時のcreateアクションを更新
オマケトピックス
9章を参考に作ってもよいが
@user.authenticated?
を便利にしたい!
メソッドを使い分けたいときどうするか?
エラーになる。以下のことを実現したい。
@user.remember_diggest 上に対して a = "remember"
@user.#{a}digest @user"#{a}digest"
rubyの機能に`send`メソッドがある。 "文字列でも書ける" メソッド名が一致していないとこの前提条件を満たすことができない!!! 未来を予想して作らないと無理 `メタプログラミングの序章`
attribute = :activation user.send("#{attribute}_digest")
nilじゃないか確認&2回目のクリックしたとかがないか& <span style="color: #62c7c9">params[:id]となっているけどこれはトークンが入っている</span>
if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now)
# 目立たないバグ activation前にログインできてしまう問題 # リファクタリング メソッド化する
UserMailer.accont...これがメールに飛ぶっていうのがわかりにくいのでメソッド化する
RailsTutorial9章
参考資料前提
[
ユーザIDの暗号化 IDとTokenもcookieの中で暗号化したい。→@user.authenticate!した後にログイン不要になる。 attr_accessor データベースには保存しないがインスタンス変数には保存したい。
トークン生成用メソッドを追加
さしあたっての実装計画としては、user.rememberメソッドを作成することにします。このメソッドは記憶トークンをユーザーと関連付け、トークンに対応 する記憶ダイジェストをデータベースに保存します。リスト 9.1のマイグレーションは実行済みなので、Userモデルには既にremember_digest属性が追加 されていますが、remember_token属性はまだ追加されていません。このためuser.remember_tokenメソッドを使ってトークンにアクセスできるようにし、 かつ、トークンをデータベースに保存せずに実装する必要があります。そこで、6.3で行ったパスワードの実装と同様の手法でこれを解決します。あのとき は仮想のpassword属性と、データベース上にあるセキュアなpassword_digest属性の2つを使いました。仮想のpassword属性はhas_secure_passwordメソ ッドで自動的に作成されましたが、今回はremember_tokenのコードを自分で書く必要があります。これを実装するため、4.4.5で触れたattr_accessorを使 って「仮想の」属性を作成します。
def remember self.remember_token = ... update_attribute(:remember_digest, ...) end
rememberメソッドの1行目の代入にご注目ください。selfというキーワードを使わないと、Rubyによってremember_tokenという名前のローカル変数が成 されてしまいます。この動作は、Rubyにおけるオブジェクト内部への要素代入の仕様によるものです。今欲しいのはローカル変数ではありません。
def User.new_token SecureRandom.urlsafe_base64 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?(cookies[:remember_token]) log_in user @current_user = user end end
class User < ApplicationRecord attr_accessor :remember_token before_save { email.downcase! } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true has_secure_password validates :password, presence: true, length: { minimum: 6 } 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 def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end end
class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) log_in user remember user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' # 本当は正しくない render 'new' end end def destroy log_out redirect_to root_url end end
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 def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end def logged_in? !current_user.nil? end def log_out session.delete(:user_id) @current_user = nil end end
目立たないバグ
今のcurrent_userの使い方では、ユーザーが1つのタブでログアウトし、もう1つのタブで再度ログアウトしようとするとエラーになってしまいます。これは、もう1つのタブで "Log out" リンク>をクリックすると、current_userがnilとなってしまうため、log_outメソッド内のforget(current_user)が失敗してしまうからです(リスト 9.12)13 。
この問題を回避するためには、ユーザー がログイン中の場合にのみログアウトさせる必要があります。
ユーザーが複数のブラウザ(FirefoxやChromeなど)でログインしていたときに起こります。例えば、Firefoxでログアウトし、Chromeではログアウトせずにブラウザを終了させ、再度Chrome で同じページを開くと、この問題が発生します14 。FirefoxとChromeを使った具体例で考えてみましょう。ユーザーがFirefoxからログアウトすると、user.forgetメソッドによって>remember_digestがnilになります(リスト 9.11)。この時点では、Firefoxでまだアプリケーションが正常に動作しているはずです。このとき、リスト 9.12ではlog_outメソッドによってユーザ ーIDが削除されるため、ハイライトされている2つの条件はfalseになります。
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?(cookies[:remember_token]) log_in user @current_user = user end end end
RailsTutorial8章
積読中
全体像
メソッドについて
user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password])
if@userがないと一行目でnilが返って来たときにnilに対してauthenticate を使うことになってerrorが出ることを防ぐ目的がある。
renderとredirect_toの違い
参考 【Rails】renderとredirect_toの違いと使い分け - Qiita
DB問い合わせ削減の書き方
def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end
if session[:user_id]と予め書いておくことでfind_byの試行回数を減らせる。 DB問い合わせ数削減のため。
@current_userがnilまたはfalseだったら右を実行する。 これもDBへの問い合わせ削減。インスタンス変数の寿命の長さを利用したコ−ド。
なぜsessions_controllerではインスタンス変数ではなくローカル変数を使うのか
Ruby - railsチュートリアル 8章 createアクション内の変数についての質問|teratail
def login(user)の理由について
def log_in(user) session[:user_id] = user.id end
log_in(user)とuserを引数にとらないようにするため 以下のように書けるが
session[:user_id] = @user.id
一般的に Helper は参照透過性のある関数として書かれることが良いとされるため 別の Controller からも log_in ヘルパーを利用したくなった時に、「log_in ヘルパーを呼ぶ前に予め @user というインスタンス変数にログインしたいユーザーのインスタンスをセットしておくこと」 という複雑なルールを強いられることになります。それよりも、単純に log_in のシグネチャーとして「引数で渡されたユーザーでログインする」という挙動にしておいた方がシンプルなので設計として 落ち着きやすいわけです。
application_helperとapplication_controllerに書くときの違いについて
application_helper
viewをDRYにするためのもの
application_controller
共通のメソッドをまとめるためのもの
session_helper
module SessionsHelper def log_in(user) session[:user_id] = user.id end def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end def logged_in? !current_user.nil? end def log_out session.delete(:user_id) @current_user = nil end end
sessions_controller.rb
class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' # 本当は正しくない render 'new' end end def destroy log_out redirect_to root_url end end
application_controller
class ApplicationController < ActionController::Base include SessionsHelper end
users_controller
class UsersController < ApplicationController def show @user=User.find(params[:id]) end def new @user = User.new end def create @user = User.new(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end
RailsTutorial7章
再復習している本
- 作者:Rubyサポーターズ
- 発売日: 2017/05/17
- メディア: Kindle版
Railsの3つの環境
Railsにはテスト環境(test)、開発環境(development)、そして本番環境(production)の3つの環境がデフォルトで装備されています。Rails consoleのデフォルトの環境はdevelopmentです。
ストロングパラメータ
paramsハッシュ全体を初期化するという行為はセキュリティ上、極めて危険だからです。これは、ユーザーが送信したデータをまるごとUser.newに渡していることになります。ここで、Userモデルにadmin属性というものがあるとしましょう。この属性>は、Webサイトの管理者であるかどうかを示します(この属性を実装するのは10.4.1になってからです)。admin=’1’という値をparams[:user]の一部に紛れ込ませて渡してしまえば、この属性をtrueにすることができます。これはcurlなどのコマンドを使 えば簡単に実現できます。paramsハッシュがまるごとUser.newに渡されてしまうと、どのユーザーでもadmin=’1’をWebリクエストに紛れ込ませるだけでWebサイトの管理者権限を奪い取ることができてしまいます
params.require(:user).permit(:name, :email, :password, :password_confirmation)
エラー数を英語で表記
<%= pluralize(@user.errors.count, "error") %>
>> helper.pluralize(5, "error") => "5 errors"
このコードは、例えば"0 errors"、"1 error"、"2 errors"などのように、エラーの数に応じて活用された単語を返します。これにより、"1 errors" のような英語の文法に合わない文字列を避けることができます(これはWebアプリでもデスクトップアプリで>も実によく見かけるエラーです)。
redirect_to @userについて
redirect_to @userはredirect_to user_url(@user)と等価
エラーメッセージ
<% flash.each do |message_type, message| %> <div class="alert alert-<%= message_type %>"><%= message %></div> <% end %>
ターミナル
irb(main):007:0> flash = { success: "It worked!", danger: "It failed." ,info: "aaa"} => {:success=>"It worked!", :danger=>"It failed.", :info=>"aaa"} irb(main):008:0> flash.each do |key, value| irb(main):009:1* puts "#{key}" irb(main):010:1> puts "#{value}" irb(main):011:1> end success It worked! danger It failed. info aaa => {:success=>"It worked!", :danger=>"It failed.", :info=>"aaa"} irb(main):012:0>
RailsTutorial6章
db:migrateエラー
sandboxを終了しないとdb:migrateが失敗する
rails aborted! StandardError: An error has occurred, this and all later migrations canceled: SQLite3::BusyException: database is locked
メールアドレスについて
メールアドレスの一意性を保証するためには、もう1つやらなければならないことがあります。 それは、いくつかのデータベースのアダプタが、常に大文字小文字を区別するインデックス を使っているとは限らない問題への対処です。 例えば、Foo@ExAMPle.Comとfoo@example.comが別々の文字列だと解釈してしまうデータベースがありますが、私達のアプリケーションではこれらの文字列は同一であると解釈されるべきです。 この問題を避けるために、今回は「データベースに保存される直前にすべての文字列を小文字に変換する」という対策を採ります。例えば"Foo@ExAMPle.CoM"という文字列が渡されたら、 保存する直前に"foo@example.com"に変換してしまいます。これを実装するためにActive Recordのコールバック(callback)メソッドを利用します。このメソッドは、ある特定の時点で呼び出されるメソッドです。
before_save { self.email = email.downcase } before_save { email.downcase! }
ハッシュ化されたパスワード
has_secure_password
セキュアにハッシュ化したパスワードを、データベース内の
password_digest
という属性に保存できるようになる。 2つのペアの仮想的な属性(password
とpassword_confirmation
)が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される19 。authenticate
メソッドが使えるようになる(引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド)。この魔術的なhas_secure_password機能を使えるようにするには、1つだけ条件があります。それは、モデル内にpassword_digestという属性が含まれていることです。
以下を手動で行う必要あり
$ rails generate migration add_password_digest_to_users password_digest:string $ rails db:migrate
has_secure_password
を使ってパスワードをハッシュ化するためには、最先端のハッシュ関数であるbcrypt
が必要
RailsTutorial4章
application_helperの使い方
メソッドが呼ばれるとき→viewで
apprication_helper.rb
module ApplicationHelper # ページごとの完全なタイトルを返します。 def full_title(yield_title) base_title = "Ruby on Rails Tutorial Sample App" if yield_title.empty? base_title else yield_title + " | " + base_title end end end
application.html.erb
<!DOCTYPE html> <html> <head> <title><%= full_title(yield(:title)) %></title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <%= yield %> </body> </html>
home.html.erb
<% provide(:title, "Home") %> <!DOCTYPE html> <html> <head> <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> </head> <body> <h1>Sample App</h1> <p> This is the home page for the <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </p> </body> </html>
参考 https://qiita.com/yukiyoshimura/items/f0763e187008aca46fb4
- full_title(yield(:title))ここで引数を渡している。
<% provide(:title, "Help") %>
<title><%= yield(:title) %>
provideで定義してyieldで呼び出す。
スッキリわかるJava入門3②
メソッド呼び出しについて
test()の ()は変数名区別するために書かれているので引数なしのメソッド呼び出しを 意味しているわけではないことに注意。
mainメソッド以外からのメソッドの呼び出し
public class Main { public static void methodA() { System.out.println("methodA"); methodB(); } public static void methodB() { System.out.println("methodB"); } public static void main(String[] args) { methodA(); } }
ソースコードの中に複数のメソッドが定義されている場合、 その順序には意味や特約があるわけではない。 ソースコード内に定義された各メソッドは同列の存在であって 上に記載されたものから順番に動くというわけではない。