セレクトボックスの構造
セレクトボックスの初期値(?)を設定したい
(画像サイズ・・・)
↑「選択してください」みたいな何も選択していない時の選択肢を設定したい
セレクトボックスの構造
<%= f.select :category_id, Category.pluck(:name, :id), { include_blank: true }, class: ‘form-control’ %>
<%= f.select :カラム名, 選択肢として表示する配列またはハッシュ, {オプション}, {HTMLオプション}%>
- このオプション部分に
include_blank: '選択してください'
とすると指定した文字列が先頭に表示される include_blank: true
とすると空の選択肢が先頭に表示される- include_blankを使わずに初期状態でいずれかの項目を選択肢た状態にしておきたい場合、
{selected: 1}
のようにオプションに設定すればOK
真ん中のCategory.pluck(:name, :id)
pluckメソッド
引数に指定したカラムの値を配列で返してくれる。
[1] pry(main)> Category.pluck(:name, :id) (0.1ms) select sql from (select * from sqlite_master where type='table' union select * from sqlite_temp_master where type='table') where tbl_name = 'taxonomies' (0.3ms) SELECT "taxonomies"."name", "taxonomies"."id" FROM "taxonomies" WHERE "taxonomies"."type" IN ('Category') => [["テストカテゴリ1", 4]]
↑引数に複数カラムを指定した時の返り値は二次元配列で返される。
二次元配列とは、categoriesという大きな箱(配列)の中に入っている[:nameと:id]が入った小さな箱みたいなイメージ
categories = [["カテゴリー1", 1],["カテゴリー2", 2]]
pluckメソッドで指定されたCategoryのnameとidを含む配列を返している。この配列がセレクトの選択肢として使われる。
テストの書き方における疑問点(メモ)
テストの書き方で疑問だった箇所とその理由
describe '検索機能' do let(:article_with_author) { create(:article, :with_author, author_name: '田中') } let(:article_with_another_author) { create(:article, :with_author, author_name: '佐藤') } # 中略 it '著者名で絞り込み検索ができること' do article_with_author # ① article_with_another_author visit admin_articles_path select '田中', from: 'q[author_id]' #② click_button '検索' expect(page).to have_content(article_with_author.title), '著者名での検索ができていません' expect(page).not_to have_content(article_with_another_author.title), '著者名での絞り込みができていません' end
- 指定した著者名で検索ができているのかテストしたいので、著者名田中の記事と著者名佐藤の記事をそれぞれletで作成している
① article_with_authorと記載する理由は?
describe直下に記載したletをlet!にしてしまえば、わざわざitの下にarticle_with_author
,article_with_another_author
と記載しなくても済むのに、なぜ上記のようにしているのか。
- 結論let!でも構わない
- 全部let!って良いじゃんと思っていたけれど、let!でexampleを個別実行した場合、そのテストケースに必要ないデータまで作成されてしまいテスト実行時間が若干遅くなる
- 今は関係なくてもアプリの拡大があれば、パフォーマンスも考慮しなくてはならないので頭の片隅に入れておく
- ただテストにおいて最も重視されるべきなのは、コードの記述量でも実行時間でもなく「可読性」!!
Clean Test Code Revised - Speaker Deck
② select '田中', from: 'q[author_id]'の箇所
Capybaraにおけるシミュレーションでselect '田中', from: 'q[author_name]'
と記載すべきなのか。(select article_with_author.author_name
と書いた方が今までの学習に近い)
E2E(システムスペックのようなユーザーの操作をシミュレーションする)テストにおいて、「ユーザーの操作を忠実に再現すべき」という考えがあり、その点で言えば
fill_in name, with: '田中'
の方が好ましいのではないかという考え方
→これに関しては書き手の考え方によるので、厳格にこうしなければならない!!というものはない。あとは単純に、何を選択するのか、入力するのか明記してあった方がリーダブルである
→確かに圧倒的にaritcle_with_author.author_nameみたいに長々書くより読みやすいと思った。
FactoryBotにおけるcreateとbuildの使い所
trait :with_tag do transient do sequence(:name, "test_tag_name_1") sequence(:slug, "test_tag_slug_1") end after(:build) do |article, evaluator| article.tags << build(:tag, name: evaluator.tag_name, slug: evaluator.tag_slug) # ここ end end trait :with_sentence do transient do sequence(:sentence_body, 'test_body_1') end after(:build) do |article, evaluator| article.sentences << create(:sentence, body: evaluator.sentence_body) # ここ end end
コールバックのブロック内、シャープの箇所でbuildとcreateと追加分けている理由は?
article_blockモデルのバリデーションをチェック
# app/models/aritcle_block.rb class ArticleBlock < ApplicationRecord belongs_to :article belongs_to :blockable, polymorphic: true, dependent: :destroy # ここ with_options on: %i[create update] do validates :blockable_type, presence: true validates :blockable_id, presence: true #これ validates :level, presence: true, uniqueness: { scope: :article_id } end
belongs_to :blockable, polymorphic: true, dependent: :destroy
の記述があるので、blockable_typeとblockable_idのバリデーションは要らない可能性アリ- 悪さ(?)をしているのは
validates :blockable_id, presence: true
の一行らしい - blockable_idはDB保存時に自動で振られる値のため、DBに保存しないbuildではnilになり、バリデーションに引っ掛かってしまう。このため、createを用いてテストに通るようにする
参考サイト
Rails tips: RSpecテストの高速化/リファクタリングに役立つ4つの手法(翻訳)|TechRacho by BPS株式会社
FactoryBot 関連データの作成
FactoryBotにおけるアソシエーション
belongs_toとhas_many ※ 解釈が合っているか分からない
- 所属(belongs_to)先を記載する場合はFactoryBotに
association: author
(associationは省略可)みたいに記載すればいい - has_manyの場合はassociationによる記載が使えないのでtraitやコールバックを用いて、後から関連データを入れ込む形
FactoryBot.define do factory :article do sequence(:title, "title_1") sequence(:slug, "slug_1") # belogns_toのリレーションのみ以下の記載が可能? category author # has_manyリレーションではassciationは使えないので、authorやtagの場合はtraitとコールバックであるafter(:create)を使う end
transientとコールバック after build
callback…特定の瞬間に呼び出されるメソッド
# articleのFactory続き # traitを用いてhas_manyリレーションを入れ込む trait :with_author do transient do sequence(:author_name, 'test_author_name_1') sequence(:tag_slug, 'test_author_slug_1') end after(:build) do |article, evaluator| article.author = build(:author, name: evaluator.author_name, slug: evaluator.tag_slug) end end
- transientは実際に作成するデータと直接関係がない変数を定義する機能
- モデルの属性以外のデータをファクトリに含めることができる
- 作成時に挙動を変更するためのフラグや追加データとして利用する
■ after(:build)は何をしているのか。evaluatorって何者?
コールバックを使うことで生成したインスタンスがcreate,buildされたイベントの直後に自由にインスタンスを修正できる。
- つまり
after(:build) do |article, evaluator|
というブロックでは、コールバックによってarticleインスタンスが作られた後に、article.authorの値に修正が加えられている
after(:build) do |article, evaluator| article.author = build(:author, name: evaluator.author_name, slug: evaluator.tag_slug) end
↑割と素直にコードを読んで良いんだなと思った。
- after(:build) do |article|でarticleインスタンスが作られた後、ブロック内でarticle.authorの名前をtransientで生成したものに修正している
- 第二引数の
evaluator
を渡すことで、transient
ブロック内の変数にアクセスできる
→この仕組みは、rails newコマンドでなぜ色んなファイルができるのか?みたいなレベルのお話になってくるので考えなくて良いとのこと◎
参考にしたサイト
FactoryGirlのtransientとtraitを活用する - Qiita
雑メモ帳
まとめ方を気にしている余裕がなくなったので、覚書として簡単にメモしておきます。
雑にどんどん追加していきます。
gitでcommit間の差分を出力する
経緯
課題デバッグでHTMLを不必要にいじって表示が乱れてしまったので、どこが原因でおかしくなったのか調べたい。バグに気がついたのがだいぶ後だったため、過去のコミット間の差分を調べる必要がある。
二つのcommit間の差分を出力する
git logで当該箇所のコミットIDを調べ、下記コマンドで差分をチェックできる。
git diff <コミットID1> <コミットID2>
Time.nowとTime.current
結論:Time.currentを使った方がいい
[4] pry(main)> Time.now => 2022-04-23 16:22:59 +0900 [5] pry(main)> Time.current => Sat, 23 Apr 2022 16:23:04 JST +09:00
Time.nowは環境変数(ENV['TZ]')もしくはシステムのタイムゾーンをもとに現在時刻を取得しており、デフォルト設定で環境変数の値はnil。このためアメリカ時刻になっているシステムのタイムゾーンを参照してしまう。
Time.currentはシステムのタイムゾーンに左右されず、Railsアプリごとにapplication.rbに設定したタイムゾーンを参照してくれるので、コンソール上で確認してみると日本時刻で表示されている。
【Rails】Time.currentとTime.nowの違い - Qiita
DateTime
DateTimeは非推奨らしいので、特別な理由がなければTIimeクラスを使う。
より詳しい記事
RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い - Qiita
Capybara click_onについて
click_onはclick_buttonとclick_linkのエイリアス
これはもう全部click_onでいいのでは🙄
と思っていたけど下記のような場合はclick_onで上手くクリックすることができないらしい。
#<button class="cssClassName">ボタン名</button> # このような場合は押せる click_on "ボタン名" #<button class="cssClassName"><i xxxx />ボタン名</button> #<button class="cssClassName">ボタン名(1)</button> # こんな感じだタグ内の値だとうまくいかない # click_on "ボタン名"
Rspec Capybaraで実際テストを書いて困ったシチュエーションの解消法 - Qiita
範囲演算子
経緯
昨日(終日)中に公開された記事を取得したい。
下記のように記述したけれど、実際もっとスマートな書き方があるかも知れない。
@articles_published_yesterday = Article.where(published_at: Time.zone.now.yesterday.beginning_of_day..Time.zone.now.yesterday.end_of_day)
beginning_of_day
とend_of_day
でその日の始まりとその日の終わりを取得..
か...
で範囲を指定できる..
は素直に〇〇から〇〇まで...
の場合は終端を含まないSQLが発行されている
ActiveRecordで日付・時刻の範囲検索をシンプルに書く方法|TechRacho by BPS株式会社
whenverの設定
これに関しては理解できていない箇所がかなり多い。
- cronは.zshとrbenvの環境で動いてくれないらしいので、シェルコマンドの設定と.zshrcとrbenvのPATHを設定で通す必要がある
[Rails4] wheneverを使ってcrontabを管理して、cronを回す - Qiita
remoteの設定で出たエラー
No such remote 'origin'
認可と認証 authorizeメソッド(Pundit)
経緯
課題のアプリ内で使われていたauthorizeメソッドが何のメソッドか分からなかったため少し調べてみた。Punditというgemに搭載されているメソッドらしい。PunditはRubyのgemであり、認可の仕組みを提供してくれる。
認可と認証
認証と認可は密接に絡み合っている一方で全く別の概念です。
それでも多くの場合、認可は認証に依存しています。
認証(Authenctication)
パスワードや秘密の質問等の、その人だけが知っていることを提示して貰い、通信の相手が誰(何)であるかを確認すること。
認可(Authorization)
とある特定の条件に対して、リソースアクセスの権限を与えること。
★ 認可における「とある特定の条件」が認証にあたることもある
認証せずに認可する例
電車の切符があれば電車に乗れるし、ある部屋の鍵があればその部屋に入ることができる。その人が誰であるかは関係ない。人から切符や鍵を譲り受けていることもある。誰かどうかの認証をせずに電車に乗ったり、部屋に入ったりという認可が得られる。
認証したのに認可しない例
まずないらしい。何のために認証したんだ。(確かに)
しかし OpenID Connect等、認証の委譲が発生するような分散環境においては複雑な事情がありえます。
マクロな視点では「Aシステムがユーザの認証を行い、その事実をBシステムに通知した」という状態において、Aシステムは認証をしたが、認可はしていないことになります。 一方ミクロな視点では「BシステムはAシステムからユーザを認証したことを通知された。だからBシステムは独自で持つリソースへのアクセスを許すことにした」ということがあるかもしれません。恐らく、あるでしょう。 しかしそれはミクロの話で、マクロレベルでは認証しただけ。認可したかどうかは 知らん のであります。
gem: Pundit
rails g pudit:install
上記コマンドによって、app/policies/配下にapplication_policy.rbという認可のルールを記述するファイルが作成される。
class ApplicationPolicy attr_reader :user, :record def initialize(user, record) @user = user @record = record end def index? false end end
- このファイルに定義されているクラスApplicationPolicyを継承して、コントローラごとの認可ルールを記述していく
- userにはデフォルトでcurrent_userが引数に割り当てられている
- recordには対応するモデルのインスタンスを手動で割り当てる
例)postという名前のモデルに対してpolicyを作成した場合
# app/policies/post_policy.rb class PostPolicy < ApplicationPolicy def update? user.admin? or not recorc.published? end end
- モデル名_policy.rbでファイルを作成
- モデル名Policyでクラス名を定義
- def アクション名?で認可ルール(policy)を記述
→このアクションの返り値によって認可するかどうか」を判断する
def update authorize @post
- authorizeメソッドによって、policyファイルに記述されたdef update?が処理される
- 引数には対応するモデルオブジェクトを入れる
uuidとは
URLヘルパーにarticleのみ渡したらエラーが出た
describe '記事作成で文章ブロックを追加' do let(:article) { create :article } context '文章を追加せずにプレビューを閲覧' do it '正常に表示される' do visit edit_admin_article_path(article) # article.uuidを渡すと通る click_link 'ブロックを追加する' click_link '文章' click_link 'プレビュー' switch_to_window(windows.second) expect(page).to have_content(article.title), 'プレビューページが正しく表示されていません' end end end end
そもそもuuidって何?
Universally Unique Identifier(ユニバーサリー・ユニーク・アイデンティファイア)
重複しない(ことになっている)IDのこと。
uuidを使う理由は?
URLのidから情報を推測しづらくする
いつも利用している整数のidを用いた場合、URLのidから情報が推測しやすくなってしまう。
https://*****/users/1
のようなURLがあったとして、末尾のidを変えれば別ユーザーのページに行けそうという考えを持たせる要因になってしまう。
このため550e8400-e29b-41d4-a716-446655440000
のような被らない一意の文字列uuidを用いることでURLから情報を推測しづらくすることができる。
注意点
uuidはidの値が乱数になるため、User.firstやUser.lastなどで発行されるSELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1
のようなSQLクエリではデータが取得できない。既存のプロジェクトにuuidを導入する場合はorder,first,lastなどが使われていないか確認する。
参考にしたサイト
https://wa3.i-3-i.info/word13163.html
パンくずリスト
Webページの上部(?)に表示される現在地の階層みたいな表示のこと。これがあることによって以下のメリットがある。
gem: gretelを使用
パンくずリストを簡単に実装できるgem
gem 'gretel' # bundle install
rails g gretel:install
↑このコマンドにより、config配下にbreadcrumbs.rbというファイルが作成される
# 他のパンくず設定で親ページを指定する際、ビューファイルでパンくずを呼び出す際に使用 crumb :edit_admin_site(パンくず名) do # リストに表示されるテキストとリンクされるURL link '設定', edit_admin_site_path # 現ページの前のページ(親ページ名) parent :admin_dashboard end
詳細画面のパンくずリスト
詳細画面なので固有のidが必要
crumb :user_show do |user| link "#{user.name}さんの詳細", user_path(user) parent :root end
- ブロック変数を使う
→ブロック変数の中身はこのパンくずをビューファイルで呼び出すときや、子となるパンくずうの設定内で定義する - リストに表示する文字は式展開を使うことが可能(いつものlink_toと一緒)
ビュー
<% breadcrumb :user_show, @user %>
- インスタンス変数はコントローラで定義されているものを使用
- ブロック変数に渡すため、呼び出し時に変数記載
- application.html.erbへの記載により、各ビューでの呼び出し時には表示させたい箇所に記載する必要がない(後述)
編集ページ
crumb :user_edit do |user| link "ユーザー編集" parent :user_show, user end
- ユーザー編集の親ページである詳細画面にも固有のuser.idが必要なため、userを記載する
<% breadcrumb :user_edit, @user %>
パンくずリストの区切り文字
Home > ○○さんの詳細画面 > ユーザー編集
区切り文字とは文字通り、リストを区切っている「>」のような文字
<!-- app/views/layouts/application.html.erb --> <body> <%= breadcrumbs separator: "区切り文字" %> <%= yield %> </body>
- パンくずリストは全ページに表示させたいので、application.html.erbの表示させたい箇所に記載
- 区切り文字はseparator: "区切り文字"のように指定
- ただし、 「>」はHTMLの閉じタグの意味を持つのでそのまま使うのは良くない
<%= breadcrumbs separator: " › "%>
- &で始まり、;で終わる書き方でその中に表示させたい文字に対応するコードを記述する
その他
main.content-wrapper section.content-header h1 = yield 'content-header' == breadcrumbs style: :ol, class: 'breadcrumb'
補足
- breadcrumbsメソッドがパンくずリストのhtmlを生成している
- このためビューファイルにbreadcrumbsメソッドを記載しなかった場合、パンくずリストは画面に表示されない
- またbootstrapのbreadcrumbを適用させるため、class: 'breadcrumb'と記載している
- bootstrapの公式ドキュメントより、breadcrumbをol要素に適用させているため、style: :olも記載している(デフォルトだとinline)
参考サイト
【Rails】 gretelを使ってパンくずリストを作成しよう | Pikawaka
SEO とは 意味/解説/説明 (エスイーオー) 【Search Engine Optimization, 検索エンジン最適化】 | Web担当者Forum