RSpecでページのステータスコードを取得する方法
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
今回は超短いアウトプットです!!
いつも6000字は超えるようにしてるんですけど、今回は3000字ぐらいになります。
というのも紹介することがシンプルなんですよね。
タイトルにあるとおり、今回は「RSpecでページのステータスコードを取得する方法」を紹介します。
Capybaraではテストでステータスコードを取得できない
RSpecのテスト方法にはCapybaraを使ったテスト方法が主流かなと思っています。
テスト時に実際にブラウザを起動させて行うテストで、エラーがあったときにはエラー時のスクリーンショットを取ってくれる便利なやつです。
設定ファイルは以下のようになります。
# spec/support/capybara.rb RSpec.configure do |config| config.before(:each, type: :system) do driven_by :selenium, using: :chrome, screen_size: [1920, 800] end end
spec/support
のディレクトリを生成し、spec_helper.rb
とは違うファイルでテストの設定を行えるようにしています。
一見完璧に見えるCapybaraなのですが、意外と欠点があるんですよね。
それが「ページのステータスコードを取得できない」です。
例えば、権限がないページにユーザーがアクセスしたときに、403エラー(:forbidden
)が発生します。そして、エラーが発生した時のステータスコードは403となるはずです。ですので、ステータスコードを検証したいのですが、それがCapybaraだとできないんです。
driven_by(:rack_test)
ステータスコードを取得するテストはどうすれば良いのかというと、実は簡単でCapybaraではなく、テストの設定をdriven_by(:rack_test)
に変えるだけです。
この設定は実はシステムスペックファイルを作成するコマンド実行時にデフォルトで生成されたシステムスペックファイルに記述されています。
$ bin/rails g rspec:system sample create spec/system/samples_spec.rb
作成されたシステムスペックファイル
# spec/system/samples_spec.rb require 'rails_helper' RSpec.describe "Samples", type: :system do before do driven_by(:rack_test) # ここ! end pending "add some scenarios (or delete) #{__FILE__}" end
これでステータスコードを取得できるんですね。
実際にテストコードに書いてみます。
RSpec.describe "Pundit", type: :system do let(:writer) { create(:user, :writer) } before do driven_by(:rack_test) # ここに記載 end describe 'Writer' do before { login_as(writer) } describe 'Not authorized pages' do context 'When writer accesses to admin_categories_path' do before { visit admin_categories_path } fit 'Current_page is 403 error page' do expect(page).to have_content('You are not authorized to perform this action.'), '403エラーページが表示されていません' expect(page.status_code).to eq(403), 'ステータスコードが403ではありません' end end end end end
今回はadmin_categories_path
に権限を持たないwriterがアクセスするとステータスコードが403になるかを検証します。driven_by(:rack_test)
を使うとステータスコードを取得するpage.status_code
が使用できるようになります。実際に使ってみると403が返ってきます。
[1] pry(#<RSpec:・・・省略)> page.status_code => 403
しかし、driven_by(:rack_test)
を記述すると、Capybaraのテスト設定がオーバーライドされてしまい、スクリーンショットをとることができなくなるのでご注意を。
じゃあCapybaraでpage.status_code
を実行すればいいのでは?と思う方もいると思いますが、残念ながらCapybaraのテストでpage.status_code
を実行するとエラーが返ってきます。
[1] pry(#<RSpec:・・・省略)> page.status_code Capybara::NotSupportedByDriverError: Capybara::Driver::Base#status_code
なのでステータスコードを取得することを検証するシステムスペックファイルにのみ、driven_by(:rack_test)
を記載しましょう。
終わりに
これで連続投稿8日目となりました!
どこまで行けるかわかりませんが、少なくとも今月中は毎日アウトプットを繰り返してみようと思います。
以上、大ちゃんの駆け出し技術ブログでした!
transientとafter(:build)
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
RSpecで少し詰まった箇所があったので、今回はそちらを記事にします。今回の記事は試験的に少し雑なアウトプットをさせていただきます。いつもの記事も雑かもしれませんが、、、
理由は下記2つです。
- そもそも読者想定は初学者ですが、ある程度勉強を進めているので1からアプリを用意するということはもうすでに必要ないことなのかなと思った。
- 2月は毎日投稿を目指していますが、仕事しながらだとRUNTEQの課題や転職用のポートフォリオのために使う時間が撮れないと感じた。
2つ目の理由についてはネガティブな理由ではあるものの、やはり投資時間を分散させるために試験的に行ってみたいと思います。
トピックは記事のタイトル通り、transient
とafter(:build)
です。
RSpecを書く上でとっても役に立つと思ったので是非みなさんも使ってみてください。
テストの概要
早速本題に入らせていください。
RUNTEQの応用編に現在取り組んでいますが、その課題でRSpecを作成する課題があります。
どのようなページで検証するのかと言うと、記事(Article)について、著者がいる記事Aと著者がいない記事Bが記事一覧ページにて検証します。
検証する内容は下記の通りです。
記事Aの著者で検索をすると記事Aだけが表示される。
このページには検索窓がありそれが下記の画像のような感じ。
この著者のセレクトボックスで著者Aを選択肢検索すると記事Aだけが表示されるということです。
テストのシステムスペックファイル
よってシステムスペックのシナリオは以下の通りになります。
# spec/system/admin_article_searches_spec.rb describe '著者検索' do let(:admin_user) { create(:user, :admin) } let(:article_with_author_A) { create(:article, :with_author, author_name: '著者A') } let(:article_with_author_B) { create(:article, :with_author, author_name: '著者B') } before do article_with_author_A article_with_author_B login_as(admin_user) # ポイント1 visit admin_articles_path # ポイント2 end context "著者Aで検索される時" do before do within('.form-inline') do select article_with_author_A.author.name, from: 'q[author_id]' # ポイント3 click_button '検索' end end it '著者Aの記事のみが表示される' do # ポイント3 expect(page).to have_content(article_with_author_A.title), '著者Aの記事が表示されていません' expect(page).not_to have_content(article_with_author_B.title), '著者A以外の記事が表示されています' end end end
let
の部分の説明は今回の記事の肝ですので、後で説明します。最初にシナリオの流れだけざっくり説明します。
[ポイント1]
login_as(admin_user)
でまずは管理者画面にログインします。login_as
メソッドはモジュールで独自に定義してあります。
# spec/support/login_support.rb module LoginSupport def login_as(user) visit admin_login_identifier_path fill_in 'user_name', with: user.name click_button '次へ' expect(current_path).to eq admin_login_password_path fill_in 'user_password', with: 'password' click_button 'ログイン' end end
[ポイント2] Article(記事)モデルとAuthor(著者)モデルの関係性は多:1です。Authorが複数の記事を持つということです。
ですので、セレクトボックスから著者の名前を取り出す方法はarticle_with_author_A.author.name
となります。
[ポイント3] そして、検証部分では著者Aの記事だけが表示され、著者Bの記事は表示されないことを想定しています。
expect(page).to have_content(article_with_author_A.title), '著者Aの記事が表示されていません' expect(page).not_to have_content(article_with_author_B.title), '著者A以外の記事が表示されています'
問題なのはlet
の部分です。
admin_user
に関しては簡単です。create
の第二引数でtrait
を指定し、管理者ユーザーが作成されるように指定しています。UserモデルのFactoryBotの中身を見てみると、trait
で:admin
の記述があるのが分かります。
# spec/factories/users.rb FactoryBot.define do factory :user do ・ ・ trait :admin do sequence(:name) { |n| "writer-#{n}" } role { :admin } end ・ ・ end end
一方でarticle_with_author_A
とarticle_with_author_B
はどうでしょうか。
let(:article_with_author_A) { create(:article, :with_author, author_name: '著者A') } let(:article_with_author_B) { create(:article, :with_author, author_name: '著者B') }
create
の第三引数にauthor_name
の指定がありました。自分はRSpecにおけるcreateの第三引数自体が初見だったので少し混乱しました。
ArticleのFactoryBotでtraitが:with_author
の部分を見てみると見慣れない記述があります。
FactoryBot.define do factory :article do trait :with_author do transient do sequence(:author_name) { |n| "test_author_name_#{n}" } sequence(:author_slug) { |n| "test_author_slug_#{n}" } end after(:build) do |article, evaluator| article.author = build(:author, name: evaluator.author_name, slug: evaluator.author_slug) end end
これが自分にとって初となるtransient
とafter(:build)
の出会いでしたww
なんだこの記述ってすごく思ったんですけど、理解するのは意外と簡単なんです!
transient
まずtransientから説明していきます!
let
でtrait
を指定すると、同時にtrait
のブロック内に存在するtransient
内のの値が取得できます。例えば、下記のようにcreate
に第三引数を渡さずにFactoryBotを呼び出す記述を書くとします。
let(:article_with_test_author) { create(:article, :with_author) }
このFactoryBotが呼び出されるとtransient
内の中の値、つまりauthor_name
とauthor_slug
も呼び出されます。
試しに、binding.pry
で止めて値を確かめてみます。
transient do sequence(:author_name) { |n| "test_author_name_#{n}" } sequence(:author_slug) { |n| "test_author_slug_#{n}" } binding.pry # ここで止める! end
するとそれぞれの値は以下のようになっていました。
[1] pry(#<FactoryBot::Declaration::Implicit>)> author_name NameError: undefined local variable or method `author_name' for #<FactoryBot::Declaration::Implicit:0x00007f852fc39ba8> Did you mean? Pathname from (pry):5:in `__pry__' [2] pry(#<FactoryBot::Declaration::Implicit>)> author_slug NameError: undefined local variable or method `author_slug' for #<FactoryBot::Declaration::Implicit:0x00007f852fc39ba8> from (pry):6:in `__pry__'
ありゃりゃ、取得できていませんね。
ご安心ください。値自体はafter(:build)
の部分で確認することができます!
補足しておくと、let
のcreate
の第三引数であった:author_name
は"test_author_name_#{n}"
の値ではなく別の値を格納したい時に指定します。今回の場合は"著者A"、"著者B"といった具合ですね。
after(:build)
続いてafter(:build)の中を覗いてみましょう。
after(:build) do |article, evaluator| article.author = build(:author, name: evaluator.author_name, slug: evaluator.author_slug) end
evaluator
とは何なんでしょうか??
実はさっきのtrasient
内の値はこの中に格納されています。
[2] pry(#<FactoryBot::SyntaxRunner>)> evaluator.author_name => "test_author_name_1" [3] pry(#<FactoryBot::SyntaxRunner>)> evaluator.author_slug => "test_author_slug_1"
そしてこの値がarticle.author
というAuthorモデルの作成時に渡されています。
ブロック内の部分はとても単純ですよね。AuthorモデルのFactoryBotを作成しています。
build(:author, name: evaluator.author_name, slug: evaluator.author_slug)
だんだん見えて理解できてきましたでしょうか。
本来であれば、アソシエーション関係のあるFactoryBotの呼び出しをlet
のみで行う場合、let
を2回使う必要がありました。今回の場合、記事が1つとその記事の著者が1つです。
しかし、transient
を使用した場合、let
の1回の呼び出しで2つのFactoryBotを作成しています!
let(:article_with_author_A) { create(:article, :with_author, author_name: '著者A') } let(:article_with_author_B) { create(:article, :with_author, author_name: '著者B') }
これによりlet
をシステムスペックファイルに記述する量を減らしているのです。今回の場合、著者Aの記事だけでなく著者Bの記事も必要だったため、let
のみでFactoryBotを作成しようとすると、合計で4回も呼び出さなければなりません。すごい冗長ですよね、、、。
終わりに
今回の説明でtransient
とafter(:build)
の概要は何となく理解できたかなと思います。
たくさんのlet
の使用は冗長化につながりますので、1回のlet
の使用でアソシエーション関係にあるFactoryBotの作成は是非とも使ってみてください!
以上、以上、大ちゃんの駆け出し技術ブログでした!
【Rails】content_forとyieldで動的にタイトルを表示
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
今回は「Railsでcontent_forとyieldを使用して、タイトルを動的に表示させる方法」について紹介します!!
「動的とは?」となった人(自分もそうでした笑)もいるかもしれませんが、プログラミングでは高い頻度で出てくる概念です。その意味は 「状態や構成が状況に応じて変化したり、状況に合わせて選択できたりする柔軟性を持っていること。」
この記事で紹介するcontent_forとyieldを使った実装内容はどちらかといえば主に文の後ろ側、「状況に合わせて選択できたりする柔軟性」に当たります。
その柔軟性について詳しく見ていきましょう。
成果物の確認
以下が実装した成果物になります。
トップページのタイトル
アクセスページのタイトル
Webサイトがサイト内で複数のページを持つのは当然のことです。 例えば、家電量販店サイトにアクセスしたときに表示されるトップページ、お店への行き方を紹介するアクセスページといった具合です。 そして各ページでのタイトルはページ内容に則していることが普通です。
本記事の実装でも上の図で示したように各ページでサイト名を変えています。
- トップページでは「AAA 電機」(家電量販店の名前)
- アクセスページでは「Access | AAA 電機」(ページ内容| 家電量販店の名前)
※Googleでもタイトルは 「検索した内容 + "- Google 検索"」 の仕様となってるみたいですので確認してみてください。
実装方法を解説
まず、全てのビューのの共通レイアウトであるapplication.html.erbのheadタグ内にyield文を記載。
<title><%= page_title(yield(:title)) %></title>
yield(:title)ですが、()内の:titleは他のコードでも構いません。(:notice、:info、etc.) 後ほど記載するcontent_forでは()内のコードをここで記載したものと共通にする必要があります。 (ちなみに:titleはrubyの記法でシンボルというもの。ここでの理解はただの文字列でも問題ないです。)
ここで定義しているpage_titleメソッドはapp/helpers/application_helper.rbで定義しています。
module ApplicationHelper # 引数が渡されなかった場合は空文字をデフォルト値とする def page_title(page_title = '') base_title = 'AAA 電機' # 三項演算子 page_title.empty? ? base_title : page_title + " | " + base_title end end
page_titleの引数にはデフォルト値として空文字を指定しています。 これにより引数が渡されなかったとしても空文字(””)がpage_titleに代入され、メソッド内の演算で使用されます。
そして、三項演算子によって、引数が渡されていなければ「AAA 電機(base_title)」、引数が渡されていれば「引数の文字(page_title) | AAA 電機」を返します。
三項演算子ですがここでは詳しく説明しませんが、一言で説明すると「if文を一行で記載する演算子」です。つまり、上の文はif文と同じ意味になります。(最初見たときはこれがifと同じ意味とは思いませんでした、、。)
条件式 ? trueの処理 : falseの処理
念のためif文で書き換えたものを記載しておくので、三項演算子をスキップしたい人はこちらを見て意味を確認してください。
# page_title.empty? ? base_title : page_title + " | " + base_title をif文で書き換え if page_title.empty? base_title else page_title + " | " + base_title end
話が少し脱線しましたが、このpage_titleメソッドを定義することでapplication.html.erbで使用することが可能になります。
では、page_titleメソッドの引数であるyiield(:title)は果たしてどのように渡されるのかというと、 そう、content_forメソッドの出番です。
しかもその実装方法はとても簡単! 各ページにcontent_forメソッドを使用し、第一引数に共通のシンボル、第二引数に渡したい文字列を記載するだけ!
成果物のように実装する場合、
<% content_for(:title, 'Access' ) %>
これにより、文字列’Access’が共通レイアウトのページにあるyeildに渡され、page_titleメソッドの引数が’Access’となり、成果物のようにタイトルが「「Access | AAA 電機」(ページ内容| 家電量販店の名前)」となります。
逆に、トップページとしてルーティングされているページのビューに何も記載しなければ、そのページのタイトルは「AAA 電機」となります。
動的であるメリット
冒頭で動的(状況に合わせて選択できたりする柔軟性)の意味を説明しましたが、具体的にどのような柔軟性が今回の実装にあるのでしょう?
例えば、AAA 電機が社名変更でBBB 電機となったとしましょう。 当然タイトルも新しい会社名に変更しなければなりません。 どのようにコードに変更を加えるべきでしょうか?
答えは"ヘルパーメソッドのbase_titleに入れる文字を変える"だけです。
module ApplicationHelper def page_title(page_title = '') # 変更箇所 ----------- base_title = 'BBB 電機' # -------------------- page_title.empty? ? base_title : page_title + " | " + base_title end end
本当にたったこれだけ! これだけでcontent_forが使われる他の全ページのタイトルが「AAA 電機」から「BBB 電機」に変わります。 メンテナンスが驚くほどに簡単になるのです。
逆に、content_forとyieldを使わない実装方法を考えてみましょう。 各ページにはそれぞれのページのタイトルが静的に表示されています。
<title>AAA 電機</title>
<title>Access | AAA 電機</title>
そして先ほどの例のように会社名が変更になったとしましょう。
さて、変更を加える箇所はいくつあるでしょうか。 Accessページだけでなく、他のページにも+ | AAA 電機と実装していた場合、その数だけ修正をしなければなりません。 content_forとyieldを使った実装なら一箇所で済むのに、です。
これが動的であることの威力です。 状況に合わせて対応を選択できる柔軟性です。
最後に
今回紹介した例はとても簡単な実装方法になります。 しかし、他にも幅広い方法でcontent_forとyieldは活用できるので、是非色々試してみてください! 自分も試します!
この記事が少しでも皆様の学習の役に立てば幸いです。
以上、大ちゃんの駆け出し技術ブログでした!
【Rails】独自のRakeタスクの定期的実行
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
先日にRakeタスクに関する記事を書きました。
記事ではデフォルトで実装されているRakeタスクの表示方法[ $ rake -T
]や、開発環境を表示するRakeタスク[ $ rake about
]の中身を実際に見てみました。
Rakeタスクの概要はざっくりと理解できたかなと思います。
このざっくりとした理解をより凝固なものにするために、今回は前回学んだRakeタスクを独自に作成する方法について発信します。
実際にRakeタスクを自分で実装することでより理解が深まると思います。
それでは本記事の内容に入っていきます。
※なお、今回使うアプリケーションは前回と同じファイルで行います(ファイル名が「scaffold_rake
」とトピックが同じであるため)
Rakeタスクファイルの作成
それではRakeタスクファイルを作成してみます。
作成方法はシンプルで以下のようになります。
$ rails g task ファイル名
今回はファイル名をsample_task
としてコマンドを実行しましょう。
$ rails g task sample_task create lib/tasks/sample_task.rake
パス: lib/tasks/
配下に新規ファイルが作成されました。
.rake
とあるようにRakeファイルであることがわかります。
このように、Rakeタスクファイルはlib/tasks/
配下に置かれていきます。
作成時のファイル状態は以下のようになります。
# lib/tasks/sample_task.rake namespace :sample_task do end
ここで簡単なRakeタスクの作成に入る前に、Rakeタスクファイルの構成について再度確認します。
name_space :ファイル名 do desc "Rakeタスクの処理内容の説明" task Rakeタスク名: :environment do 処理内容(Rubyで記述) end end
desc内にはタスクの説明を記述するんでしたよね。rake about
なら "List versions of all Rails frameworks and the environment"といった具合ですね。
そしてその下にtask
を使ってRakeタスク名を定義します。
:environment
の記載は必須でしたね。
そしてdo~end
のブロック内に実行したい処理をRubyで記述します。
それではsample_task.rake
内に記載していきましょう。
実行する処理は「コンソールに"Hello"を出力する」ことにしましょう。
# lib/tasks/sample_task.rake namespace :sample_task do desc "'Hello'をコンソールに出力" task output_hello: :environment do puts 'Hello' end end
ここでrake -Tを使ってRakeタスク一覧をターミナル上に表示しましょう上で定義したRakeタスクが表示されると思います。
$ rake -T ・ ・ rake restart # Restart app by touching tmp/restart.txt rake sample_task:output_hello # 'Hello'をコンソールに出力 rake secret # Generate a cryptographically secure secret key (this is typically used to generate a secret for cook... ・ ・
実はタスクの実装自体も既に完了しております。
$ rake sample_task:output_hello
を実行してみましょう。
$ rake sample_task:output_hello Hello
文字列「Hello」を出力できたかと思います。
Rakeタスクを簡単に実装できることが分かったのではないでしょうか。
cronとwhenever
独自のRakeタスクのことはある程度理解しましたが、用途についてはまだ説明していません。
色々とあると思いますが、今回はRUNTEQで学習した定期的に登録したRakeタスク(ジョブ)を実行する方法について発信します。
どのように定期タスクを登録するのかというとcron
に登録します。
cron
について下記サイトでは以下のように説明しています。
cronとは、多くのUNIX系OSで標準的に利用される常駐プログラム(デーモン)の一種で、利用者の設定したスケジュールに従って指定されたプログラムを定期的に起動してくれるもの。
引用元: https://e-words.jp/w/cron.html
つまり UnixOS上にある定期的に何かのプログラムを自動で実行する設定のことを指します。
そして、このcron
の設定にRakeタスクを登録するために使うgemがあります。それがwhenever
です。
whenever
はRakeタスクがどれぐらいの頻度でどのRakeタスクを実行するかを設定することができます。つまり、whenever
で作成したファイルにRakeタスクを指定し、それをcron
に登録するということです。cron
上では頻度やRake
タスク自体の変更はできません。
では実際にwheneverからインストールしていきましょう。
# Gemfile gem 'whenever', require: false
$ bundle install Fetching whenever 1.0.0 Installing whenever 1.0.0
wheneverを起動するために$ bundle exec wheneverize .
をターミナル上で実行してください。
$ bundle exec wheneverize .
[add] writing `./config/schedule.rb'
[done] wheneverized!
するとconfig/
配下にschedule.rb
というファイルが作成されました。これが先ほど説明した、Rakeタスクがどれぐらいの頻度でどのRakeタスクを実行するかを記述するファイルになります。
中身を見てみましょう!!!
# Use this file to easily define all of your cron jobs. # # It's helpful, but not entirely necessary to understand cron before proceeding. # http://en.wikipedia.org/wiki/Cron # Example: # # set :output, "/path/to/my/cron_log.log" # # every 2.hours do # command "/usr/bin/some_great_command" # runner "MyModel.some_method" # rake "some:great:rake:task" # end # # every 4.days do # runner "AnotherModel.prune_old_records" # end # Learn more: http://github.com/javan/whenever
全てコメントアウトされたファイルが作成されています。しかしながら、何を設定しているのかが見て理解できるファイル構造になっています。以下の箇所を参照して説明します。
# every 2.hours do # command "/usr/bin/some_great_command" # runner "MyModel.some_method" # rake "some:great:rake:task" # end
every 2.hours
とありますが、これは言うまでもなくタスクの実行頻度でしょう。
そして、ブロック内にcommand
、runner
、rake
の3つのメソッドがあります。実はこのファイルでは、Rakeタスクだけ登録できると言うわけではなく、ターミナルのコマンドや定義されたメソッドも実行することができます。
command
は/usr/bin/some_great_command
からわかる通り、ターミナル上で実行するタスクを登録runner
はMyModel.some_method
からわかる通り、クラスメソッドを登録rake
はsome:great:rake:task
からわかる通り、Rakeタスクを登録
今回使用するのはもちろんrake
です!
今回は先ほど作成したRakeタスクoutput_hello
を毎時実行されるようにします。
# config/schedule.rb every :hour do rake "sample_task:output_hello" end
それではいよいよcron
に登録してみましょう。
登録する前に、現在cronに登録されているタスクが何もないことを確認しましょう。cron
に登録されているタスクを確認するためには、$ crontab -l
をターミナル上で実行します。
$ crontab -l
何も表示されていないことを確認したら、次は以下のコマンドで設定ファイルをcron
に反映します。
$ bundle exec whenever --update-crontab
[write] crontab file updated
再度$ crontab -l
をターミナル上で実行します。
$ crontab -l # Begin Whenever generated tasks for: /Users/sakidendaiki/Downloads/RUNTEQ/for_blog/scaffold_rake/config/schedule.rb at: 2021-02-04 14:39:04 +0900 0 * * * * /bin/bash -l -c 'cd /Users/sakidendaiki/Downloads/RUNTEQ/for_blog/scaffold_rake && RAILS_ENV=production bundle exec rake sample_task:output_hello --silent' # End Whenever generated tasks for: /Users/sakidendaiki/Downloads/RUNTEQ/for_blog/scaffold_rake/config/schedule.rb at: 2021-02-04 14:39:04 +0900
これで毎時登録したRakeタスクが実行されるようになりました。(サーバーが起動している時にのみ実行されます。つまり、$ rails s
を実行してから1時間待つ必要がありますので、「Hello」をターミナル上で確認したい方は、1時間待つかww、設定ファイルを:hour
から:minute
に変えて、再度cron
に登録してください。)
終わりに
今回の内容はRUNTEQで学習したことの簡単なアウトプットです。 RUNTEQではもっと実用的な実装をしています。本当このスクールレベル高いなと思いながら毎日を生きています。
今回はただターミナル上に「Hello」を出力するだけのメソッドでしたが、本来であれば定期的にメールを送信するメソッドだったり、定期的にDBを更新するメソッドが使われます。 もしそんなメソッドを使う機会がアプリ作成時にあれば是非今回紹介した独自のRakeタスクの定期的実行方法を参考にしてみてください。
以上、大ちゃんの駆け出し技術ブログでした!
【scope】EverydayRailsで初学者が理解できなそうなところ
なんだこの記述は、、!?
こんにちは!大ちゃんの駆け出し技術ブログです。
RSpec学習には以下の有名教材があります。 Everyday Rails - RSpec による Rails テスト入門 テスト駆動開発の習得に向けた実践的アプローチ
Aaron Sumnerさんという方が書いた本を日本語訳されています。 翻訳した方々の中にはあの「プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで」を書かれた伊藤淳一もいらっしゃいます。
自分もプログラミングスクールの課題のために読み進めていますが、かなり序盤に出てくる以下の記述に驚きました。
class Note < ApplicationRecord scope :search, ->(term) { where("LOWER(message) LIKE ?", "%#{term.downcase}%") } end
うわ、なんだこの見たこともない記載方法は、、!?。scope? whereの中身がなんか変!
この文章を初学者の人は理解できるのかなと思いました。 自分は初見では全くわからずぐぐりましたね。
ということで今回は上記の文をまるっと全部説明したいと思います!
本記事を読む読者のレベル
本記事は「Everyday Rails」を読んでいる読者を想定しています。 したがって、本記事の対象者のレベルは「Everyday Rails」に書かれている読者レベルと同じです。 つまり、
- Rails で使われているサーバーサイドの Model-View-Controller
- gem の依存関係を管理する Bundler
- Rails コマンドの実行方法
- リポジトリのブランチを切り替えられるぐらいの Git 知識
なお、SQLについての知識、特に、where、ワイルドカード、LIKEについて知識があるとより理解が簡単になると思います。
使用方法の説明
具体的な説明に入る前に、全体像を掴むために、今回説明するものがいったいどのようにして使われるのか、どんな機能を持つのか見ておきましょう。
上記のscope〜
を記述することで以下のような使われ方をします。
@notes = Note.search("first")
この形どこかで見たことありませんか? そう、コントローラでDBからモデルの対象のレコードを取得する方法です。 これはクラスに対して実行するクラスメソッドというものですね。
@users = User.all @user = User.find(params[:id])
Note
はモデルです。
ファイルを見てみるとtext
型でmessage
カラムがあります。
create_table "notes", force: :cascade do |t| t.text "message" . . . end
具体的にNote.search("first")
が何をしているのかというと、searchとあるように、Noteモデルのmessageカラムに文字列"first"を含んだ文字列があるかどうか検索し、あればそのモデルを取得します。
例えば、DBに以下の3つのnoteが登録されているとします。
- note1・・・messageカラムが"first"
- note2・・・messageカラムが"first name"
- note3・・・messageカラムが"second"
そして、@notes = Note.search("first")
とした場合、インスタンス変数@notes
にはnote1とnote2が格納されますが、note3は文字列"first"を含まないので取得されません。
ここまで全体像を説明してきましたが、ポイントは2つです。
① scopeを使うことでクラスメソッドが使えるようになる
② whereの箇所では、指定した文字列を含む文字列がmessageカラムにあるかを検索する機能がある
では①、②の順番で詳しく説明していきます。
scopeについて
まずはscopeの部分。
class Note < ApplicationRecord # ----------------------- scope :search, ->(term) { # ----------------------- where("LOWER(message) LIKE ?", "%#{term.downcase}%") } end
説明したように、上記はモデル内で定義し、コントローラで使われます。
言い換えれば、モデル内で定義をしなくても、コントローラで使用することが可能です。
term = "first" @notes = Note.where("LOWER(message) LIKE ?", "%#{term.downcase}%")
ですが、見てわかる通り、こちら長くないですか?
まず、term = "first"
のように検索したい文字列変数として定義しなければなりません。
Note.where
以降も長くて理解しづらいかと思います。
そこでモデルに定義することで上の文が、あらスッキリ、生まれ変わります。
@notes = Note.search("first")
文字列の定義をせず引数として文字列を指定!
where
メソッド自体もモデルに定義されているので長い文をコントローラに記載せずに使用!
可読性がすごくあがったのをお分かりいただけたかと思います。
search("first")
で何をしているのかが直感的にも理解できますしね。
このようにメソッドとして切り出すことがscopeを使うメリットです。
ここまで説明してscope :search, ->(term)
の部分に関して以下のことも 理解できたのではないのでしょうか。
① searchの部分はクラスメソッドを定義していること
② termの部分はクラスメソッドの引数であること
本来であれば、search(term)
みたいな記述だと理解しやすいのですが、scope
独特の記述方法によって、引数が離れてしまっていますし、searchの箇所も:search
とシンボルで記載しなければなりません。
ここまでわかればもうscope :search, ->(term)
について疑問はないでしょう。
総括すると、 本来であればDBからのレコード取得時にすごく長い文章を書かなければならない場合、scopeでクラスメソッドを定義し、短文でコントローラで使用することができます。
where("LOWER(message) LIKE ?", "%#{term.downcase}%")
そしてとっても難解なこの文章はなんなんでしょう。
class Note < ApplicationRecord scope :search, ->(term) { # ---------------------------------------------------- where("LOWER(message) LIKE ?", "%#{term.downcase}%") # ---------------------------------------------------- } end
3つに分けて説明します。
①whereメソッドについて
②"LOWER(message) LIKE ?"について
③"%#{term.downcase}%"について
whereメソッドについて
まず、whereメソッド
から簡単に説明します。
where(カラム名: 値)
where
メソッドはfind
、find_by
同様にDBからレコードを取得します。
決定的に違うのはfind
、find_by
は1つのレコードを取得するのに対し、where
メソッドは複数のレコードを取得するのです。
例えば、1番最初に紹介した例と同様にDBに以下の3つのnoteが登録されているとします。
- note1・・・messageカラムが"first"
- note2・・・messageカラムが"first name"
- note3・・・messageカラムが"second"
そして、find_by
で文字列"first"を含むmessage
カラムがあるレコードを取得する時は以下のようになると思います。
Note.find_by(message: "first")
ですが、当然これではnote1, note2は同時に取得できません。 理由は簡単。 find_byはレコードを1つしか取得できないからです。
よってwhere
を使って複数取得します。
Note.where(message: "first")
しかしながら、"LOWER(message) LIKE ?"
は本来where
の第一引数であるカラム名とは少し違うようです。
"LOWER(message) LIKE ?"について
まずwhere
の第一引数の"LOWER(message) LIKE ?"
がそもそも何なのかですが、これはSQLの文章です。
まずLOWER
ですがこれはSQLの関数になります。
意味は「引数の文字列を全て小文字にする」、です。
LOWER("FIRST") --> "first"
LOWER
の引数はmessage
となっています。
したがって、message
カラムにある文字列を全て小文字にしているということになります。
なぜ小文字にする必要があるのか?理由は後ほど説明します。
次にLIKE
の部分ですが、これはLIKE演算子の「あいまい検索」と言われるものです。
例えば、DB上の都道府県のテーブルから"島"という文字列を含んだ件名を取得したいとします。 そのような場合SQLの文章は以下のようになります。
SELECT 都道府県名 FROM 都道府県テーブル WHERE 都道府県名 LIKE "%島%"
LIKE
以降にある文字列がLIKE
の手前の文字列に含まれているかを判別しています。
ちなみに%
はワイルドカードと呼ばれるもので、0個以上の任意の文字を表します。
よって、%島%
は島を含んだ文字列となります。
LIKE
の手前はLOWER(message)
です。では後ろは?
後ろは?
ですね笑
?
って何と思われるかもしれませんが、じつは?
には第二引数である"%#{term.downcase}%"
が入ります。
実はこの?
ですがよくrailsでサーバを起動している際に頻出しています。
SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
“users”.“id” = ? の?は[“id”, 1]の1の値が代入される LIMIT ? の?には[“LIMIT”, 1]の1の値が代入される
上記の"LOWER(message) LIKE ?"
の?
は代入される値をSQLの後ろで指定している方法と同じ考え方です。
つまり、where("LOWER(message) LIKE ?", "%#{term.downcase}%")
は以下のSQLの条件に当てはまるレコードを取得しているのです。
WHERE LOWER(message) LIKE "%#{term.downcase}%"
"%#{term.downcase}%"について
そして、最後に"%#{term.downcase}%"
の部分です。
両端の%
は先ほど説明したように、0個以上の任意の文字を表します。
#{term.downcase}
は式展開で、引数のterm
に格納された文字列をメソッドであるdowncase
で全て小文字にしています。
term = "FIRST" "#{term.downcase}" --> # "first"
ここで先ほど紹介したLOWER(message)
でなぜmessage
カラムの値を全て小文字にしているのかの答えにつながります。
引数のtermが"First"、messageカラムの値が"FIRST"の時、それは文字列が一致しないということになり、結果そのmessageカラムのレコードは取得されません。 しかし、完全に一致する検索より、小文字大文字を区別しないほうが使い勝手が良さそうです。 なので、両方を小文字にすることで大文字と小文字を区別しないようにしているのです。
よって"%#{term.downcase}%"
は「termを含んだ文字列」となります。
結論: scopeとwhere("LOWER(message) LIKE ?", "%#{term.downcase}%")とは
長い文章を読んでいただきありがとうございました。
最後に結論として今まで記述したことをまとめて終わりとします。
class Note < ApplicationRecord scope :search, ->(term) { where("LOWER(message) LIKE ?", "%#{term.downcase}%") } end
scope
の部分の説明は再掲します。
① searchの部分はクラスメソッドを定義していること ② termの部分はクラスメソッドの引数であること
よって、コントローラで以下のように使われることができます。
@notes = Note.search("first")
そしてsearch
の内容であるwhere("LOWER(message) LIKE ?", "%#{term.downcase}%")
ですが、意味は
モデルのmessageカラムにtermを含んだ文字列があるかどうか検索し、あればそのモデルを取得
という意味になります。
終わりに
scope
とwhere
メソッドを説明しました。
この2つは今回のようにモデルで使用されることが多く、そのため2つセットで使われる場合が多い気がしています。
見た目は自分の知っているRailsの記述ではなかったので理解に苦労しましたが、仕組みを理解できれば簡単です。 初学者のうちから逃げずにしっかりと理解しておきましょう!
以上、大ちゃんの駆け出し技術ブログでした!
【Rails】Action Mailer
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
今回はRails標準機能であるAction Mailerという機能について紹介します!名前から類推できる通りメールの送信機能になります。
※Action Mailboxという別の標準機能もありますが、こちらはメールの受信機能です。
導入方法
今回はアプリケーションからユーザーに対しアカウントアクティベーションのメールを送ることを想定してます。アカウントアクティベーションとはよくある本人確認方法の1つですね。Webサイトなどでアカウント登録時に登録したメールに対し、本人確認メールが送られるシチュエーションです。
では早速実装していきましょう。
いつものようにscafflodを使用してアプリの雛形を作成します。
$ rails new action_mailer_sample
$ cd action_mailer_sample
$ rails g scaffold user name:string email:string
$ rails db:create
$ rails db:migrate
次にmailerのファイルを作成します。
$ rails g mailer UserMailer create app/mailers/user_mailer.rb invoke erb create app/views/user_mailer invoke test_unit create test/mailers/user_mailer_test.rb create test/mailers/previews/user_mailer_preview.rb
作成されたapp/mailers/user_mailer.rb
に対してactivation_email
メソッドを定義します。
# app/mailers/user_mailer.rb class UserMailer < ApplicationMailer def activation_email @user = params[:user] @url = 'http://example.com/login' mail(to: @user.email, subject: 'アカウントのアクティベーションメール') end end
インスタンス変数である@user
と@url
を定義しています。
このインスタンス変数はRailsのコントローラと同様に、ビューに渡されるようになっています。このビューがメール本文自体を表示するようになっています。
mail(to: @user.email, subject: 'アカウントのアクティベーションメール')
は見て理解できると思います。to:
の部分で宛先を、subject:
の部分で件名を指定しています。
次はメール本文にあたるビューを作成します。メソッド名に対応したerbファイルを作成する必要があるため、ファイル名は「activation_email~
」となります。
しかし、先ほど実行したコマンドではビューのフォルダであるapp/views/user_mailer
のみ作成しています。従って、ビュー自体は雛形が作成されていないので手動でファイルを作成し、本文も手動で実装します。テキスト形式とHTML形式の2つのフォーマットで表示させるために、activation_email.html.erb
とactivation_email.text.erb
を作成します。
※メールの閲覧方法はアプリ上やwebブラウザ上など表示するフォーマットが異なる場合があるため、
# app/views/user_mailer/activation_email.html.erb
<p><%= @user.name %>様</p>
<p>この度は、「○○○○○○」にお申し込み頂きまして誠にありがとうございます。</p>
<p>お申し込み頂きましたアカウント情報は以下となります。</p>
<p>ログインID:○○○○○</p>
<p>パスワード:個人情報のため表示を伏せています</p>
<p>下記URLをクリックしてログインしてください。</p>
<p><%= @url %></p>
# app/views/user_mailer/activation_email.text.erb
<%= @user.name %>様
この度は、「○○○○○○」にお申し込み頂きまして誠にありがとうございます。
お申し込み頂きましたアカウント情報は以下となります。
ログインID:○○○○○
パスワード:個人情報のため表示を伏せています
下記URLをクリックしてログインしてください。
<%= @url %>
erbファイルなので<%= %>
の記法が使用できます。
ここでプレビュー機能を使ってメール本文を確認してみたいと思います。
プレビュー機能を使うために、コンソール上で前もって1つのユーザーを登録しておきましょう。
$ rails c irb(main):001:0> User.create(name: "dai-chan", email: "daichan@example.com") (0.1ms) begin transaction User Create (1.3ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "dai-chan"], ["email", "daichan@example.com"], ["created_at", "2021-02-02 10:58:11.562336"], ["updated_at", "2021-02-02 10:58:11.562336"]] (0.9ms) commit transaction => #<User id: 1, name: "dai-chan", email: "daichan@example.com", created_at: "2021-02-02 10:58:11", updated_at: "2021-02-02 10:58:11">
プレビュー機能を使うためにはUserMailer作成時に作成されたtest/mailers/previews/user_mailer_preview.rb
にメソッドを追記します。
class UserMailerPreview < ActionMailer::Preview def activation_email UserMailer.with(user: User.first).activation_email end end
rails s
を実行して[http://localhost:3000/rails/mailers/user_mailer/activation_email.html
]にアクセスしましょう。すると下の画像のようなプレビューを見ることができると思います。こちらでどのような見た目で送信されているのかが確認できますね。
最後にUsersコントローラのユーザー作成アクションであるcreateアクションで、ユーザー作成時にメールが送信されるように記述します。
def create @user = User.new(user_params) respond_to do |format| if @user.save UserMailer.with(to: @user.email, name: @user.name).activation_email.deliver_now # 追記 format.html { redirect_to @user, notice: "User was successfully created." } format.json { render :show, status: :created, location: @user } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @user.errors, status: :unprocessable_entity } end end end
これでユーザー作成時にメールを送信するように実装できました。
終わりに
ActionMailerなどのメール機能は他にもたくさんあるそうです。 正直プログラミング言語は勉強しきれないので、他にもたくさんあるそうですとしか言っていませんが、、、、
最近は技術ブログ多めなので、技術ブログだけでなく、個人的な体験談も近日アップします!
以上、大ちゃんの駆け出し技術ブログでした!
【Rails】Rakeタスク
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
Rubyを学習していると"rake"という言葉に遭遇すると思います。
例えば $ rake routes
は $ rails routes
と同じようにルーティングをターミナル表示してくれます。
$ rake routes Prefix Verb URI Pattern Controller#Action root GET / users#index users GET /users(.:format) users#index POST /users(.:format) ・ ・ 略
そもそもRakeって何なんでしょうか?
Rubyを勉強し始めてなんとなくルーティングを表示できるという理解で留めていたので、今回はRakeについてまとめます。
Rakeとは
Railsドキュメントでは以下のようにて説明しています。
短いし説明が全然されていないのであんまり理解できませんでした、、、
Rails5以降ではrailsコマンドでrakeコマンドを呼び出せる仕様になっていることから、現在rakeコマンドはrailsコマンドの一部になっているという理解で良さそうです。よって$ rails routes
も元々はrakeコマンドの機能だったということです。しかしもっと説明が欲しいですね笑
次にTechAcademyマガジンの説明を引用します。
Linux環境でにおけるビルドツールとしてmakeがありますが、実はRubyにおいてもビルドツールがあります。それがRakeです。Rakeを使うとRakefileに一連の処理として定義されたタスクを実行することができます。
ビルドというのはプログラミング言語において、テキストファイルに従った処理を実行することです。わかりやすくいうとJavaなどがコンパイルなどもビルドの一部に該当します。
このテキストファイルにを読み込んで処理をすることは、アプリケーションの複雑化に伴い人間で管理することが難しくなりました。例えば、ファイルを読み込む順序の工数が膨大になために、手動でファイルを実行すると順番を間違えたりしてしまう恐れがあります。そこで、テキストファイルを自動で読み込むツールが必要となり、そのツールがビルドツールにあたります。
Linux環境ではmakeというビルドツールがありますが、Rubyでも同様にビルドツールがあります。それがRakeということになります。Makeの頭文字をRに変更したということが名前の由来っぽいですね。
Rakeタスク
Rakeがアプリのファイルを自動で読み込んでくれるツールであることは理解できました。
しかし、実際どのような処理を自動でしてくれているのかがわからないですね、、、
実はRakeが自動で行ってくれている処理(これをRakeタスクといいます)はターミナル上で確認することができます。
まず、簡単なアプリを作成します。
$ cd 任意のディレクトリ
$ rails new scaffold_rake
$ cd scaffold_rake
$ rails db:create
$ rails generate scaffold user name:string email:string
$ rails db:migrate
コマンドライン上で以下のコマンドを打ち込んでください。
$ rake -T
たくさんの文字がターミナル上に表示されたかと思います。これらは全て自動でファイルを読み込み処理を行なってくれるRakeタスクです。
rake about # List versions of all Rails frameworks and the environment rake action_mailbox:ingress:exim # Relay an inbound email from Exim to Action Mailbox (URL and INGRESS_PASSWORD required) rake action_mailbox:ingress:postfix # Relay an inbound email from Postfix to Action Mailbox (URL and INGRESS_PASSWORD required) rake action_mailbox:ingress:qmail # Relay an inbound email from Qmail to Action Mailbox (URL and INGRESS_PASSWORD required) rake action_mailbox:install # Copy over the migration rake action_text:install # Copy over the migration, stylesheet, and JavaScript files rake active_storage:install # Copy over the migration needed to the application rake app:template # Applies the template supplied by LOCATION=(/path/to/template) or URL rake app:update # Update configs and some other initially generated files (or use just update:configs or update:bin) rake assets:clean[keep] # Remove old compiled assets rake assets:clobber # Remove compiled assets ・ ・ ・
次にrakeタスクの定義場所を見てみましょう。
今回はrake about
の定義場所を覗きにいきましょう。rake about
は説明部分にある通り、「すべてのRailsフレームワークと環境のバージョンを一覧表示する」処理を行います。
$ rake about About your application's environment Rails version 6.0.3.4 Ruby version ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19] RubyGems version 3.0.3 Rack version 2.2.3 ・ ・ ・
.gitignore
によってエディタで検索できない場合があります。今回に限り.gitignore
の/.bundle
をコメントアウトしましょう。
※通常.gitignore
をコメントアウトすることはありません。今回はRakeタスクの中身を見るために仕方なく行なっています。
.gitignore
# Ignore bundler config. # /.bundle
それではrake about
のファイルを覗いてみましょう。文字検索する場合、検索は「rake about」ではなく、$ rake -T
で表示したrake about
の説明部分にあたる「List versions of~~~」で検索してみてください。
すると以下のファイルで定義されていることがわかりました。
vendor/bundle/ruby/2.6.0/gems/railties-6.0.3.4/lib/rails/tasks/misc.rake
desc "List versions of all Rails frameworks and the environment" task about: :environment do puts Rails::Info end
このファイルから定義箇所の構成はこのようになっています。
desc "Rakeタスクの処理内容の説明" task Rakeタスク名: :environment do 処理内容(Rubyで記述) end
desc
はおそらくですがdescriptionの略でしょうか。処理の説明を記述しています。$ rake -T
で実行すると説明部分が# の後ろに表示されます。Rakeタスク名も表示されていますね。
rake about # List versions of all Rails frameworks and the environment
:environment
の部分ですが、Qiitaの記事で詳しく説明されています。説明する量が多いため、詳しく知りたい方は記事を参照してください。
記事が難しいと思った方のために雑に説明すると、:environment
は必ずrakeタスクの定義では記載します。もし記載しないとエラーが起きてしまいます。試しに削除してみましょう。
desc "List versions of all Rails frameworks and the environment" task about: do puts Rails::Info end
ターミナル上実行すると、SyntaxError
、つまり記述ミスのエラーが出ます。
$ rake about rails aborted! SyntaxError: /Users/sakidendaiki/Downloads/RUNTEQ/for_blog/scaffold_rake/vendor/bundle/ruby/2.6.0/gems/railties-6.0.3.4/lib/rails/tasks/misc.rake:10: syntax error, unexpected do (for block) task about: do
※削除した部分は必ず元に戻しておいてください。
また、railsコマンドでrakeを呼び出せるので、$ rails about
でも同じ処理ができます。
$ rails about About your application's environment Rails version 6.0.3.4 Ruby version ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19] RubyGems version 3.0.3 Rack version 2.2.3 ・ ・ ・
終わりに
以上、Rakeについて説明させていただきました。
今回説明したrake about
以外にもRakeタスクが定義されています。
コマンドを実行したり、ファイルを覗いてみたり、いろいろ調べてみてください。
次回の記事ではRakeタスクを自分で定義する方法について発信します!
以上、大ちゃんの駆け出し技術ブログでした!
参考文献
【Gem】パンくずリスト "gretel"
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
本日はパンくずリスト gretel
というRailsのgemについて紹介したいと思います!
名前がパンくず!?読み方は”グレてる”!?
本当に読み方が印象的だなと思いますwww
ですが、こちらのパンくずリストは皆さんもウェブサイトでみたことがあるものだと思います! では説明をしていきます!
gretelの概要
gemのgretel
は公式のREADMEで以下のように説明されています。
Gretel is a Ruby on Rails plugin that makes it easy yet flexible to create breadcrumbs. It is based around the idea that breadcrumbs are a concern of the view, so you define a set of breadcrumbs in config/breadcrumbs.rb (or multiple files; see below) and specify in the view which breadcrumb to use. Gretel also supports semantic breadcrumbs (those used in Google results).
これを日本語訳すると以下のようになります!(翻訳の仕事をしたことはないので多めにみていただけると幸いですmm)
GretelはRuby on Railsのプラグインで、パンくずリストの作成を簡単かつ柔軟に行うことができます。パンくずリストはビューに基づいているので、config/breadcrumbs.rb (または複数のファイル。以下を参照) でパンくずリストのセットを定義し、どのパンくずリストを使うかをビューで指定します。Gretelはセマンティックパンくずリスト (Google の結果で使用されるパンくずリスト) もサポートしています。
めちゃくちゃ分かりにくい訳でごめんなさいm( _ _ )m
最後の文の訳にのみ触れておきます。なぜなら、パンくずリストが何であるかがわかるからです。
Gretelはセマンティックパンくずリスト (Google の結果で使用されるパンくずリスト) もサポートしています。
この文で注目して欲しい部分は「Google の結果で使用されるパンくずリスト」の部分です。
実はパンくずリストはGoogleの検索結果のページにもあります。例えば、「Amazon Prime」と検索してみましょう。
どれがパンくずリストなのか分かりますか?実はこちらがパンくずリストです。
Webページのタイトルの部分の上にある小さい文字の部分です。 Google検索すると必ず表示されるので、絶対に見たことがあるはずです。
このリストは何を意味しているのかは直感的にわかるでしょう。 回答は左のページ(www.amazon.co.jp)は右のページ(Prime-Video)の親ページということになります。 (Amazon Primeは元々あったAmazonのHomeページ(親ページ)に付け足されたページ(子ページ)ということですね)
このようにパンくずリストはページごとの親子関係をリストで表示するという機能を持っています。
gretelのインストール手順
では実際にRailsでgretel
をインストールします。
なお、本記事で取り扱うサンプルアプリのRails、Rubyのバージョンは以下になります。
ではさっそくアプリを作成するディレクトリに移り、$ Rails new
をつかって初期アプリを作成しましょう!
本記事ではscaffold
を使用して雛形のアプリを作成しましょう。
$ cd 任意のディレクトリ $ rails new scaffold_gretel $ cd scaffold_gretel $ rails db:create $ rails generate scaffold user name:string email:string $ rails db:migrate
今回はroot_pathをユーザー一覧画面とします。
そのため、config/routes.rb
を編集します。
Rails.application.routes.draw do root to: 'users#index' resources :users end
念のため、サーバーを起動しルートパスがユーザー一覧画面となっているか確認してください!
次にGemfile
にgretel
を追加し、bundle install
します。
Gemfile
gem 'gretel'
ターミナル
$ bundle install ・ ・ Fetching gretel 4.2.0 Installing gretel 4.2.0 ・ ・ Bundle complete!
次にgenerateコマンドでインストールしたら、config/breadcrumbs.rb
が作成されます。
$ rails generate gretel:install create config/breadcrumbs.rb
最初のインストール手順は終了です。
実装方法
いよいよ実装に移ります!
まず、先程作成したの中身を見てみましょう!
config/breadcrumbs.rb
crumb :root do link "Home", root_path end # crumb :projects do # link "Projects", projects_path # end # crumb :project do |project| # link project.name, project_path(project) # parent :projects # end # crumb :project_issues do |project| # link "Issues", project_issues_path(project) # parent :project, project # end # crumb :issue do |issue| # link issue.title, issue_path(issue) # parent :project_issues, issue.project # end # If you want to split your breadcrumbs configuration over multiple files, you # can create a folder named `config/breadcrumbs` and put your configuration # files there. All *.rb files (e.g. `frontend.rb` or `products.rb`) in that # folder are loaded and reloaded automatically when you change them, just like # this file (`config/breadcrumbs.rb`).
上のファイルに追記します。
crumb :root do link 'Home', root_path end crumb :users do link "ユーザ一覧", users_path end crumb :user_show do |user| link user.name, user_path(user) parent :users end
追記したファイルのそれぞれの意味は後ほど説明します。
ビューにパンくずリスト表示部分を追加します。 まず、application.html.erbに以下の記述を足してください。
scaffold_gretel/app/views/layouts/application.html.erb
<body> <%= breadcrumbs separator: " › " %> # 追記 <%= yield %> </body>
そして、以下の記述をルートパスであるユーザー一覧画面のビューに記載しましょう。
scaffold_gretel/app/views/layouts/application.html.erb
<% breadcrumb :users %>
そうして再度ルートパスを読み込むと、リンクタグが表示されたかと思います。
まずroot_path
である"Home"が親ページとして表示され、その子ページとして"ユーザー一覧"が表示されているかと思います。
breadcrumb :users
の記載は:users
と対応したconfig/breadcrumbs.rb
の以下の部分を表示しています。
crumb :users do link "ユーザ一覧", users_path end
これはlink_to
ではなくlink
ですが、意味はほとんど同じです。
テキストがユーザー一覧でユーザー一覧画面のリンク(users_path)を含んでいます。
次にユーザー詳細画面であるscaffold_gretel/app/views/users/show.html.erb
にも追記します。
scaffold_gretel/app/views/users/show.html.erb
<% breadcrumb :user, @user %>
ユーザー詳細画面に移動しましょう。
このパンくずリストを表示しているのは以下の部分です。
crumb :user_show do |user| link user.name, user_path(user) parent :users end
<% breadcrumb :user, @user %>
の@user
は上の記載のuser
の値となるので、
テキストがuser.name
でユーザー一覧画面のリンク(user_path(user))を含んでいます。
また、parent :users
と記載したおうに親ページのユーザー一覧のリンクも表示されています。
こちらのリンクをクリックするとユーザー一覧画面に遷移できます。
最後に
以上ざっくりとパンくずリストについて説明しました。 導入するのはとても簡単なのでぜひ使ってみてください!
以上、大ちゃんの駆け出し技術ブログでした!
【Rails】resources と resource
こんにちは!大ちゃんの駆け出し技術ブログです。
今回は初心に戻って、基礎編で学習したresource
とresources
の違いについてアウトプットします。
resources
Ruby on Railsでルーティングを行う際に必ず使うであろうresources
。
resources
はモデルに対してアプリケーションにおける基本メソッド7つを自動的にルーティングしてくれます。
例えばUser
モデルに対してresources
を使ってルーティングを記載した場合
Rails.application.routes.draw do resources :users end
$ rails routes
でルーティングを表示させると以下のようになります。
$ rails routes Prefix Verb URI Pattern Controller#Action users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroy
このようにして、たった1行記載するだけで、簡単にルーティングをしてくれます。
ただ、resourcesが使われるのは基本的にモデルに複数のリソースがある時です。
例えば、User
モデルにはアプリケーションのユーザのモデルがたくさん登録されると思います。
他にも、ユーザの投稿モデルであるPost
モデルも、アプリケーション内に複数の投稿が存在するので、resources
を使用することができます。
resource
では、複数リソースに対してではなく単数のリソースにルーティングする場合はどうでしょうか?
単数のリソースとはつまり、アプリケーション内のページにおいて、ログインユーザだけが使用するリソースです。 例えば、Instagramの設定にあるプロフィールページは自分だけが使用できますよね? 他の人はあなたのプロフィール設定ページににアクセスすることはできません。(できたらアカウントが乗っ取られてしまいます。笑)
そのような単一リソースに対してルーティングを設定する際はresources
ではなく、単数形resource
を使います。
例えば、プロフィール画面をルーティングするとしましょう。
Rails.application.routes.draw do resources :users # 追記 resource :profile end
$ rails routes Prefix Verb URI Pattern Controller#Action new_profile GET /profile/new(.:format) profiles#new edit_profile GET /profile/edit(.:format) profiles#edit profile GET /profile(.:format) profiles#show PATCH /profile(.:format) profiles#update DELETE /profile(.:format) profiles#destroy POST /profile(.:format) profiles#create
resources
と何かが違いますね、、。
①indexアクションがない
なぜ?、と思うかもしれませんが、これはよくよく考えてみると簡単です。
上述したように、resource
を使用する場合、単一リソースが使用対象をなることを説明しました。
しかし、index
アクションはモデルのリソース全てを表示することを意図しています。
結果、単一リソースであるプロフィール画面を扱う際に複数を表示するindex
アクションは不要となるわけです。
②:idがない
4つのアクション(edit、show、update、destroy
)から:idという部分が消えています。
実はこれもindex
アクションがない理由と同じになります。
resources
において4つのアクションに:id
が必要だった理由は、複数あるリソースの中からどのリソースかを指定するためです。
例えば、自分の投稿を編集する場合、どのidの投稿かを指定しないと、どの投稿を編集していいのかがわかりません。投稿の詳細閲覧、投稿の更新、投稿の削除も同様に、どの投稿であるかを指定しないといけません。
しかし、プロフィール設定画面はどうでしょうか。
単一リソースであるプロフィールは、指定せずとも一つしかないのだからわかるという仕組みです。
ですので、idを指定する必要がそもそもないという理由で:id
が消えています。
終わりに
1つのアプリケーションを作成する中で単一リソースを使う頻度はそこまで多くありません。 しかし、プロフィール画面などはアプリケーションによく使われるので、念のため違いについては今一度再確認しておきましょう。
以上、大ちゃんの駆け出し技術ブログでした!
【Rails】日付や時刻を扱うRubyのクラス
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
今回は日付、時刻を扱うRubyのクラスについて紹介したいと思います。
早速説明に入ります。皆さんは日付や時刻を表すRubyのクラスを知っていますか。
日付や時刻を表すクラスとして、Rubyの標準ライブラリには以下の3つが定義されています。
Time
クラスDate
クラスDateTime
クラス
Date
クラスは日付を扱うクラスで、Time
クラスとDateTime
クラスは日付と時刻を扱うクラスです。
(引用: プロを目指す人のためのRuby入門-言語仕様からテスト駆動開発・デバッグ技法まで)
今回は3つのクラスの違いにたまに触れつつ、日付に関わるクラスの使用方法や使えるメソッドなどについて紹介をしていきます。
準備としてテキトーなアプリケーションでコンソールを起動してください。いろいろな入力をしていくので、念のため変更内容が反映されないようオプションに--sandbox
をつけておきましょう。(もしくはirb
でもいいと思います)
$ rails c --sandbox
現在時刻を取り出す
まずは簡単に現在時刻をクラスメソッドを使用して出力してみましょう。
Time.new => 2021-01-27 08:33:49 +0900
2021-01-27
の部分は本日の日付を表しており、08:33:49
の部分は現在時刻です。では+0900
とはなんでしょうか。察しの良い方はわかると思いますが、これは日本標準時と協定世界時の時差を表しています。つまり、協定世界時より9時間先に進んでいるということになります。
ここでDate.new
とDateTime.new
を入力すると少し違う形で出力されます。
Date.new => Mon, 01 Jan -4712 DateTime.new => Mon, 01 Jan -4712 00:00:00 +0000
上述したように、Date
は日付を扱いとDateTime
は日付と時刻を扱います。よってその違いは時刻の有無となります。
Mon, 01 Jan
(つまり、1/1(月))とあるように、現在時刻を出力してくれないのです。これは暦日付に相当する日付オブジェクトを生成しているためです。暦日付について詳しくは説明しませんが、要は時間の出力方法が違うということですね。
Date
クラスを使って現在時刻を出力するにはtoday
メソッドを使います。DateTime
クラスの場合はnow
メソッドを使います。(Time
クラスもnow
メソッドを使用すれば現在時刻を出力できます!出力結果はTime.new
と同じになります。Time
が付けばnow
と覚えておきましょう。)
Date.today => Wed, 27 Jan 2021 DateTime.now => Wed, 27 Jan 2021 10:18:01 +0900
特定の時刻を出力
現在時刻ではなく特定の時刻を引数を与えることで出力することができます。
例えば去年のクリスマスの日付を取り出したいのであれば、Date.new
に対して2020, 12, 24引数として指定します。
Date.new(2020, 12, 24) => Fri, 24 Dec 2020
遊びの待ち合わせ時刻も出力したいのであればTime
もしくはDateTime
を使用します。
Time.new(2020,12,24,11, 0, 0) => 2020-12-24 11:00:00 +0900 DateTime.new(2020,12,24,11, 0, 0) => Thu, 24 Dec 2020 11:00:00 +0000
上記のTime
とDateTime
を見てみると、2つの違いがわかると思います。
DateTime
の方はThu
とあるようにその日の曜日も出力しています。また、+0000
から日本標準時ではなく世界標準時を出力しています。
あともう1つ。引数を渡さなければデフォルト値が出力されます。例えば、Time.new(2020,12,24,11, 0, 0)
の0の部分はデフォルト値ですので、Time.new(2020,12,24,11)
で同じ値が出力できます。
Time.new(2020,12,24,11) => 2020-12-24 11:00:00 +0900
クラスの型の変更
上記でTime
クラスとDateTime
クラスの違いを上述しましたが、Time
クラスのからDateTime
クラスの出力に変更することもできます。Time
クラスの出力にto_datetime
メソッドを使用します。
Time.new(2020,12,24,11).to_datetime => Thu, 24 Dec 2020 11:00:00 +0900
to_datetime
メソッドがあるということは、、、
そうです。to_date
メソッドもto_time
メソッドもあります。
Time.new(2020,12,24,11).to_datetime.to_date => Thu, 24 Dec 2020 Time.new(2020,12,24,11).to_datetime.to_date.to_time => 2020-12-24 00:00:00 +0900
日付・時刻の各要素を出力
次は年・月・日のように日付や時刻を表している各要素を出力する方法を紹介します。と言ってもめちゃくちゃシンプルな方法です。なんと、year
メソッド、month
メソッドなどが標準で定義されています。さすがRuby。
xmas_time = Time.new(2020,12,24,11) => 2020-12-24 11:00:00 +0900 xmas_time.year => 2020 # 年 xmas_time.month => 12 # 月 xmas_time.day => 24 # 日 xmas_time.hour => 11 # 時 xmas_time.min => 0 # 分 xmas_time.sec => 0 # 秒
strftimeメソッド
今までの日付・時刻の出力(2020-12-24 11:00:00 +0900
など)は見た目で何を表しているのかがわかりますが、「2020/12/24」と出力したいとします。
そんな時に便利なメソッドがstrftimeメソッドです。引数に出力したいフォーマットを指定します。
Time.new(2020,12,24,11).strftime("%Y/%m/%d") => "2020/12/24"
フォーマットの引数を見ると少し複雑ですが覚える必要はありません。公式リファレンスを参照すれば出力したいフォーマットに合わせた引数をリストとして載せているのでそちらを参照すれば大丈夫です。下記は公式の使い勝手が良さそうなフォーマットの例になります。
t = Time.new(2001,2,3,4,5,6,"+09:00") p t.strftime("%Y%m%d") # => 20010203 p t.strftime("%F") # => 2001-02-03 p t.strftime("%T") # => 04:05:06
まだまだあります!
今回紹介した以外にもまだまだたくさんのメソッドによる出力方法があります。
例えば、end_of_day
やbeginning_of_year
などです。本当に直感的ですよねww
また機会があれば他のメソッドも紹介していきたいと思います!
以上!大ちゃんの駆け出し技術ブログでした!