ruby チュートリアル10章 メモ
form_for(@user)
の判定
form_for
を利用すると自動で入力フォームとそのsubmit先の設定等を設定し表示できるようにしてくれる。一般にcreate
アクションとedit
アクション用のフォームは同じform_for(@user)
で生成される。このアクションの違いは@user内のnew_record?
論理値メソッドを使用して判定している。その結果true
ならPOST
をfalse
ならPATCH
をリクエストに使用する。
$ rails console >> User.new.new_record? => true >> User.first.new_record? => false
認証と認可
認証(authentication)はユーザを識別すること、認可(authorization)はそのユーザが実行可能な操作を管理すること。
unless
if
文がtrue
の際の処理を書くなら、unless
文はfalse
の際の処理を記述する。
before_action
すでに定義されているアクション実行前に作動する。登録してあるユーザのみ実行させるなどの認証をここで指定できる。only:
を使用すると指定したアクションでのみ、before_action
が作動するようになる。
リダイレクトの発生タイミング
チュートリアルでは下のようなコードが出る。これは別ページでセッションに移動先を保持させておいて、このメソッドを使用してリダイレクトを発生させる。(引数は無視) この場合、リダイレクトの発生タイミングは最終行のコードが実行されてからになる為、リダイレクトも発生するがセッション削除も行われる。リダイレクトの発生タイミングは最終行のコードの実行後か明示的にreturn
を呼ばれた時になる。
#app/helpers/sessions_helper.rb def redirect_back_or(default) redirect_to(session[:forwarding_url] || default) session.delete(:forwarding_url) end
request.get?
送られてきたリクエストがGET
かどうか判定できる。チュートリアルでは下のように使用して、GET
リクエストの時だけ、URL
をセッションに退避させておき、必要になった時にリダイレクトで飛ばせるようにしている。UPDATE
やPATCH
等が飛んできた時にそれを退避してしまわないよう、GET
のみに絞っている。
#app/helpers/sessions_helper.rb session[:forwarding_url] = request.original_url if request.get?
ページネーション(pagination)
ユーザ一覧を1ページにずらっと並べると見にくいので、それを10人ずつ表示したりすること。チュートリアルではgem 'will_paginate'
を使用する。
導入後、下のように確認が可能。
@users = User.paginate(page: params[:page])
デフォルトでは30人ずつ取り出され、page
はそのインデックス番号を指定。nil
が届いた場合は、1ページ目が自動で指定される。
パーシャルの利用
下のコードはhtmlタグをパーシャルを利用して上手く表示する例。
#app/views/users/index.html.erb #before <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> #after <ul class="users"> <% @users.each do |user| %> <%= render user %> <% end %> </ul>
肝心のパーシャルの部分は、_user.html.erb
ファイルを自動で探しに行くのでそこに定義してやる。
<li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li>
この時、下のようにすれば更にeach
の部分も省略可能になる。
<ul class="users"> <%= render @users %> </ul>
RailsはこれをUser
オブジェクトのリストであると認識、それぞれをパーシャルとして書き出してくれる。すごい。
【Ruby】チュートリアルのform_forをform_withで書き換え (おまけ:capybaraでのテスト)
Ruby5.1前まではフォームを生成してくれるメソッドとして、form_for
とform_tag
があった。特にRubyチュートリアルでは2018/11/18現在、form_for
を利用した方法を紹介しています。が、5.1以降はform_with
推奨とのことなので、書き換えようと思いました。
form_with
APIドキュメントの翻訳版はこちらで読む事ができます。
基準となるモデルがある場合
チュートリアルでは7.2.1で登場してます。Userモデルを作るフォームです。
<%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %>
form_with
を使用すると下のようにかけます。基本的に変わりませんが、model:
でUserモデルを指定してあげます。チュートリアルの方を見ればわかりますが、コントローラの方で@user = User.new
やってます。
<%=form_with model: @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%>
基準となるモデルがない場合
一方チュートリアル8.1.2では、上のような誰のフォームであるかをモデルで表わせず、session
という言葉でまとめています。
<%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %>
これは下のようにして書き換えられます。
<%=form_with scope: :session, url:login_path do |f|%> <%=f.label :email%> <%=f.email_field :email, class:'form-control', id:'email'%> <%=f.label :password%> <%=f.password_field :password, class: 'form-control',id: 'password'%> <%=f.submit "Log in", class: "btn btn-primary"%> <%end%>
capybaraでのテスト
form_for
では、生成されるinputタグにid属性が付与されていました。一方form_with
ではid属性はオプションでつくようになっています。上の書き換えの例では、id属性にemail
とpassword
を指定しています。これはcapybaraでfill_in
メソッドを使用してテストするためです。(他にもテスト方法はありますが、、)
#form_for <input class="form-control" id="session_email" name="session[email]" type="email" /> #form_with(id指定なし) <input class="form-control" type="email" name="session[email]"> #form_with(id指定あり) <input class="form-control" id="email" type="email" name="session[email]">
fill_in
メソッドはidを参照するようで、id属性がないとエラーが起きます。変更前は以下のようにテストコードを書いていました。
#capybaraを使用しフォームに値を入れる fill_in "Email", with: "入力する値"
実はこれidがemail
だと反応しません。_email
かEmail
が含まれていれば反応します。 なので、どっちも小文字でemail
を指定するのが一番まるくおさまる気がします。もしくはid属性にEmail
を指定してやればテストコードはそのままで大丈夫かもしれません。
ruby チュートリアル9章 メモ
SecureRandom.urlsafe_base64
Ruby標準ライブラリのメソッド。長さ22文字の文字列を生成してくれる。base64文字列なので64の22乘通りあるため、衝突の可能性も限りなく低い。
アクセサ
インスタンス変数のゲッターとセッターのこと。rubyのクラスのインスタンス変数にはアクセサが無いとアクセスすることができない。attr_accessor
を使用することで自分で書くことなく利用できるようになる。
model(class)内でのselfの意味
チュートリアルでは、変数にself
をつける事でそのインスタンス内で共有が可能な変数を生成している。self
がない場合、ローカル変数になるとの記述があったので、おそらく定義したメソッドのスコープ内でのみ使用可能なものになってしまうはず。
ちなみにメソッドはクラスメソッド内でself
を使用するとクラスを指し、インスタンスメソッド内でselfはそのインスタンスになる。クラスメソッドはmodelクラスのレコードの更新や検索をする用に実装する。
class Article < ActiveRecord::Base def hoge #インスタンスメソッド end def self.hoge #クラスメソッド end def pdf #インスタンスメソッド self.hoge #インスタンスメソッドのhogeが呼ばれる hoge #インスタンスメソッドのhogeが呼ばれる self.class.hoge #こうするとクラスメソッドのhogeを呼べる end def self.pdf #クラスメソッド self.hoge #クラスメソッドのhogeが呼ばれる hoge #クラスメソッドのhogeが呼ばれる end end # https://qiita.com/suzuki_koya/items/1553c405beeb73f83bbc
cookiesメソッド
1つのvalueとオプションのexpires(有効期限)から成っている。下のようにpermanent
を使用すると有効期限が20年に設定される(よく20年設定が使われていた為)
cookies.permanent[:remember_token] = remember_token
それに伴いユーザIDの保存方法も変更する必要がある。signedを使用すると署名付きcookieを使うこと宣言し、permanentではcookieと同じ方法で永続化している。
#変更前 cookies[:user_id] = user.id #変更後 cookies.permanent.signed[:user_id] = user.id
ユーザIDを取得する際はcookies.signed[:user_id]
とする。
remember_me機能
remember_me
機能をチェックの有無でクッキーがブラウザに保存されるかが決まる。これだけでこの機能が実現できているのは、セッションにはチェックに関わらず、変数にユーザ情報を保持させているから。一周回って仕組みが把握しにくくなってきた。
ログアウト時のcookieの削除
ログアウト処理にてcookie
の値が正常に削除されたかをテストするには下のようにする。response
の中身のcookies
を覗かないと値が削除されていないので注意。
expect(response.cookies['user_id'].nil?).to eq false
インスタンス変数のテスト
インスタンス変数へはassigns
を利用することでアクセスが可能になる。チュートリアルではこれを用いることで今までテストされていないかったインスタンス変数をテストした。
#@user内のremember_tokenへアクセスしている expect(response.cookies['remember_token']).to eq assigns(:user).remember_token
ruby チュートリアル8章 メモ
セッションとクッキー
混同しがち(自分だけかも、)HTTPはステートレスなプロトコルなので、セッションのような物を実装するには、状態を保存するものが別に必要なる。それの一つがクッキー。クッキーはユーザのブラウザ内に保存される小さなテキスト。一方セッションはそもそも何かを継続的に行なっているものを指すらしく、接続し続けることもその中の一つと言う解釈でしょうか。
ログインページ
チュートリアルではこれから生成するセッションのコントローラがそのままログインページ系を司どるように作るそうです。言葉にするとわかりにくいので下にそれっぽいルーティングを示します。loginと言うコントローラを作るのではなく、セッションを開始終了のためのページがログイン/ログアウトってイメージですね。なお、セッションコントローラの処理はこれだけでよいので、generate
で無駄なアクションを作ったり、resources
で無駄にルーティングを作るのは避けます。
#config/route.rb get '/login', to: 'sessions#new' post '/login', to: 'sessions#create' delete '/logout', to: 'sessions#destroy'
renderメソッドとアクション
下のようにflashに値を格納した後にrenderを呼ぶと、flashは予期しない挙動をとる。と言うのも、render
でnew
を呼ぶとそれはアクションとは認められないため必然的にflashの生存時間が1アクション分伸びてしまう。
#app/controllers/sessions_controller.rb def create if ~~ # ユーザーログイン後にユーザー情報のページにリダイレクトする else flash[:danger] = 'Invalid email/password combination' render 'new' end end
flash.now
メソッドを利用することでこれを回避することができる。
flash.now[:danger] = 'Invalid email/password combination'
findメソッドとfind_byメソッド
User.find(session[:user_id])
でユーザ検索を行った場合、ユーザIDが存在していない状況(まだログインしていない、session[:user_id]の値がnilになる状態)だと、例外が発生する。一方User.find_by(id: session[:user_id])
は、例外を発生せずnilを返す。
fixtureとFactorybot
チュートリアルではMinitest利用なのでfixtureを利用しているが、RSpecを使用していならFactorybotでカバーできる。個人的には下のようにしてチュートリアルのテストコードを実装しています。
#spec/controllers/user_controller_spec.rb let(:user){build(:user)} let(:post_create){ post( :create, params: { user: { name: user.name, email: user.email, password: user.password, password_confirmation: user.password_confirmation } })} it 'ユーザが新規に生成される' do expect{post_create}.to change{User.count}.by(1) end
t-pot 観察 #6 明け方からアクセス増加
こんにちは。目立つログが残っていたので、メモしておきます。まず、今月のここまでのハニポ全体の記録をみてみます。
今の所今月一のアクセス数です。では中身を見てみます。上のグラフと形が似てしまっていますが、今日(14時まで)の記録です。
Cowrieが検知してますが、どうやら一度にあのアクセス量を受けていたわけでは無いようです。では国別とポート別に分けてみます。
詳細
中国からは211.143.198[.]237から16731程度のアクセス、アメリカからは1162.252.106[.]245から8300程度のアクセスで、どちらもsshへ向いてましたが時間帯は被っておらず。ロシアからはhttpへのアクセスですがこれもピークは他のアクセスとは被ってはおらず128.0.31[.]26から2000程度のアクセスでした。現地時間的にはアメリカとロシアからは夕方頃、中国は明け方頃ですね 。
3つの国からのアクセスは特に珍しくもなかったのですが、時間帯が近いのは珍しいと思います。(少なくとも私の環境だと) ここ最近だとMiraiが怪しいのかと思いますが、httpは確かmiraiの守備範囲ではなかった気がするので(IoTでは無い)、仮にmiraiのbotnetが動き出していたのなら、ロシアのは別攻撃ですね。そうすると中国とアメリカからのピーク時間が微妙に近いのもなんとなく腑に落ちるかもしれない?