⭐︎RSpec
RSpecの復習
内容
今回はRSpec編で自分の理解が浅い部分を復習していきます。
FactoryBotの設定
spec/rails_helper.rb RSpec.configure do |config| config.include FactoryBot::Syntax::Methods end
config.include FactoryBot::Syntax::Methods
をhelperに設定する事によって、
rspecのテストコード中でFactory_botのメソッドを使用する際に、クラス名の指定を省略できるようになります。
・例
# 通常FactoryBotをつけないと、メソッドを呼べない user = FactoryBot.create(:user) # 上の設定を追加することで、FactoryBotの記述が省略できる。 user = create(:user)
FactoryBot.define do factory :user do sequence(:name) { |n| "TEST_NAME#{n}"} sequence(:email) { |n| "TEST#{n}@example.com"} end end
sequence
を付ける事によって、対象のFactoryをcreate_list
などで複数件作成した時にnameのunique制約で失敗する事を防ぐ。
traitについて
・テストデータ(ファクトリー)を作る際に、
「何かのカラム(emailやstatusなど)だけ違う値を作りたいな」と思った時に使う機能。(重複を防ぐ)
例えば、「10個のインスタンスがある内の1個だけ属性値を変えたい」時にtrait
を使い定義する。
定義したファクトリをspecファイルで呼び出したい時は、以下のように記述します。
user_spec.rb user = FactoryBot.create(:user(モデル名), :another_user(ファクトリ名))
letついて
factoryファイルを元にDBにユーザ情報を作成。
・let
文の書き方
let(:変数) { 処理したい内容 }
例
let(:task) { create(:task, project_id: project.id) }
# @userというインスタンス変数に代入 before do @user = create(:user) end # userという変数に代入 let(:user) { create(:user) }
◯◯_spec.rb describe 'let' do let(:user) { create(:user) } let(:user_article) { create(:article, user_id: user.id) } specify 'User が Article を持っていること' do expect(user.articles.first).to eq user_article end end
上記のような場合、let(:user_article) { create(:article, user_id: user.id) }
が呼ばれるのは、
expect(user.articles.first).to eq user_article
の中で "user_article" が呼ばれた時で
"user.articles.first" が処理される時にはまだ実行されていないので "user.articles.first" は空となりテストは通らない。
この事を遅延評価と呼ぶ。
let
を各サンプルが実行される前に評価したい場合(it文やspecity文など)は
let!(:user_article) { create(:article, user_id: user.id) }
のようにしてあげる。
特定のuserでログインできるマクロを作成
色々なユーザでログインする処理を共通化するために、特定のuserを渡すことでログインができるマクロを追加します。
spec/support/login_macros.rb
module LoginMacros def login(user) visit admin_login_identifier_path fill_in 'user[name]', with: user.name click_button('次へ') # DB上では暗号化されて crypted_password に保存されているため user.password で取り出せないので、直接文字列で password を渡す fill_in 'user[password]', with: 'password' click_button('ログイン') end end
このマクロを使用できるように、設定を追加します。
spec/rails_helper.rb RSpec.configure do |config| # 省略 config.include LoginMacros end
⭐️[管理画面] 掲示板/ユーザのCRUD機能の作成
概要
手順
・今回の課題では、今までの課題で使ったことがある機能もあったので、
この課題で新しく出てきた機能で、自分の理解の薄い内容のみアウトプットしていきます!
メニューのアクティブ・非アクティブ化
Bootstrapを使用しているので、例えば下記のようにclassにactiveを含めると、アクティブにしてくれます。
<a class="nav-link active" href="#">Active</a>
これを使って、アクティブにしたいときにはclass
にactive
を含めるようにして実装していきます。
三項演算子を使って、真のときはactive、偽のときは何も返さない(''
)ようにします。
controller_path
でコントローラー名を取得できる。
・view(_sidebar.html.erb)で、ヘルパーメソッドを使い
そのメニューにいる時にメニューをアクティブ、
いない時に非アクティブに出来る。
<li class="nav-item"> <%= link_to admin_boards_path, class: "nav-link #{active_if('admin/boards')}" do %> <i class="nav-icon far fa-file"></i> <p> <%= t('boards.index.title') %> </p> <% end %> </li>
<li class="nav-item"> <%= link_to admin_users_path, class: "nav-link #{active_if('admin/users')}" do %> <i class="nav-icon far fa-user"></i> <p> <%= t('users.index.title') %> </p> <% end %> </li>
ransackを使った範囲指定の検索機能
今回は作成日を指定して検索できるように実装しました。 掲示板の「いつからいつまで」の検索条件について、「いつまで」部分をカスタマイズします。
・config/initializers配下にransack.rbファイルを作成し、lteq_end_of_day
というpredicate(述語)を追加します。
predicate(述語)とは、
title_or_body_cont
の、_cont
のような検索する範囲を定義するものだと解釈しました。
・created_at_lteq
だと0:00が基準になってしまうので正しく絞り込みができないので、
lteq
をカスタマイズしたいと思います。
arel_predicate: 'lteq'
でカスタマイズしたいpredicateを指定。
formatter: proc { |v| v.end_of_day }
では、end_of _day
というすでに裏方で定義されているメソッドを
formatter: proc { |v| v.〇〇}
の〇〇に代入すれば、
lteqをend_of_dayに置き換えることができる。
end_of_dayとはデフォルトの設定を置き換えることができるメソッド。
https://api.rubyonrails.org/classes/Time.html#method-i-end_of_day
・viewに反映
app/views/admin/users/_search.html.erb <%= search_form_for @search, url: admin_users_path do |f| %> <div class="row"> <div class="form-inline align-items-center mx-auto"> <div class="col-auto"> <%= f.search_field :first_name_or_last_name_cont, class: 'form-control', placeholder: t('defaults.search_word') %> </div> <div class="col-auto"> <%= f.select :role_eq, User.roles_i18n.invert.map{|key, value| [key, User.roles[value]]}, { include_blank: t('defaults.unspecified') }, { class: 'form-control mr-1' } %> </div> <div class="col-auto"> <%= f.submit class: 'btn btn-primary' %> </div> </div> </div> <% end %>
enum_helpを使ったenum値の多言語化対応
gem 'enum_help'
で
bundle install
enum_helpというgemを導入する。
config/locales配下に日本語化用のファイルを下記のように用意。
ja: enums: user: role: general: 一般 admin: 管理者
Userモデルで定義している
enum role: { general: 0, admin: 1 }
この記述を下記のようにi18nの翻訳機能を使う事によって、ユーザーのrole(権限)も翻訳したものを表示できるようになる。
# app/views/admin/users/_user.html.erb <%= user.role_i18n %>
✅総括
今回の課題で基礎編は終わりなので、応用編に進む前にしっかりと自分の理解が足りていない箇所の復習をして更なる知識や自走力の向上に向けて学習の方頑張っていきたいです!!
⭐️管理画面へのログイン機能、管理画面トップページの作成
概要
管理画面へのログイン機能を実装。
管理画面のトップページを仮で作成。
手順
1 . AdminLTE version3をインストール
2 . マニフェストファイル設定
3 . 管理者用コントローラー設定
4 . Userモデルに管理者判定用カラム追加
5 . ルーティング設定
6 . ビューの設定
1 . AdminLTE version3系をインストール
yarn add admin-lte@^3.1
これで、node_modules/admin-lteが作成されます。 このディレクトリ内に各種テンプレートがあるので、コピペして管理者用画面を作成していきます。 今回は、同ディレクトリ内のstarter.htmlを使用していきます。
2 . マニフェストファイル設定
今までは、app/assets/javascripts/application.js
app/assets/stylesheets/application.scss
に全て記述していましたが、 管理者画面は一般ユーザー用と見た目が大きく異るため、別々で管理していきます。
以前は、//= require tree.
でapplication.js配下の全ファイルを読み込んでいました。
しかし、今回は同じ階層に管理者用のマニフェストファイルをファイルを配置するため、このままだと不必要な管理者用ファイルを読み込んでしまいます。
そのため、個別にファイルを読み込む記述に変更します。
新しく、管理者用ページのマニフェストファイルを作成します。
アセット関連の設定
・config/initializers/assets.rbに下記がコメントアウトになっているので外してあげる。
Rails.application.config.assets.precompile += %w[admin.js admin.css]
3 . 管理者用コントローラー設定
・管理系の機能を持つコントローラーを作成していきます。
・管理系コントローラーに共通する機能を持つ基底クラス、Admin::BaseController
を作成する
・他の管理系コントローラーは、Admin::BaseController
を継承する。
application_controllerを継承するadmin/base_controllerを作成し、全ての管理画面用コントローラーはこのbase_controllerを継承する設計とするには、
rails g controller Admin::Base
admin::
とすれば自動で改装を作ってくれる。
他にも、ダッシュボード(トップページ)とログイン用のコントローラーを作っておきます。
rails g controller Admin::Dashboards index
rails g controller Admin::User_sessions new
class Admin::DashboardsController < Admin::BaseController
ここの記述でbase_controllerを継承します。
そのため、レイアウト宣言は不要です。
4 . Userモデルに管理者判定用カラム追加
usersテーブルにroleというカラムを追加し、そこの値で権限を判定できるようにしていくので、
rails g migration AddRoleToUsers
で
整数(integer)のデータ型でroleカラムを追加し、デフォルトで0を指定する。(0は一般) - general・・・一般 - admin・・・管理者
rails db:migrate
して同時に、
Userモデルにenum role: { general: 0, admin: 1 }
を記述。
5 . ルーティング設定
namespace :admin do root to: 'dashboards#index' get 'login', to: 'user_sessions#new' post 'login', to: 'user_sessions#create' delete 'logout', to: 'user_sessions#destroy' end
/admin
で始まるURLにしたいので、namespace :admin
で名前空間を設定します。
6 . ビューの設定
・node_modules/admin-lte/starter.htmlから必要部分を切り分けしています。
header/sidebar/footerは切り分けて、views/admin/sharedに配置しました。詳細は省略します。
管理者用共通ビューのこのファイルで、先程設定したadmin.js admin.scssを読み込んでいます。
・今回、管理者用のページは「ダッシュボード | RUNTEQ BOARD APP(管理画面)」のように(管理画面)と出力したいです。
デフォルトでadmin = false
にしておきます。
これで、今まで作ってきたタイトルはadmin: falseなので書き換えが不要です。
最後に評価された式を戻り値としてbase_titleに代入しています。
・管理用共通テンプレートに読み込み
繰り返しになるので、タイトル部分だけ抜粋
admin: true
にします。
<title><%= page_title(yield(:title), admin: true) %></title>
✅総括
基礎編課題も終盤になりかなり難しい機能になってきたので、一個一個復習しながら、手順(開発の流れ)をしっかり頭に入れていきたいです!
気を引き締めてこれからも頑張っていきます!!💪🔥
⭐️パスワードリセット機能の実装
手順
1 . パスワードをリセットするための申請
2 . メールを受信してリンクにアクセス
3 . パスワードを変更
sorceryのreset_passwordモジュールを使用
・sorceryの中のモジュールであるreset_passwordを導入
$ rails g sorcery:install reset_password --only-submodules
・上記コマンドで作成されたmigrationファイルを適用
rails db:migrate
・tokenにユニーク制約
トークンは唯一無二でなけれならない。
被ると、別の人のパスワードを変更できてしまう。
user.rb validates :email, presence: true, uniqueness: true validates :reset_password_token, presence: true, uniqueness: true
・UserMailerという名前でパスワードリセットメール用のMailerを作成
$ rails g mailer UserMailer reset_password_email
・sorcery.rbにreset_passwordサブモジュールを追加し、パスワードリセットに使用するActionMailerとしてUserMailerを定義
sorcery.rb Rails.application.config.sorcery.submodules = [:reset_password] Rails.application.config.sorcery.configure do |config| config.user_config do |user| user.reset_password_mailer = UserMailer end end
※sorceryの定義ファイルに、使用するサブモジュール記述があることが多いが、もし、Rails.application.config.sorcery.submodules = [:reset_password])や、user.reset_password_mailer = UserMailer入っていなければ、sorcery.rbに追記する
ルーティング、コントローラー、viewファイル作成
rails g controller PasswordResets new create edit update
・ルーティング設定
resources :password_resets, only: %i[new create edit update]
パスワードリセット画面
ユーザーがパスワードリセットをしたい時に、申請するための画面です。
ここに自分がユーザー登録時に登録したアドレスを入力して送信すると、
パスワードリセットメールがそのアドレスに届き、メールにしたがって手続きを進めていくという仕様。
メール文作成
メール文の作成は下記ファイルで行う。
・app/views/user_mailer/reset_password_email.text.erb ・app/views/user_mailer/reset_password_email.html.erb
ユーザーにメールを送信するメソッド部分
先ほど作成したパスワードリセットするためのメール本文を送信するためのメソッド。
メールを送信するためのコントローラーが下記ファイルになる。
・default from: 'メールの送信元'
でメールの送信元のアドレスを指定できる。
・mail(to: 宛先,subject: '件名')
でメールの宛先、件名を指定
パスワードリセット画面
パスワードリセット申請後、送られてきたメールのURLにアクセスすると、表示される画面の実装。
letter_opener_webを追加し、開発環境では実際のメールは送られないように設定。
以下のような画面をローカルで確認できる!
Gemfileにletter_opener_webを追加する
group :development do gem 'letter_opener_web' end
bundle install
ルーティングにLetterOpenerWebアクセスするために必要な記述を追記
一番下に以下の記述を追記。
mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?
config/environments/development.rbに設定を追加
Rails.application.configure do 省略... config.action_mailer.perform_caching = false config.action_mailer.default_url_options = { host: 'localhost:3000' } config.action_mailer.delivery_method = :letter_opener_web 省略... end
メールが送られたか確認する
・http://localhost:3000/letter_opener
にアクセス
環境変数の設定
・host情報はconfigというgemを使ってsettings/development.ymlに記載する
gem 'config'
でbundle install
gemの機能で設定ファイルを生成
rails g config:install
カスタマイズ可能な設定ファイルconfig/initializers/config.rbとデフォルト設定ファイルのセットを生成
→
config/settings.yml
config/settings/development.yml
config/settings/production.yml
config/settings/test.yml
定数置き換え
・Settings.hostで置き換え
config/environments/development.rb
config.action_mailer.default_url_options = { host: Settings.host }
✅総括
今回の課題であるパスワードリセット機能の実装は、
セキュリティ面を厳重にするための構築をしないといけなかったのと、
初めてMailerでユーザーにメールを送信する作業があったりして、
色々と新しいメソッドが出てきたのでとても理解が難しかったです。
ただ、Mailerに関しては色々なgemを取り入れる事によって、より簡易的にコードを書けることが分かりました。
パスワードリセット機能に関しては、どこかでもう一度復習して頭に入れていきたい!!
⭐️掲示板のページネーション
概要
掲示板とブックマーク一覧画面にページネーションを実装
ページネーションにはkaminariを使用し、1ページあたり20件の掲示板を表示する。
その際、設定値はコントローラに記載するのではなく専用のファイルに記載ページネーションにはBootstrap4のスタイルをあてる。
手順
1 . kaminariのインストール
2 . コントローラーの修正
3 . ページネーションの表示
1 . kaminariのインストール
・Gemfileにgem 'kaminari', '~> 1.2', '>= 1.2.1'
を記載し、
bundle install
・rails g kaminari:config
でページネーションの設定ファイルを作成。
・今回は掲示板とブックマーク一覧画面どちらも1ページに20件表示するので、
config.default.per_page = 20 #1ページ辺りの項目数
を設定することでコントローラーへの記述量を減らせる。
(per
メソッドを使って、
params[:page].per(1ページに指定したい項目数)
)
・configにデフォルト値を設定しない場合はper
メソッドを使う。
2 . コントローラーの修正
・対象となるコントローラーのアクションにpage
メソッドをparams[:page]
を指定し、追加で定義する。
・しかし、このアクションをの中身を一行で書けるのが下記。
(こちらの方がシンプルかつ記述量が少なくてGood!)
3 . ページネーションの表示
・対象のviewファイルに
<%= paginate コントローラーに定義した変数 %>
を記述してビューにページネーションを反映させる。
Bootstrap4のスタイルをあてる
$ rails generate kaminari:views bootstrap4
上記コマンドを打つことで、
viewファイルにkaminariフォルダができ、bootstrap4を当てたページネーションが作成される!
✅総括
ページネーションをつける事によって、一覧画面などがグンと見やすくなる。
bootstrapを当てる事によってより見栄えが良くなるので、
bootstrapのバージョンを変えたり、page
メソッドを変えたりして、
ポートフォリオを作る際に自分なりにアレンジ出来そうで楽しそう!!🔥
⭐️プロフィール編集機能の実装
ユーザーのプロフィール編集機能を実装
手順
1 . プロフィールコントローラーを作成
2 . ルーティングの設定
3 . アバター画像のカラムを追加
4 . Viewの表示
1 . プロフィールコントローラーを作成
・rails g controller profiles
でプロフィールコントローラーを作成し、
ユーザーのプロフィール画面では
詳細、編集、更新
ができれば良いので、
show、edit、updateアクションのみを定義。
privateで定義している
def set_user @user = User.find(current_user.id) end
上記で現在ログインしているユーザーのidを@userに代入している。
プロフィールの編集や更新する時にはこのidが必要なのでedit、updateにbefore_actionを使って読み込ませている。
・プロフィールを更新する時にはストロングパラメーターを使って編集内容を引き継いでいる。
def user_params params.require(:user).permit(:email, :last_name, :first_name, :avatar) end
2 . ルーティングの設定
本来、/users/:id/edit
のようになるはずですが、もしidを変えれば、他のユーザーのプロフィール編集ページに入ることができてしまう可能性があります。(もちろん、入れないようにバリデーションはかけますが)
プロフィール変更ページは自分自身に対してしか使わないので、idを用いず、pofile/editとなるようにルーティングを設定していきます。
resource :profile, only: %i[show edit update]
3 . アバター画像のカラムを追加
rails g migration AddAvatarToUsers
でusersテーブルにアバター画像のカラムを追加するためのマイグレーションファイルを作成。
class AddAvatarToUsers < ActiveRecord::Migration[5.2] def change add_column :users, :avatar, :string end end
・画像のデータ型はstringに設定する。
4 . Viewの表示
・app/views/profiles/edit.html.erb
<div class="mt-3 mb-3"> <%= image_tag @user.avatar.url, class: 'rounded-circle mr15', size: '100x100', id: 'preview' %> </div>
上記の@user
は、
Profiles_controller#editで定義している@user
をform_with
に渡してviewに反映させている。
✅総括
プロフィール編集機能の実装でのルーティングでは、idを用いずに実装する。(セキュリティの為)
ログインユーザーからみてアプリケーション上、1つしか存在しない様なリソースが存在する場合、REST的なルーティングを定義したい場合には、resoursesという複数形ではなく、resourceを定義する。
個人的につまずいたこと(凡ミス)
プロフィール詳細画面で、課題自体は完了してたけど見本とHTML構成だけが違うと思って検証ツールやページのソース表示を試していたのですが、 ただただメールアドレスの長さが違うもので比べていたせいで、2時間ほど無駄にしました!😅
今後viewが見本と違うと思ったときは、
ローカルのものと全く同じ状態(メールアドレスやユーザー名などの情報)にして検証することがとても大事だと痛感しました!!泣
今後に生かしていきます!!
⭐️掲示板の検索機能を実装
ransackを用いて掲示板の検索機能を実装
・検索時には、検索フォームに入力された文言が「掲示板のタイトルor本文」に含まれている掲示板のみを表示。
・ブックマーク一覧のページで検索した場合は「ブックマークした掲示板の中から」検索条件に合致したものを表示。
手順
1 . gem ransackをインストール
2 . コントローラーに検索機能を追加
3 . Viewに反映
1 . gem ransackをインストール
・Gemfileにgem ransack
を記述して
bundle install
2 . コントローラーに検索機能を追加
・掲示板一覧画面での検索はboards#indexアクションで行います。
Boardモデルを通してransack
メソッドを使って
検索フォームから送られてきた情報をparams[:q]で受けて、@search
に代入。
result
メソッドで検索結果の表示ができる。
・ブックマーク一覧画面での検索はboards#bookmarksで行う。
3 . Viewに反映
・課題に指定は無いが、掲示板一覧画面とブックマーク一覧画面の検索フォームは同じ条件で実装(ここで言えば、掲示板のタイトルor本文の部分一致検索)だったので、記述量を減らすため
_search_form.html.erb
というパーシャルを作成して掲示板一覧画面とブックマーク一覧画面でレンダリングするようにしました。
上記、viewからsearch_form_for
へ検索ワード入力してEnterを押すと、指定したpathへ送信。
今回はboards_controllerのindexアクションかbookmarksアクションへ送信して検索する仕様。
✅search_field
form_forやform_withのransack版のようなもの
✅:title_or_body_cont
この記述でタイトルor本文で_cont
(部分一致検索)が出来る。
・掲示板一覧画面とブックマーク一覧画面でそれぞれpathとコントローラーに定義したインスタンス変数を指定してレンダリング
gem ransackで使える検索メソッド一覧
🟢総括
今回ransackを使った検索機能を学びましたが、
コントローラーでの定義が少し難しかった。
しかし、ransackメソッドの意味さえ分かれば後はviewにform_withと同じやり方で表示出来て、
改めてform_withの仕組みについて深く理解できたので次にも生かしていきたいです!