⭐︎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機能の作成

概要

  • 管理画面に、掲示板のCRUD機能を作成してください。

  • 管理画面に、ユーザーのCRUD機能を作成してください。

手順

・今回の課題では、今までの課題で使ったことがある機能もあったので、
この課題で新しく出てきた機能で、自分の理解の薄い内容のみアウトプットしていきます!

メニューのアクティブ・非アクティブ化

Bootstrapを使用しているので、例えば下記のようにclassにactiveを含めると、アクティブにしてくれます。

<a class="nav-link active" href="#">Active</a>

これを使って、アクティブにしたいときにはclassactiveを含めるようにして実装していきます。

Image from Gyazo

三項演算子を使って、真のときは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を使った範囲指定の検索機能

今回は作成日を指定して検索できるように実装しました。 掲示板の「いつからいつまで」の検索条件について、「いつまで」部分をカスタマイズします。

Image from Gyazo

・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 %>

Image from Gyazo

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 %>

Image from Gyazo

✅総括

今回の課題で基礎編は終わりなので、応用編に進む前にしっかりと自分の理解が足りていない箇所の復習をして更なる知識や自走力の向上に向けて学習の方頑張っていきたいです!!

⭐️管理画面へのログイン機能、管理画面トップページの作成

概要

  • 管理画面へのログイン機能を実装。

  • 管理画面のトップページを仮で作成。

手順

  • 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に全て記述していましたが、 管理者画面は一般ユーザー用と見た目が大きく異るため、別々で管理していきます。

Image from Gyazo

以前は、//= require tree.でapplication.js配下の全ファイルを読み込んでいました。 しかし、今回は同じ階層に管理者用のマニフェストファイルをファイルを配置するため、このままだと不必要な管理者用ファイルを読み込んでしまいます。

そのため、個別にファイルを読み込む記述に変更します。
新しく、管理者用ページのマニフェストファイルを作成します。

Image from Gyazo

Image from Gyazo

アセット関連の設定

・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

Image from Gyazo

Image from Gyazo

Image from Gyazo

class Admin::DashboardsController < Admin::BaseController
ここの記述でbase_controllerを継承します。
そのため、レイアウト宣言は不要です。

4 . Userモデルに管理者判定用カラム追加

usersテーブルにroleというカラムを追加し、そこの値で権限を判定できるようにしていくので、
rails g migration AddRoleToUsers
Image from Gyazo

整数(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(管理画面)」のように(管理画面)と出力したいです。

Image from Gyazo

デフォルトでadmin = falseにしておきます。
これで、今まで作ってきたタイトルはadmin: falseなので書き換えが不要です。 最後に評価された式を戻り値としてbase_titleに代入しています。

・管理用共通テンプレートに読み込み

Image from Gyazo

繰り返しになるので、タイトル部分だけ抜粋
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ファイルを適用

Image from Gyazo

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]

パスワードリセット画面

ユーザーがパスワードリセットをしたい時に、申請するための画面です。
ここに自分がユーザー登録時に登録したアドレスを入力して送信すると、
パスワードリセットメールがそのアドレスに届き、メールにしたがって手続きを進めていくという仕様。

Image from Gyazo

メール文作成

メール文の作成は下記ファイルで行う。

・app/views/user_mailer/reset_password_email.text.erb ・app/views/user_mailer/reset_password_email.html.erb

Image from Gyazo

Image from Gyazo

ユーザーにメールを送信するメソッド部分

先ほど作成したパスワードリセットするためのメール本文を送信するためのメソッド。
メールを送信するためのコントローラーが下記ファイルになる。

・app/mailers/user_mailer.rb
Image from Gyazo

default from: 'メールの送信元'でメールの送信元のアドレスを指定できる。

mail(to: 宛先,subject: '件名')でメールの宛先、件名を指定

パスワードリセット画面

パスワードリセット申請後、送られてきたメールのURLにアクセスすると、表示される画面の実装。

Image from Gyazo

letter_opener_webを追加し、開発環境では実際のメールは送られないように設定。

以下のような画面をローカルで確認できる!

Image from Gyazo

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

定数置き換え

Image from Gyazo

・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でページネーションの設定ファイルを作成。

Image from Gyazo

・今回は掲示板とブックマーク一覧画面どちらも1ページに20件表示するので、

config.default.per_page = 20  #1ページ辺りの項目数

を設定することでコントローラーへの記述量を減らせる。
(perメソッドを使って、
params[:page].per(1ページに指定したい項目数))

・configにデフォルト値を設定しない場合はperメソッドを使う。

2 . コントローラーの修正

・対象となるコントローラーのアクションにpageメソッドparams[:page]を指定し、追加で定義する。

Image from Gyazo

Image from Gyazo

・しかし、このアクションをの中身を一行で書けるのが下記。
(こちらの方がシンプルかつ記述量が少なくてGood!)

Image from Gyazo

3 . ページネーションの表示

・対象のviewファイルに
<%= paginate コントローラーに定義した変数 %>
を記述してビューにページネーションを反映させる。

Image from Gyazo

Image from Gyazo

Bootstrap4のスタイルをあてる

$ rails generate kaminari:views bootstrap4

上記コマンドを打つことで、
viewファイルにkaminariフォルダができ、bootstrap4を当てたページネーションが作成される!

Image from Gyazo

✅総括

ページネーションをつける事によって、一覧画面などがグンと見やすくなる。
bootstrapを当てる事によってより見栄えが良くなるので、
bootstrapのバージョンを変えたり、pageメソッドを変えたりして、
ポートフォリオを作る際に自分なりにアレンジ出来そうで楽しそう!!🔥

⭐️プロフィール編集機能の実装

ユーザーのプロフィール編集機能を実装

手順

  • 1 . プロフィールコントローラーを作成

  • 2 . ルーティングの設定

  • 3 . アバター画像のカラムを追加

  • 4 . Viewの表示

1 . プロフィールコントローラーを作成

rails g controller profiles
でプロフィールコントローラーを作成し、
ユーザーのプロフィール画面では
詳細編集更新
ができれば良いので、
showeditupdateアクションのみを定義。

Image from Gyazo

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の表示

Image from Gyazo

Image from Gyazo

・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で定義している@userform_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メソッドで検索結果の表示ができる。

Image from Gyazo

・ブックマーク一覧画面での検索はboards#bookmarksで行う。

Image from Gyazo

3 . Viewに反映

・課題に指定は無いが、掲示板一覧画面とブックマーク一覧画面の検索フォームは同じ条件で実装(ここで言えば、掲示板のタイトルor本文の部分一致検索)だったので、記述量を減らすため
_search_form.html.erb というパーシャルを作成して掲示板一覧画面とブックマーク一覧画面でレンダリングするようにしました。

Image from Gyazo

上記、viewからsearch_form_forへ検索ワード入力してEnterを押すと、指定したpathへ送信。

今回はboards_controllerのindexアクションかbookmarksアクションへ送信して検索する仕様。

search_field
form_forやform_withのransack版のようなもの

:title_or_body_cont
この記述でタイトルor本文で_cont(部分一致検索)が出来る。

掲示板一覧画面とブックマーク一覧画面でそれぞれpathとコントローラーに定義したインスタンス変数を指定してレンダリング

Image from Gyazo

Image from Gyazo

gem ransackで使える検索メソッド一覧

Image from Gyazo

🟢総括

今回ransackを使った検索機能を学びましたが、
コントローラーでの定義が少し難しかった。
しかし、ransackメソッドの意味さえ分かれば後はviewにform_withと同じやり方で表示出来て、
改めてform_withの仕組みについて深く理解できたので次にも生かしていきたいです!