【復習】Railsのflashの使用方法
・Railsではviewで一時的にテキストを表示するするために、flash
というハッシュ形式のオブジェクトが設定されている。
・flash
のkey
にはデフォルトでnotice
とalert
が設定されているが、add_flash_types
でkey
を追加できる。
add_flash_types :info, :warning # %i(info warning)のように、%記法は使えない?
・add_flash_types
を記入することでflash
メッセージを1行で記載できる
# before flash[:info] = 'hoge' redirect_to root_url redirect_to root_url, flash: { info: 'hoge' } #上と同じ # after redirect_to root_url, info: 'hoge'
・redirect_to
では指定したpathにルーティング⇨コントローラ⇨viewと画面を遷移させているので、さらに次のviewではflash
メッセージは表示されない。対してrender
ではaction
名に対応したview
を表示させているので、さらに次のview
でもflash
メッセージは表示される。
⇨render
ではflash.now[:info]
とすることで現在の表示に対してflash
を行う
flash.now[:info] = 'hoge' render action: :new
【復習】Railsの多言語化に関する設定
・Railsではさまざまな言語に対応するためにi18nというgemが元々導入されている。
・以下、英語化の対応方法
1.デフォルトの言語を英語にする & 日本語化するためのファイルのpathを通す
# config.application config.i18n.default_locale = :en # デフォルトの言語設定 config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '*', '.{rb,yml}').to_s] # pathを通す
2.翻訳するファイルにlazy lookup記法で翻訳名を記入する
en: activerecord: models: user: Customer attributes: user: login: Handle books: index: title: Título
3.lazy lookup記法でactiverecord:
以下にmodel:
やattributes:
がある場合、Model.model_name.human
メソッドとModel.human_attribute_name(attribute)
メソッドを使用することで対応した訳文を返す。
User.human_attrubute_name(:login) # Handleを返す
補足:form_with model: @user
のように、form_with
のmodel
オプションにUser
モデルのインスタンスを渡すと、Railsが自動で翻訳してくれる。 model
オプションがない場合は3. に書いたように明示する必要がある。
【復習】sorceryのメソッドまとめ、及びログインとは
sorceryについて
・sorcery
とは:Railsで認証機能を実装してくれるメソッドをまとめてくれたgem
・暗号化されたパスワード(crypted_pasword
)とsalt
カラムのデータはユーザーに直接閲覧や編集をさせたくないから、viewで使用できない。
⇨crypted_pasword
に対応する仮想的な属性(password
, password_confirmation
)をviewに使用する
⇨password
属性はカラムに対応してないので、入力された情報がデータベースには保存されない
login
メソッド: login(user, password)
のように引数を2つとり、引数の情報と合致するデータベースのユーザーをセッション状態にする。(第一引数はemailといったユーザーを特定するもの、第二引数はpassword
といったcrypted_pasword
に対応する属性?)
logout
メソッド: ユーザーのセッション状態を解除する。
require_login
メソッド: ログイン状態を要求するメソッド。検証用(before_action)に使用する。
not_authenticated
メソッド: ログインが認証されなかった際にroot_pathに遷移するメソッド。上書き可能。
logged_in?
メソッド: ログイン状態かを検証するメソッド。内部でcurrent_userメソッドが使用されている。
redirect_back_or_to
メソッド: フレンドリーフォワーディング(ログインが必要なページ(ページA)に未ログイン状態でアクセスした場合に、ログインページに遷移させてログイン後にページAに戻す機能)を実装するためのメソッド
セッションとCookieについて
Cookieとは
:ブラウザ側で保存される小さな情報、または領域
セッションとは
:サーバー側で一時的に保存される小さな情報、または領域
ステートレスは
:前後の繋がりで状態を保持してないこと。HTTPリクエストがこれに当たる
ログインとは
:ユーザーの情報をセッションやCookieに保存させておいて、異なるHTTPリクエストに繋がりを持たせること
form_withについて
・form_withとは
:form_tag
とform_for
両方の特徴を兼ね備えたフォーム生成ヘルパー
・モデルに紐づくフォームを生成する時
form_with model: モデルから生成されたインスタンス(@object) do |f| フォーム内容 end
モデルから生成されたインスタンスに対して、persisted?
メソッドの返り値がfalse
の場合create
アクションに、true
ならupdate
アクションに送る。その時、インスタンスの生成元のモデルと同名のコントローラのアクションに自動で遷移するようにパスを生成してくれる。
・モデルに紐づかないフォームを生成する時(create, updateを行うコントローラとモデルが紐づかない場合も)
form_with url: "パス名"(login_path), method: :メソッド名'(get) do |f| フォーム内容 end
【復習】RubyでのJavaScriptファイルの読み込み方
・ブラウザに出力されるHTMLファイルを生成する時、「layoutファイル+個別ファイル(テンプレートファイル)」の2つのファイル を足し合わせてHTMLを生成する
・Sprocketsを使用する場合、CSSを読み込むにはstylesheet_link_tag、JSファイルの読み込みはjavascript_include_tagというヘルパーメソッドを使用し、第一引数に読み込みたいアセットファイルを指定する
stylesheet_link_tag 'application' javascript_include_tag 'application'
・Webpackerを使用する場合、CSSを読み込むにはstylesheet_pack_tag、JSファイルの読み込みはjavascript_pack_tagというヘルパーメソッドを使用し、第一引数に読み込みたいアセットファイルを指定する
stylesheet_pack_tag 'application' javascript_pack_tag 'application'
・デフォルトで指定されているapplication.css、application.jsはマニュフェストファイルと呼ばれ、どのファイルをどのように連結して出力するかをまとめるファイルである
・マニュフェストファイルに含むファイルの指定には、ディレクティブと呼ばれる記法で指定する必要がある。以下CSSとJSのディレクティブ
# JavaScriptのディレクティブ //= require ファイル名 # 指定したJavaScriptファイルの内容を取り込む //= require_tree # 指定したディレクトリは以下の全ファイルを読み込む //= require_self # 地震のファイルを読み込む CSSのディレクティブ //*= require ファイル名 # 指定したJavaScriptファイルの内容を取り込む Sassでディレクティブ(CSSの//*= も使えるっぽい?) @import "ファイル名";
・image_tagを含んだlink_toヘルパーの書き方
link_to image_tag("/assets/imageにある画像のファイル名"), パス名
以下の書き方でも同じ挙動
link_to パス名 do image_tag "画像のファイル名" end
Railsの雑多なメモ(assign_attributes、バリデーションnumericality、render_to_string、delegate、truncate)
assign_attributes
特定のattributeを変更するためのメソッド。オブジェクトの変更をしただけで、DBには保存されない。
article.assign_attributes( article_params ) def article_params params.require(:article).permit( :title, :description, :state ) end
Active Recordのattributesの更新メソッド | 酒と涙とRubyとRailsと
ActiveRecord の attribute 更新方法まとめ - Qiita
バリデーションnumericality
属性に数値のみ(Numericクラス)が使われていることを検証するバリデーション。
class Player < ApplicationRecord validates :points, numericality: true end
数値の上限を制限するless_than_or_equal_to
オプションや、下限を制限するgreater_than_or_equal_to
オプションなど、オプション指定することで制約を追加できる。
class Player < ApplicationRecord # 100以下の数値になるように制限する validates :points, numericality: { less_than_or_equal_to: 100 } # 10以上の数値になるように制限する validates :points, numericality: { less_than_or_equal_to: 10 } # 以下のように複数オプションを指定することもできる validates :points, numericality: { less_than_or_equal_to: 100, greater_than_or_equal_to: 10 } end
render_to_string
コントローラーないで使用するメソッド。renderした結果のviewをブラウザで表示する必要がなく、文字列として取得する場合はrender_to_string
メソッドを使用する。
# render先のviewを文字列で返している controller.render_to_string("shared/_media_image", locals: { medium: medium }, layout: false) => "<div class=\"media-image\"><img src=\"http://localhost:3000/rails/active_storage/representations/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBRQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--dde48d8f52e682864a7f2b7d68a3468f7f1fa227/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9MY21WemFYcGxTU0lOTVRBeU5IZzNOamdHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--6ec66a681693b203f182cf67cd6ee51b8eb65eca/sample01.jpg\" /></div>"
delegate
アソシエーションを組んだモデルのメソッドを簡単に移譲できるようにするメソッド。
以下の記述をすることで、user.profile.name
を記述する所をuser.name
と記述することができる。
class User has_one :profile delegate :name, to: :profile end
prefixオプションをtrueにすると、生成されるメソッド名にプレフィックスを追加できる。今回の場合だとメソッド名がuser.profile_name
になる。
class User has_one :profile delegate :name, to: :profile, prefix: true, allow_nil: true end
Active Support コア拡張機能 - Railsガイド
truncate
文字列の最後を切り捨てて省略表示するメソッド。String#truncate
とTextHelper
としてのtruncateがある。
'Once upon a time in a world far far away'.truncate(27) # => "Once upon a time in a wo..."
truncate("Once upon a time in a world far far away", length: 20) # => "Once upon a time ..."
FactoryBotのtrait、transient、evaluatorについて
概要
Railsのテストでテストデータを作成する際に便利なgemである、FactoryBotにあるtrait、transient、evaluatorの機能について備忘録として書きます。
trait
通常のテストデータの作成とは別に、モデルの属性値の集合を定義できる。共通の属性値を持ったテストデータの作成時、重複を避けることができる。
一例として、以下のようなUserのファクトリを作成します。
FactoryBot.define do factory :user do sequence(:name) { |n| "admin-#{n}" } password { 'password' } password_confirmation { 'password' } role { :admin } trait :writer do sequence(:name) { |n| "writer-#{n}" } role { :writer } end end end
通常の書き方は以下の通りで、nameがadmin-1、roleがadminのデータを作成している。
let(:user) { create(:user) } pry(~)> user => #<User:0x00007fd7e6a9f0e0 id: 1, name: "admin-1", password: "password", password_confirmation: "password", role: "admin", created_at: Tue, 25 May 2021 03:14:48 JST +09:00, updated_at: Tue, 25 May 2021 03:14:48 JST +09:00>
次にtraitを活用したuserは、nameがwriter-1、roleがwriterのデータを作成している。
let(:writer) { create(:user, :writer) } pry(~)> writer => #<User:0x00007fd7f23b4eb8 id: 2, name: "writer-1", password: "password", password_confirmation: "password", role: "writer", created_at: Tue, 25 May 2021 03:15:04 JST +09:00, updated_at: Tue, 25 May 2021 03:15:04 JST +09:00>
transient
テストデータの元となるモデルに定義されてない任意の属性を定義することができる。
一例として、以下のようなUserのファクトリを作成します。
FactoryBot.define do factory :user do transient do state_name { 'user' } end name { "tanaka(#{state_name})" } password { 'password' } password_confirmation { 'password' } trait :admin do state_name { 'admin' } end trait :writer do state_name { 'writer' } end end end
traitのadminを使用した場合、nameがtanaka(admin)のデータを作成している。
let(:admin) { create(:user, :admin) } pry(~)> admin.name => "tanaka(admin)"
traitのwriterを使用した場合、nameがtanaka(writer)のデータを作成している。
let(:writer) { create(:user, :writer) } pry(~)> writer.name => "tanaka(writer)"
evaluator
FactoryBotのcallbackとして渡されている引数。その中身は、FactoryBotで作成されたインスタンスも、transientで定義した属性もハッシュの形で返してくれるオブジェクト。transientで定義した属性を取り出したいときに必要となる引数。
一例として、Articleを作成後、一対多のアソシエーションを結んだAuthorモデルのデータを作成するファクトリを作成します。
FactoryBot.define do factory :article do sequence(:title) { |n| "title-#{n}" } state { :published } end trait :with_author do transient do sequence(:author_name) { |n| "test_author_name_#{n}" } end after(:build) do |article, evaluator| article.author = build(:author, name: evaluator.author_name) end end
with_authorのtraitを使用したデータを作成後、authorメソッドを使用することでAuthorモデルのデータを取得することができる。
let(:article_with_author) { create(:article, :with_author) } pry(~)> article_with_author.author => #<Author:0x00007f9a263a0670 id: nil, name: "test_author_name_1", created_at: nil, updated_at: nil>
Punditを使用した認可機能の作成方法
前提
scaffoldで作成したようなスタンダードなRailsアプリを想定しており、UserとPostモデルがあり1対多の関係にあるものを想定しています。
class User < ApplicationRecord has_many :posts enum state: { writer: 0, editor: 1, admin: 2 } end class Post < ApplicationRecord belongs_to :user end
基本的な使い方
1. punditをインストールした後、application_controller.rbにPunditを適用させる
# Gemfile gem 'pundit' => bundle installコマンドを実行
application_controller.rbにPunditを適用させる
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base include Pundit end
2. 以下のコマンドを実行後、生成されたファイルに設定を記述する.
以下のコマンドを実行することで、app/policies/
配下にapplication_policy.rbというファイルが生成される。
% bin/rails g pundit:install
application_policy.rbに以下の設定を記述する
# app/policies/application_policy.rb class ApplicationPolicy attr_reader :user, :record def initialize(user, record) @user = user @record = record end end
initializeメソッドでインスタンス変数を定義している。
第一引数はuserを渡してある。Punditはデフォルトでコントローラーでcurrent_userメソッドを呼んで、userとして自動的に第一引数のuserに渡してある。
2番目の引数は、認証をチェックしたい何らかのモデルオブジェクトです。
3. コントローラーの各アクションの実行前にauthorizeを実行する.
# # app/controllers/articles_controller.rb class PostsController < ApplicationController before_action :set_post, only: [:show, :edit, :update, :destroy] before_action :authorize_post, only: [:index, :new, :create] def index; end def show; end def new; end def create; end def edit; end def update; end def destroy; end private def set_post @post = Post.find(params[:id]) authorize @post end def authorize_post authorize Post end end
4. 個別のPolicyファイルを作成し、設定を記述する.
Punditは、リソースに対して、どのユーザーであれば処理が許可されるのかを定義するものです。
「コントローラのアクション名 + ?」のメソッドを定義し、返り値が true なら許可、 false なら拒否になる。例えばPostモデルのオブジェクトに対して以下の設定を行う場合、
# app/policies/post_policy.rb class PostPolicy < ApplicationPolicy def index? true end def show? true end def new? true end def create? user.admin? || user.editor? end def edit? true end def update? user.admin? || user.editor? end def destroy? user.admin? || user.editor? end end
上記のようにポリシーファイルを設定すると、Postモデルのオブジェクトに対して、create、update、destoryメソッドは管理者、編集者のみが実行可能、他のアクションは全てのユーザーが実行可能、といった定義を行うことが出来る。
Scope
policy_scope
メソッドを呼ぶと、対象ポリシーファイル内で定義されたスコープを実行してくれる。
例えば、管理者はすべてのリソースにアクセスできるが、一般ユーザは自身が保持しているリソースのみといった認可を設定したい場合、以下のように実装すれば良い。
class PostPolicy < ApplicationPolicy class Scope < Scope def resolve if user.admin? scope.all else scope.where(user_id: user.id) end end end end
class PostsController < ApplicationController before_action :authorize_post, only: [:index] def index @posts = policy_scope(Post) end private def authorize_post authorize policy_scope(Post) end end
例外を403ステータスにするする方法
# config/application.rb module Post config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden end
補足:Punditが用意してあるPolicySpecでテストコードを書く
spec_helper.rbに以下のコードを記述
# spec/spec_helper.rb require "pundit/rspec"
上記の記述によりpermit
というヘルパーが使用できるようになるので、これにユーザとリソースオブジェクトを渡すだけで書ける。
require 'rails_helper' RSpec.describe TaxonomyPolicy do subject { described_class } permissions :index?, :create?, :update?, :destroy? do let(:admin) { create(:user, :admin) } let(:editor) { create(:user, :editor) } let(:writer) { create(:user, :writer) } let(:tag) { create(:tag) } let(:author) { create(:author) } let(:category) { create(:category) } it "ライターはタグ・著者・カテゴリーの一覧表示・編集・削除機能にアクセスできない" do expect(subject).not_to permit(writer, tag, author, category) end it "管理者はタグ・著者・カテゴリーの一覧表示・編集・削除機能にアクセスできない" do expect(subject).to permit(admin, tag, author, category) end it "編集者はタグ・著者・カテゴリーの一覧表示・編集・削除機能にアクセスできない" do expect(subject).to permit(editor, tag, author, category) end end end