パーフェクト Ruby on Rails
Qiitaよりこっちのほうが良さげなので移行
第6章から
初期構築の際の注意点
-
rails new
の際など、不要なコンポーネントが生成しないようにオプションを追加しておくべき- 不要なファイルは開発のノイズになるため
- サーバー起動時に不要なコンポーネントを読み込んだ分、メモリが余計に消費されてしまうから
- 不要ファイルは可能な限り削除しよう
-
複数のテンプレートエンジンが存在するとファイルの読み込み順によっては使用しないほうが使われてしまう恐れがあるので、gemは削除しておこう
gemのバージョンアップ戦略
gemは基本的にこまめにバージョンアップしていくべき
- テストコード書いていないから確認コスト高いといった理由などでバージョンアップしないプロダクトも多い
- 当然脆弱性は高くなっていくが、パッチも当てずになってしまうケースが多い
- gemのバージョンアップしていくには、まずテストを書き、その上でバージョンアップを続けられる仕組みを作ることが重要
- Github Dependabotとか
OAuth
OAuthとは、第三者となるアプリに対して安全にアクセス権限を付与するためのプロトコルのこと
- OAuthがないと、例えばユーザーのGithubパスワードとかを全部アプリケーション側に預けることになり、アプリケーション側はGithubと同様の権限を持つことになってしまう
- OAuthはこういったアプリ側に権限を渡さず、必要な権限だけを与える仕組み
-
OmniAuth
は様々なユーザー認証機能を提供するgem
config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
if Rails.env.development? || Rails.env.test?
provider :github, "CLIENT_ID", "SECRET"
else
provider :github,
Rails.application.credentials.github[:client_id],
Rails.application.credentials.github[:client_secret]
end
end
Rack
Rackミドルウェア
とは
- RackはRubyでWebアプリを作るときの標準仕様
- Railsアプリでは、一番外側にRackミドルウェア群が配置されている
- つまり routes - controller - view よりも外側の世界があり、それらがRackミドルウェア群
- OmniAuthはRackミドルウェアとして動作している
https://zenn.dev/igaiga/books/rails-practice-note/viewer/rack_middleware_and_rack
Railsのproduction環境で使用する平文パスワードに対する対応
-
config/credentials.yml.enc
ファイルに暗号化したyamlファイルを保存している-
.enc
はフリーソフト「ED」で暗号化・変換を行ったファイル - 復号化用のキーが無い限り中身を読み取ったりはできない
- 復号化のキーはrails new時に生成された
config/master.key
ファイルにかかれている - これは公開すると大変だが、デフォルトで.gitignoreに設定されている
- 復号化のキーはrails new時に生成された
- また、master.key以外にもRAILS_MASTER_KEY環境変数に復号化キーを設定してもよい
- 本番環境ではこっちのほうを利用することが多い
-
rails credentials:edit
コマンドでconfig/credentials.yml.enc
ファイルを編集し、暗号化できる
-
sessionsコントローラー
class SessionsController < ApplicationController
def create
user = User.find_or_create_from_auth_hash!(request.env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_path, notice: "ログインしました"
end
end
-
request.env["omniauth.auth"]
- OmniAuth::AuthHashというクラスのオブジェクト
- ここにGithubから渡されたユーザー情報やアクセストークンが格納されている
request.env
自体はRackの中の仕組み また今度調べる
https://zenn.dev/zakky/scraps/4d9944b4f5f8f1
-
session[:user_id]
にUserオブジェクトのidを入れることで「ログインした」という扱いにする
find_or_create_from_auth_hash!の実装
app/models/user.rb
def self.find_or_create_from_auth_hash!(auth_hash)
provider = auth_hash[:provider]
uid = auth_hash[:uid]
nickname = auth_hash[:info][:nickname]
image_url = auth_hash[:info][:image]
User.find_or_create_by!(provider: provider, uid: uid) do |user|
user.name = nickname
user.image_url = image_url
end
end
-
auth_hash
は呼び出し先のrequest.env["omniauth.auth"]
に入っているユーザー情報など -
find_or_create_by!
はActiveRecordが用意しているメソッド- https://railsdoc.com/page/find_or_create_by
- 引数で渡した値(providerとuid)を持つレコードが存在すれば、そのオブジェクトを返す
- 存在しなければ引数及びブロック内の値を設定してレコードを作成し、そのオブジェクトを返す
- クラメーションマークがついている場合は保存失敗した場合に例外発生を起こしてくれる
private
に関して
アクションとして呼び出せるのは、publicメソッドだけです。補助メソッドやフィルタのような、アクションとして呼び出したくないメソッドには、privateやprotectedを指定して公開しないようにするのが定石です。
https://railsguides.jp/action_controller_overview.html#メソッドとアクション
Railsに関してはアクションとして呼び出すものにはpublic それ以外はprivateらしい
- アクションとは、「ユーザーリクエストに対して行う処理」のこと
public
private
protected
- publicはクラス外部からでも自由に呼び出せるメソッド
- privateは外部に公開されないメソッド
- すなわち「クラスの外からは呼び出せず、クラスの内部のみで使えるメソッド(レシーバーがselfに限定されるメソッド)」
- ちなRuby2.7からはselfを付けてprivateメソッドを呼び出すことが許容されたらしい(それ以前はエラー)
- Rubyの特徴として、「privateメソッドはサブクラスでも呼び出せる」という特徴がある
class User
def goobye
"goodbye #{self.name}"
end
private
def hello
'hello'
end
def name
'alice'
end
end
rb(main):026:0> user = User.new
=> #<User:0x000055fc90f95ac8 id: nil, provider: nil, uid: nil, name: nil, image_url: nil, created_at: nil, updated_at: nil>
irb(main):027:0> user.hello
/usr/local/bundle/gems/activemodel-6.1.7/lib/active_model/attribute_methods.rb:466:in `method_missing': private method `hello' called for #<User id: nil, provider: nil, uid: nil, name: nil, image_url: nil, created_at: nil, updated_at: nil> (NoMethodError)
# これはクラス外部から呼び出しているのでエラー
irb(main):029:0> user.goobye
=> "goodbye alice"
irb(main):030:0>
application_controller.rb
class ApplicationController < ActionController::Base
helper_method :logged_in?, :current_user
private
def logged_in?
!!session[:user_id]
end
end
-
logged_in?
メソッドはアクションではない(ログイン状態を返すだけでユーザーからのリクエストの処理ではない)ためprivateになっている - またコントローラーとビューの両方から利用するメソッドのためapplication_controllerに定義
- ビューで使用するために
helper_method
に宣言をさせておく
- ビューで使用するために
-
!!
にすることで必ずbooleanが返ってくる
タイムゾーンに関して
- デフォルトだと当然UTC
-
config.time_zone = "Tokyo"
にすればJSTになる- ただし、この設定はDBに保存される時間はUTCに変換される
- DBカラムを直接参照しない限りはJSTをそのまま扱うことになる
- この仕様は複数のタイムゾーンにまたがるApplicationを開発するときに真価を発揮する
バリデーションに関して
- モデルのバリデーションを決める際に文字数制限は忘れられがち
- 多くのRDBではstringやtext型の入力には制限を設けていないため、例えば1万字とかでも入力できてしまう
- これらの型のときは必ず文字数の制限を入れておこう
class User < ApplicationRecord
before_destroy :check_all_events_finished
has_many :created_events, class_name: "Event", foreign_key: "owner_id"
略
- Userクラスでは「関連元のユーザーが作成したイベント」の関連であることがわかりやすくなるように
created_events
という名前で設定してる - また、外部キーもわかりやすく別名をつけている
- こうやって関連してるクラスとの関係性がよりわかりやすくなるようにするのがいいね
Railsで日時を扱うときにはTime
やDateTime
といったRuby組み込みクラスではなく、Time.zone.now
やTime.current
メソッドで返されるActiveSupport::TimeWithZoneオブジェクトを利用するのが一般的
rails-ujs
とは
Railsの一部のRESTfulな動作や非同期な処理などを実現するために、JavaScriptの送信に関する処理などが書かれたライブラリです。
-
form_with
はデフォルトでdata-remote="true"
の属性を持ったformタグを生成する- このformが送信されると、rails-ujsがそれを捕捉してAjaxで非同期にフォームの情報を送信してくれる
- これによって通常の画面遷移を行うよりもAjaxを利用して部分的にDOMを変更するほうが画面遷移を速く終わらせることができる
-
Turbolinks
も通常のリンクを自動でAjaxに変更してくれる- こっちはリンクが通常のリンクと同じように扱えるが、formからAjaxリクエストを送ったときのレスポンスは自動化されておらず、どうするか開発者に委ねられている
- AjaxでPOSTした結果、バリデーションエラーになった場合の結果は開発者で実装する必要があるが、DHHとしてはSJRで対応するとよいとしている
- Server-generated Javascript Response
document.getElementById("errors").innerHTML = "<%= j render("errors", errors: @event.errors) %>"
- 最終的にJSファイルで返すので、erbで実装されている
-
j
メソッドはescape_javascript
メソッドのエイリアス- 改行やクオートなどをエスケープし、文字列をJSとして扱えるようにしてくれる
- ビューテンプレートの探索は
app/アクションの所属するコントローラー名
で行われるが、それが見つからなかった場合は親コントローラー名で探索される- 基本的にどのコントローラーもapplicationControllerを継承しているので、
app/views/application/配下
にビューテンプレートを配置しておくと汎用的につかえて便利
- 基本的にどのコントローラーもapplicationControllerを継承しているので、
SJRとはつまり、サーバーサイドでJSを生成して、クライアントサイドではそのままそれを実行するという形式になる
要するにサーバー側でHTMLを生成してクライアントでそれを受け取るだけなので、フロントエンドをほとんど書く必要がなくなる
ただこれはReactとかリッチUIを作るケースとは真っ向から対立するので、開発用途に応じて選ぶべき
.alert.alert-danger
%ul.mb-0
- errors.full_messages.each do |message|
%li= message
hamlは要素の隣にrubyコードの=を書く場合、要素や属性と=をくっつけないと認識されないっぽい
テンプレートがないというエラーに遭遇
原因
local: false
でAjaxを許可していなかったため
= form_with(model: @event, class: "form-horizontal", local: false) do |f|
- これで
とdataremote="true"
の属性が追加される
def edit
@event = current_user.created_events.find(params[:id])
end
↑はcreated_events
メソッド使わなくてもEvent.find_by(owner_id, current_user.id, id: params[:id])
で取得できるが、関連を使える場合はなるべく関連を使うこと
例えばowner_idで絞り込む場合、絞り込みがなくてもエラーにならないので誰でも編集できるようになってしまい大事故になりかねない
class CreateTickets < ActiveRecord::Migration[6.1]
def change
create_table :tickets do |t|
t.references :user, foreign_key: true
t.references :event, null: false, foreign_key: true, index: false
t.string :comment
t.timestamps
end
add_index :tickets, %i[user_id event_id], unique: true
end
end
- 退会した際にuser_idカラムがnullになることを許容するためにnullを許容している
- ユーザーがイベントに重複して参加することがないようにユニークインデックスを貼っている
- 複合インデックスを定義したことになる
https://qiita.com/towtow/items/4089dad004b7c25985e3