Node.jsのversionを切り替える[詰んだ]🤦‍♀️

[環境構築]のおはなし

Node.jsのversionを切り替える

  • See???

    See.ruby-version.
    See Gemfile.
    

    ただ単に「見てね」って事らしい コマンドじゃない事は分かっていたがそういうこと

やらかした🤦‍♀️


指定されたNode.jsのバージョンを切り替える時になんやかんややってて、

homebrewを消してしもた。。。雑魚過ぎる

nodebrewも無い…

もっと言えばruby -vでエラーが出るのでrubyすらも消えたっぽい…yarnも

(その時のターミナルの写しは無い。)


※ただ、nodeとnpmが無い時のはあった

% node -v
zsh: command not found: node

% npm -v
zsh: command not found: npm

うぅ…

\(^o^)/

入れ直す


MacにNode.jsをインストールしてnpmを使えるようにする(Nodebrew利用) | Hirooooo's Labo

【Mac版】node.jsのアンインストールと再インストール手順メモ - Qiita

homebrewとnodebrewをもっかいインストールし直す。rubyも。


% brew -v # homebrewはインストールされたっぽい
Homebrew 3.1.8

% nodebrew -v # nodebrewもインストールされたっぽい
nodebrew 1.1.0

しかし

% npm -v
zsh: command not found: npm

ん〜。

% curl -L git.io/nodebrew | perl - setup

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
100 24696  100 24696    0     0  13591      0  0:00:01  0:00:01 --:--:-- 13591
Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew

========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH  # パス通してね
========================================

% export PATH=$HOME/.nodebrew/current/bin:$PATH # はいよ
% nodebrew ls
v12.14.0

current: none # nodebrewはあるけど指定されていない
% nodebrew use v12.14.0 # このバージョン使うよ
use v12.14.0

% nodebrew list                
v12.14.0

current: v12.14.0  # 適用された!
# パスを通してあげよう
% export PATH=$HOME/.nodebrew/current/bin:$PATH

% npm -v
6.13.4  # でた!やったね!

🐥 <でかした!

yarnも入れる

yarnチートシート - Qiita

% brew install yarn

…

% yarn --version                                                          
1.22.10

とりあえず入ったけど、

ワシが使いたいのは0.20.1なんじゃあ🧠🚿

f:id:michimo_10:20210611150824j:plain



  • バージョン指定しないといけないと思ったが

    後から言われる

    Yarnは0.20.1以上のバージョンであれば大丈夫です。
    

    え、あ、、そうなん…。


rubyもバージョン違うんで変更する

Rubyのバージョン変更 - Qiita

rbenv rehashをちゃんと理解する - MogLog

% ruby -v
ruby 2.6.4p104 (2019-08-28 revision 67798) [x86_64-darwin20]

# rubyは2.6.5にしたいので
% rbenv install 2.6.5     
rbenv: /Users/######/.rbenv/versions/2.6.5 already exists
continue with installation? (y/N) n
# 既にあるっぽいので no

% rbenv local 2.6.5 # localを2.6.5にする

% ruby -v                 
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin20]
# でけた!

でかした!

f:id:michimo_10:20210611150745j:plain



これで勝つる!

環境構築もだがPCの信頼構築もしないといけない感

Macはトモダチ...⚽️

備忘録 Rspecヒント

📝

  • フォークしてきたgithubをよく見る

  • letの挙動と性質を振り返ること

  • 処理の流れをよく知ること

  • expectが「期待すること」

  • 期待する処理をするためにブラウザで実際にやってみる

  • letの定義をvisitの引数で使う

    let(:task) { create(:task) }
    
    …省略
    
    it '…' do
    	visit 〇〇_path(task)
    …省略
    end
    

使えるヒント

FactoryBot Traits - Qiita

FactoryBotでtraitを使おう - Qiita

fitを使う


ヒントの

it を fit にするとspec実行時にそのテストケースのみをfocus実行できます

これは前回focasを記述したspec_helper.rb内

config.filter_run_when_matching :focus

この上にある

コメントアウトの部分で**’it を fit にする’**説明が書かれている

<原文>
# with RSpec, but feel free to customize to your heart's content.
  # This allows you to limit a spec run to individual examples or groups
  # you care about by tagging them with `:focus` metadata. When nothing
  # is tagged with `:focus`, all examples get run. RSpec also provides
  # aliases for `it`, `describe`, and `context` that include `:focus`
  # metadata: `fit`, `fdescribe` and `fcontext`, respectively.

							↓     ↓
 <翻訳>
# RSpecでは、心ゆくまで自由にカスタマイズしてください。
  # これにより、`:focus`メタデータでタグ付けすることで、specの実行を個々の例やグループに限定することができます。
  メタデータでタグ付けすることで、気になる個々の例やグループに # 限定することができます。何もないとき
  # 何もないときは、すべての例が実行されます。また、RSpecでは
  # it`, `describe`, `context` には `:focus` を含むエイリアスが用意されています。
  # メタデータを提供しています。それぞれ、`fit`, `fdescribe`, `fcontext` です。

f:id:michimo_10:20210607143455p:plain




spec/support/driver_setting.rb にspec実行時のブラウザON/OFFの切替設定があります


✋(΄>◞౪◟<`)タハー

これ前回見たやつだ!!!

"ブラウザのON/OFF"って言ったらアレよ

アレっしょ?

f:id:michimo_10:20210607143548p:plain


f:id:michimo_10:20210607143602p:plain

  •  

 

テスト失敗時には tmp/screenshots フォルダ以下に画面キャプチャが保存され、確認することが出来ます(このフォルダはgit ignoreされています)


もう、ヒントもね、問題文もね、よく読まないとダメなんだよね

こういうふうにね、今までね、課題で見てたよく見るスクショのやつをね、rspecやるときはね、自分でね、設定しなきゃいけないんだよね

f:id:michimo_10:20210607143632p:plain



信じるか信じないかはあなた次第です

👁️


f:id:michimo_10:20210607143707p:plain



f:id:michimo_10:20210607143720p:plain



それでいて、そのあとどういう処理をするんだっけ?

その処理で何か必要なものとかあったりするっけ?

itってなんだっけ?visitってなんだっけ?click_linkってなんだっけ?expectってなんだっけ?

Macのマウスのポインタを変える

まずは以下のGithubで"Mousecape_1813.zip"をダウンロード

Releases · alexzielenski/Mousecape

次にこのサイトに適当に登録をして⬇︎を押してcapeコードをダウンロードします

Mousecape-Throwback 2021 (Mac cursors) by allannyholm on DeviantArt

とりあえずここまで。


そしたら

1個目のzipのファイルを解凍するとネズミのアイコンが出ますが、ふつーにクリックしたら開けないはず。

なので、右クリックして警告が出ますが無理矢理開きます。

f:id:michimo_10:20210607142634p:plain



開いたらfileから[import Cape]をクリックし

f:id:michimo_10:20210607142654p:plain



2つ目にダウンロードしたファイルから.capeファイルを選択しimportする(※1個ずつしかできない)

f:id:michimo_10:20210607142923p:plain

importすると、このように増えます

f:id:michimo_10:20210607142841p:plain



あとは名前あたりをクリックすればマウスポインタが変わります

やったね!!

(ちなみに僕は別にあったAnnoying dogのワンちゃんポインタです)

f:id:michimo_10:20210607143004g:plain

戻したい時はRemoveで削除すればデフォルトに戻ります

使いたい時はまたimportしてください

f:id:michimo_10:20210607143108p:plain



System Spec

 

おことわり

実装に関してはググればもっと上手く綺麗にまとめてある方の記事があるのでそちらをご覧ください。 未来の忘れた自分に対する説明の仕方なので要点だけ知りたい人には向きません。ごちゃごちゃしてます。(下手くそかよ)


必要最低限の記述の仕方は現場RailsとQiitaの記事を見れば大体わかる *雰囲気知るには'現場Rails'の方が個人的に分かりやすいかも(chapter5)

ただ、書いてないことも多いのであとは調べるしかない

  • 今回必要な単語
    • len
    • let!
    • fill_iv
    • click_button
  • やらないといけない事
    •  system_specファイルの設定
    • login処理
    • moduleを作る

  • 今回の要件(いいか?ちゃんと読めよ?!*100)

    タスクとユーザーに関するsystem specを作成し、下記要件のテストケースを作成してください。

    [正常系]
    ・ユーザーの新規作成、編集ができること
    ・ログインが成功すること
    ・ログインした状態でタスクの新規作成、編集、削除ができること
    ・マイページにユーザーが新規作成したタスクが表示されること
    
    [異常系]
    ・メールアドレスが未入力時にユーザーの新規作成、編集が失敗すること
    ・登録済メールアドレス使用時にユーザーの新規作成、編集が失敗すること
    ・フォーム未入力時にログインが失敗すること
    ・ログインしていないユーザーでタスクの新規作成、編集、マイページへの遷移ができないこと
    ・他のユーザーのユーザー編集、タスク編集ページへの遷移ができないこと
    

    共通の確認観点として「処理後の遷移先のパスがどこか」を確認してください。

    [正常系] では、下記の観点も確認してください。

    • 「ログイン・ユーザー新規登録の成功時に対応するフラッシュメッセージが表示されること」
    • 「タスクの新規作成・編集・削除が成功した場合、対応するフラッシュメッセージと更新したデータが画面上に表示されていること」

    [異常系] では、下記の観点も確認してください。

    • 「ログイン・ユーザー新規登録の失敗時に対応するフラッシュメッセージが表示されること」
    • 「タスクの新規作成・編集が失敗した場合、対応するフラッシュメッセージと更新しようとしたデータが画面上に表示されていないこと」

    [その他の要件]

    • login処理はmoduleを作成して共通化して、specファイル間で呼び出せるようにしてください。
    • spec実行時にブラウザの表示有無を切り替える設定を spec/support 以下に追加してください
    • 実行するテストケースを限定できる設定を spec_helper.rb の50行目辺りを確認して有効化してください。
    • テストデータの作成にはFactoryBotやletを利用してください。 

    また作成したspecがテストとして機能しているか、ControllerやViewのコードを変更した際にテストが失敗することを確認してください

  • 確認ポイント(ちゃんと読めよ??)

    • Task, User, UserSession のシステムテストが記載できていること
    • expectではなるべくDBへのアクセスを行わないこと(テスト実行時の時間的コストがかかるため)
    • FactoryBot と let, let! を利用してテストデータを作成していること 
    • ブラウザのON/OFFやテストケースのfocus実行などの設定を行っていること
    • login用のメソッドはmoduleとして共通化し、引数に渡したUserオブジェクトによって異なる権限のユーザでログインできるように定義すること

できる限り細かく噛み砕いてみます

そもそもSystem Specってなにだ?🌝 🍙


Railsアプリケーションの全体的なテスト。 ブラウザを通してアプリケーションの挙動を外部的に確認できる(現場Rails,190p) (192pのコラムも見たらいいかも)



(前回はModel Specだったので、その名の通りModelファイルに関するテストだった。)

「なるほ、全体テストすりゃいいんだな!」ってのが分かったが、

🤔「どこに書いたらいいんだ?」

【問題文よく読め】

ちゃんと要件に書いてあるんだよ 作成 と。

さらに今回のようなSystem Specを行いたい場合は、現場Rails(5-8 / 199p)とqiitaの記事を見ると書いてあるように

ディレクトリは自身で作成する

ターミナルでコマンドで作ってもいいし、vscodeとかならマウスでポチポチ🌱作ってもいい

rspec-rails 3.7の新機能!System Specを使ってみた - Qiita

 

🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🐤

🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀

必要な単語

let


  • letは変数のように使えるオブジェクトを定義することができます。(現場Rails-211,212p)

    ただ定義するだけでは実行されません。 コントローラーなどに作ったdef ●●のように"作ったけど使わないメソッド"と同じです。

    これはletを定義した後にletまたは定義名を使わないと作っても実行されません。

    • let(定義名) { 定義の内容 }

let!


  • letは使われないと実行されないですが、! をつけると常に呼び出されます。(現場Rails-214p)

    「呼び出される時と呼び出されない場合があるけど、データは作っておきたい」そんな時にも使えます

    使い所としてはbeforeの外に記述がある場合です。beforeが先に呼び出されてしまうと外に書いていたletは実行されなくなります。beforeの前に呼び出して欲しいのでlet!で定義します。

    !を使わずにbeforeの中に書いてもいいですが、別所でまた書かないといけなくなったりするので汎用性のためです。

    • let!(定義名) { 定義の内容 }

fill_in


  • fill_inはhtmlでいう<label>タグの<input>要素であるテキストフィールド(label)名を指定して、その中に値を入れてくれるメソッドです。(現場Rails-202p)

    fill_in 'ほにゃらら', with: 'フガフガ'
    # ↑ ラベル名          ↑初めて見た
    

    上記で言うと<input>部分の'ほにゃらら'というlabelの中に'フガフガ'という文字(値)を入れてくれます

    f:id:michimo_10:20210607140502p:plain



    よく見るメールフォームなら

    fill_in 'Email', with: 'フガフガ'
    

    f:id:michimo_10:20210607140506p:plain



    こんな感じ。

  • あとwithってのは「ここに書いた文字入れてくれるんやな」程度だったので少しだけ調べてみました

    Matching arguments

    説明にあるやつを翻訳すると

    Use with to specify the expected arguments. A message expectation constrained by with
    will only be satisfied when called with matching arguments. A canned response for an
    allowed message will only be used when the arguments match.
    
    																↓   ↓
    
    期待される引数を指定するにはwithを使います。withで制約されたメッセージの期待値は
    によって制約されたメッセージの期待値は、一致する引数で呼び出された場合にのみ満たされます。許可されたメッセージに対する定型応答は
    許可されたメッセージに対する定型応答は、引数が一致したときにのみ使用されます。
    

    f:id:michimo_10:20210607140558p:plain



    期待される引数を指定する

    なるほどな!

click_button


🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯

🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀

・その他の要件

- ログイン処理を作る(汎用性のUP)


  • ログインヘルパーを作る-パーフェクトRails(360p)

    ログイン機能は複数のテストで共有するので、ヘルパーメソッドとしてhelperファイルに書きますログインが必要なページでもテストが行えるようにテストコードでログインする機能を実装します

    イメージとしては「コントローラーじゃなくてモデルに書いとく」みたいな感じです。共通する場所に先に書いとけばあとはそのメソッドだけ使えるので、その方が汎用性上がるから。

ただ、イメージに近いけど軽く読んで今回はパス。

なぜこれをするのか?要件に書いてあるんだよ!🐻 <ダボが

一応「login module rspec」でググるとこういう記事が出てきたよ。とりあえず脳死でその通りにやってみるよ

Rspecでサインインメソッドを共通化して切り出す(devise使わないとき)[system spec][request spec] - Qiita

【Rails】はじめてのSystemSpec(RSpec) - Qiita

f:id:michimo_10:20210607140634p:plain



f:id:michimo_10:20210607140643p:plain

コメントアウトを解除したお。これ何をしてるかっていうと

"spec/support/以下が読み込めるようにパスを渡してます"

  • 例えば。。

    よくプロフィール写真を設定しないときにデフォルトで👤 こういった表示がされるように👤 ⇦の画像がどこかのディレクトリにあるんだけどそこから引っ張ってくる為に書かれたりするよ。「ここにある画像の場所はこのディレクトリだよ!」と言う道筋を表す書き方をパスを通すと言います。

    書き方は ' ' と , で区切ってますが / でも大丈夫だ(と教えてもらった、まじ神。あざす)

  • ログインするのはuserなので中にはloginする為のモジュール(module)を作成します.
  • module LoginModule # LoginModuleという名前にしたがなんでも良い
      def login(user) # ログインするメソッドを定義する、以下内容
        visit login_path #ログイン画面にアクセスするよ
    		click_link 'Login' # Loginリンクをクリックするよ
        fill_in 'Email', with: user.email # Emailラベル内にloginの引数(user)のメールを入れる(やりたい事が変わる時があるのでメアドは都度入れる必要がある)
        fill_in 'Password', with: 'password' # Passwordラベルにpassword って入れてくれるよ
        click_button 'Login' # 最後にLoginボタンを押してくれるよ
      end
    end
    # rails_helper.rbの下に、このLoginModuleをinclude(組み込む)してあげると、
    # rails_helperをrequireしているspecファイル内でこのlogin(user)メソッドが使えるようになる
    
  • 上で書いたモジュールが読み込まれるようにrails_helper.rbに追記します

    #一番下の方
    # arbitrary gems may also be filtered via:
    # config.filter_gems_from_backtrace("gem name")
      config.include FactoryBot::Syntax::Methods # spec.rb内でletを定義する中で FactoryBot. を省略してくれる
      config.include LoginModule # login_module.rbに記載した内容のmoduleを使えるようにする(今回はLoginModule)
    end
    

この辺分かりにくいけど、上のをやっておかないと下の"spec/support 以下に"繋がらないよね


Capybara

次に行く前にCapybaraってなんだ ↓

Capybaraチートシート - Qiita


 

spec実行時にブラウザの表示有無を切り替える設定を spec/support 以下に追加してください

??? 🤔 ???

このブラウザ表示の有無って辺りの言い回しがふんわりしててわからん。 なんとなくカピバラが関係してる感だけある

あ〜…

カピバラがドライバを設定してくれるので(現場Rails194p)

chome_headlessをドライバとして使ってくれるんやな(小並感)

下記記事参照

【暫定版】Rails 5.1のSystemTestCaseでHeadlessモードのChromeを使ってみる - Qiita

現場Rails(194p)にはgemの記載がないので調べてくうちに以下を発見したのでやってみましたが、「パスを通せ」っていう余計なエラーが出ます

group :test do
	gem 'capybara'
		  … # ↓ こいつ
  gem 'selenium-webdriver'

# エラー出るので以下の様に書きましょう

	gem 'webdrivers'

Rspecの設定(SystemSpecの導入、実行時にブラウザ表示、非表示の切り替え設定) · Issue #5 · diveintocode-corp/rails_exam01_bugfix

  • こんがらがってるけど脳死でsupportの下にcapybara.rb作って中に記載するよ

    RSpec.configure do |config| # 現場Rails194pに書いてある内容
      config.before(:each, type: :system) do
        driven_by :selenium_chrome_headless #GUIを起動しないでブラウザテストする
      end
    end
    

    (カピバラの動作を行う記述って意味でcapybara.rbというファイル名にしたんだと思うよ、だから別にhogehoge.rbでも良い)


headlessの意味


記事で書いてあるけどイメージつかん!ので以下

上の_headlessを記入しないで$ rspecをしちゃうとブラウザ立ち上がっちゃう。

f:id:michimo_10:20210607141015g:plain



_headless記入してるとブラウザは立ち上がらずにターミナルだけで完結する(エラーは気にしないで)

f:id:michimo_10:20210607141107g:plain



  • 疑問点

    現場Rails(194p)にはspec/spec_helper.rb内に require 'capybara/rspec' と書いてあるが、今回の課題にはそもそもcapybaraファイルがない。

    上のログイン処理内で作成したsupport/の下にcapybara.rbというファイルを作成し中に現場railsと同じ内容を記載したが、現場Rails真似て require 'capybara/rspec'の記載をしてターミナルでrspecコマンドを打つとエラーが出る(そもそもファイル無いから当たり前)

    今回のケースでは「ファイルが無いからrequire 'capybara/rspec'を読み込まなくてもいい」という判断をしたがspec_helper.rb内に記載せずに、わざわざsupport/capybara.rbに記載し、上の中でsupportを読み込むようにしたの何故だろうか。また何故capybaraファイルが無いのだろうか。と。


    解決への道


    • ちなみに現場Railsに書いてあった通りにやるとうまくいかなかった (triさんに助けて頂きました。ありがとうございます!)


実行するテストケースを限定できる設定を spec_helper.rb の50行目辺りを確認して有効化してください。

  • この辺のやつ

     

    f:id:michimo_10:20210607141341p:plainf:id:michimo_10:20210607141337p:plain



なぜか?理由は下の確認ポイント

・確認ポイントブラウザのON/OFFやテストケースのfocus実行などの設定を行っていること 

特定のテストケースを実行したい時のfocus: true - その辺にいるWebエンジニアの備忘録

↑あやふやなまま、なんでこれすんの?

>> 実務で膨大なテストがあるときに全てをテストすると時間がかかるので、自分がテストしたい部分だけをしたいときにfocasを使います



expectではなるべくDBへのアクセスを行わないこと(テスト実行時の時間的コストがかかるため)

これの意味するのは「letを使え」という事。

理由としては以下の記事内にある *現場Railsならchapter5-11(212p)

RSpecのletを使うのはどんなときか?(翻訳) - Qiita


🌱 🦤...🌱 🦤...🌱 🦤...🌱 🦤...🌿🐥 ...🌱 🦤...🌱 🦤...🌱 🦤...🌱 🦤...🌱


弾かれた場所

ログアウトのテストが詰まった

describe 'ログイン後' do
    context 'ログアウトボタンをクリック' do
      it 'ログアウト処理が成功する' do
        login(user)
        click_link 'Logout'  # ← click_button としていた
        expect(page).to have_content 'Logged out'
        expect(current_path).to eq root_path
      end
    end
  end
end

「ボタンのタグ」か「リンクの部分なのか」というのもきちんと書き分けないと

👷👷👷👷👷👷👷👷👷👷👷👷👷🏿👷👷👷👷👷👷👷👷👷👷

Users ログイン前 ユーザー新規登録 登録済のメールアドレスを使用 ユーザーの新規作成が失敗する(3行目)

context '登録済のメールアドレスを使用' do
        it 'ユーザーの新規作成が失敗する' do
     ここ→ about_user = create(:user) # 違うユーザーで作成し
          visit new_user_path # ユーザ新規作成ページにアクセス
     ここ→ fill_in 'Email', with: about_user.email # 違うユーザのメールがmodule内の既存ユーザのメールを使う
          fill_in 'Password', with: 'password'
          fill_in 'Password confirmation', with: 'password'
          click_button 'Update'
          expect(page).to have_content '1 error prohibited this user from being saved' # 表示されるエラー文
          expect(page).to have_content 'Email has already been taken' # 表示されるエラー文
          expect(current_path).to eq users_path # 新規作成ページにいること
          expect(page).to have_field 'Email', with: about_user.email # メールフィールド内に入力したメールアドレスが残っている
        end
      end

about_userという適当な変数作ってたけどこれじゃダメだった

existed_userという様にしないといけなかったのだが、existedと言いうのはRSpec内のマッチャと呼ばれる部分のメソッドであらかじめ用意されているものらしい(🍵このメソッドもたくさんある)

Method: RSpec::Matchers#exist


他の部分もマッチャやメソッドが原因だったりした🍵

context '登録済のメールアドレスを使用' do
          it 'ユーザーの編集が失敗する' do
            visit edit_user_path(user)
            about_user = create(:user) # ← ここ
            fill_in 'Email', with: about_user.email # ← ここ
            fill_in 'Password', with: 'hogepassword'
            fill_in 'Password confirmation', with: 'hogepassword'
            click_button 'Update'
            expect(page).to have_content("Email has already been taken")
            expect(page).to have_content('1 error prohibited this user from being saved:')
            expect(current_path).to eq user_path(user) # 失敗したらedeiできるけどidがないページであることを期待する
          end
        end

context '他ユーザーの編集ページにアクセス' do
          it '編集ページへのアクセスが失敗する' do
		        about_user = create(:user) # ← ここ
		        visit edit_user_path(abouot_user) # ← ここ
            expect(page).to have_content("Forbidden access.")
            expect(current_path).to eq user_path(user)
          end
        end
      end

ここでもabout_userとしていたがエラーが止まらない!しゅごい!

こういう場合はother_というメソッドを使うとよいので other_user に変更する

Method: RSpec::Expectations::MultipleExpectationsNotMetError#other_errors

さらにこんな感じでスクショも出してくれるので、どのあたりが間違っているかも一目瞭然

f:id:michimo_10:20210607141744p:plain



回答に書いた内容の説明(一部)


RSpec.describe 'UserSessions', type: :system do
  let(:user) { FactoryBot.create(:user) } #letでuserを定義します。その内容はFactoryBotのuserをcreateします

  describe 'ログイン前' do
    context 'フォームの入力値が正常' do
      it 'ログイン処理が成功する' do
        visit login_path #ログイン画面にアクセスします
        fill_in 'Email', with: user.email # letのuserのメールを渡します
        fill_in 'Password', with: 'password' # letのuserのpasswordを渡します
        click_button 'Login' # Loginボタンをクリックします
        expect(page).to have_content 'Login successful' # 画面(page)に'Login successful'があるのを期待します
        expect(current_path).to eq root_path # 今いる場所はroot(/) であるのを期待します
      end
    end
…
…
describe 'タスク編集' do
      let!(:task) { create(:task, user: user) } # beforeより先に読み込んで欲しい
      let(:other_task) { create(:task, user: user) }
      before { visit edit_task_path(task) }
…
…
context '他ユーザーの編集ページにアクセス' do
          it '編集ページへのアクセスが失敗する' do
            other_user = create(:user) # 違うユーザーで作成し
            visit edit_user_path(other_user) # 違うユーザーページにアクセスし
            expect(page).to have_content("Forbidden access.") # エラーが表示され
            expect(current_path).to eq user_path(user) # 失敗し自分のページであること期待する
          end
        end
…
…

.rspec内に

--format documentation
  • 記述がない場合

    すげーシンプルにテストしてくれる

    f:id:michimo_10:20210607141911p:plain



  • 記載がある場合

    describe、context、itの部分を丁寧に表示してくれる

    f:id:michimo_10:20210607141934p:plain




  • 後になって気付いたこと

    ヒントに書いてあったよ…

    テスト実行時のブラウザについて

    rails generate rspec:system で作成されるファイルでは driven_by で実行時のブラウザを指定しています。

    f:id:michimo_10:20210607142016p:plain



    この記載を削除し、他のファイルでブラウザの指定を一元管理してしまいましょう。

    f:id:michimo_10:20210607142039p:plain



今なら上の意味が少し理解できる。ナンだったんだ…🍛

自分で書くのは辛いです

 

FactoryBotのシーケンス

※振り返れば「だからそうだっていってんじゃん!」と怒られてしまうようなしょうもない事です。

今回記載するのはModel specと呼ばれるモデルに関するテストの備忘録

 

RSpecの基本において参考にしたのは以下

使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita

最初はなんだか分からんかったがQiitaの記事を見たり現場Railsを読み、構文を見ながらダラダラと3日目。

理解が先

そんな中で出てきた

FactoryBot.define do
  factory :task do

    sequence(:title, "title_1") # ← これ
    content { "content" }
    status { :todo }
    deadline { 1.week.from_now }
    association :user
  end
end

sequence(シーケンス)

こいつが分からず詰まった。

他のcontent,status,deadline,associationは分かるのよ。コントローラーとモデルの中に書いてあるから。

タイトルはtitleじゃダメなの?ってところ

・前提として


まず、シーケンスとはなんぞや?

thoughtbot/factory_bot

翻訳すると、

グローバルシーケンス 特定の形式のユニークな値(例えば、電子メールアドレス)は、シーケンスを使って生成できます。シーケンスを定義するには、定義ブロックの中でsequenceを呼び出し、シーケンス内の値を生成するにはgenerateを呼び出します。

# 新しいシーケンスの定義
FactoryBot.define do
  sequence :email do |n|
    "person#{n}@example.com"
  end
end

generate :email
# => "person1@example.com"

generate :email
# => "person2@example.com"

# 上記のようにnの部分の数が増えている

以下も参照

FactoryBot(FactoryGirl)チートシート - Qiita

インクリメントとは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

モデルのvalidate内にuniqueness: trueというユニークの記載がある場合はこのシーケンスとやらを使えば良いようだ。

・疑問な点


しかし、解説には

# 元々書いてあったものが
sequence(:title) { |n| "title_#{n}" }

						 ↓ ↓

# この様に変更していた
sequence(:title, "title_1")
#       ↑第一引数  ↑ 第二引数

理由としては

sequenceの第二引数で定義した文字にはループの度にString#nextを末尾の文字に実行される

String#next (Ruby 3.0.0 リファレンスマニュアル)

とリンク先を見るも、いまいち腹落ちせず…

???🤔 ???

こういった記事は見つけたので第二引数の事は分かったが

FactoryBot (旧FactoryGirl) の sequence と .next - Qiita

なぜ "title_1" で認識してくれるのだろうか?

【そういうもの】と思うしかないのか?

という疑問が浮かんだ。

あるslackに質問を投げてみたところ

String#next でnの部分を読んで(呼んで?)くれるコードを教えてもらった

thoughtbot/factory_bot

他の方からも

・「個人的にはブロックを使う方を好む」 ・「個人的には、上の方が何が起こるかを予想しやすくて良い」

などと反応があった。

・解決への光明


そんな中、とある方がconsoleで

~ irb
irb(main):001:0> 'title_1'.next
=> "title_2"

というString#nextの動きを検証してくれたのをきっかけに私の腑に落ちるのが始まる。

先程のString#next (Ruby 3.0.0 リファレンスマニュアル)の動きをやってくれたわけだ。

それを考慮して自分でやってみるとこうなった

Loading development environment (Rails 5.2.3)
[1] pry(main)> 'title_1'.next
=> "title_2"  # titleは数字が変わる
[2] pry(main)> 'user_#{n}@example.com'.next
=> "user_\\#{n}@example.con" # メールでは数字ではなくmがnになった
[3] pry(main)> 'user_{n}@example.com'.next
=> "user_{n}@example.con"
[4] pry(main)> 'user_n@example.com'.next
=> "user_n@example.con"

なので、先程のFactoryBot (旧FactoryGirl) の sequence と .next - Qiitaにある

ブロックを渡さずに第二引数を渡すと、 .next が呼ばれるようになっている

となっていたので、メールを第一引数として.nextを呼んだわけだが、メールはnの部分の数字ではなく末尾の.comのmが.conのnに変わった。

つまり、.nextを呼んでもメールの中の数字が変わることがない(末尾じゃないから)

しかし、title_1を第二引数にしても2に変わる(末尾だから)

答えはすごくシンプルで

title_1の場合は末尾だから数字が(+1)変わる

メールは変えたい部分が末尾じゃないから途中のnの部分は変わらない。だからメールはブロック化して書いている。

なので"title_1"の書き方は

変えたい場所が末尾の場合のみ使える書き方

これで腑に落ちました。

振り返ると**"何でこんなに悩んでたんだろう"**と情けなくなります…笑

🙏

Railsで管理画面 - ユーザー 編

前回の続き

michimo-10.hatenablog.com

・コントローラー


adminの時はbase_controllerを使っているので、今まで使っていたApplicationControllerで作ったboards_controllerやuser_controller。。。などは引き継がれない(引っ張ってくることが出来ない)

つまり、もう一度boards_controllerやuser_controllerを定義し直さなければならない

とは言え一度作成しているので使いたい中身はほぼ同じ。ただ挙動を変える必要があるくらいだ。

また、今課題には今までに無かったuserの一覧画面の作成があるのでuserの情報を取って来なければならない。その辺りはコントローラーで明記し、ビューで表示させる。

まずは管理者用のboardsコントローラーの作成




続いてboardと同じようにusers_controllerを作成

rails g controller admin::Users # この時オプションでビューも作っていいがビューは後から個別で作る

出来上がったら一覧表示も作りたいのでboardsの中身身をコピペし部分ごとに編集していく

・自分の回答 # controllers/admin/users_controller.rb

class Admin::UsersController < Admin::BaseController
  before_action :login_user, only: %i[edit show update destroy]

  def index
    @search = User.ransack(params[:q]) # ↓ includeはしていない
    @users = @search.result(distinct: true).order(created_at: :desc).page(params[:page])
  end

  def show; end

  def edit; end

  def update
    if @user.update(user_params)
      redirect_to admin_user_path, success: t('defaults.message.updated', item: User.model_name.human)
    else             # ↑ パスに注意                                               ↑ 各所翻訳も注意
      flash.now[:danger] = t('defaults.message.not_updated', item: User.model_name.human)
      render :edit
    end
  end

  def destroy
    @user.destroy!
    redirect_to admin_users_path, success: t('defaults.message.deleted', item: User.model_name.human)
  end             # ↑ パスに注意

  private

  def login_user
    @user = User.find(params[:id])
  end       # ↑で userのidを@userに渡している

  def user_params
    params.require(:user).permit(:email, :first_name, :last_name, :avatar, :role)
  end      # :roleを追加(管理者か一般かを判断する為)
end

・ビュー


コントローラーでuserのidを取ってくることが出来たので、 boardと同じよう ・一覧(index),詳細(show),編集(edit)ページを作成する。 ・ファイルとフォルダは新たに作成しadmin下にすること。

一覧表示と言っても今までのページの表示ではなく管理者用のページなのでリスト化された物で良い 課題詳細にあったように

ユーザー一覧では、ID、フルネーム、権限(管理者 or 一般)の項目を表示

が出来れば良い。

しかし権限が一番大変

board同様に参考にするのは以下のBootstrap

Tables

Buttons

一覧画面(index)


・自分の回答 # views/admin/users/index.html.erb

<%= content_for(:title, t('.title')) %>
  <h1><%= t('.title') %></h1>
<div class="col-lg-10 offset-lg-1">
  <!-- 検索フォーム -->
  <form>
  <%= render 'search_form', url: admin_users_path, search: @search %>
  </form>                           # ↑ パスに注意
</div>
<table class="table table-striped">
  <thead>
    <tr>
      <th scope="col">id</th>
      <th scope="col">フルネーム</th>
      <th scope="col">権限</th>
      <th scope="col">作成日時</th>
    </tr>
  </thead>
  <tbody>
    <%= render @users %> #パーシャル作成
  </tbody>
</table>

boardと同じようにページネーションがあり、翻訳もしっかり充てられている

パーシャル部分


・自分の回答 # views/admin/users/_user.html.erb

<tr>
  <th scope="row"><%= user.id %></th>
  <td>
    <%= link_to user.decorate.full_name, admin_user_path(user) %>
  </td>
  <td>
    <%= user.role_i18n %> # 後述するrole_helpのgem
  </td>
  <td>
    <%= l user.created_at, format: :long %>
  </td>
  <td>
    <%= link_to t('defaults.edit'), edit_admin_user_path(user), id: "button-edit-#{user.id}", class:"btn btn-success" %>
  </td>
  <td>
    <%= link_to t('defaults.delete'), admin_user_path(user), id: "button-delete-#{user.id}", class: "btn btn-danger",
        method: :delete, data: {confirm: t('defaults.message.d_confirm')} %>
  </td>    # パスに注意
</tr>

検索のパーシャル


boardとほぼ同じだが、日付の代わりに入れた権限の:role_eq部分は後述

・自分の書き方 # /views/admin/users/_search_form.html.erb

<div class="input-group mb-3">
  <%= search_form_for search, url: url do |f| %>
    <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:"検索ワード" %>
      </div>
      <div class="col-auto">
        <%= f.select :role_eq, User.roles_i18n.invert.map{|key, value| [key, User.roles[value]]}, { include_blank: t('defaults.not_select') }, class:"form-control" %>
      </div>
      <div class="col-auto">
    <%= f.submit class: "btn btn-primary input-group-append" %>
      </div>
    </div>
  <% end %>
</div>

form_forで使用できるhtmlタグ

f.selectやf.date_selectを使うことで選択肢や日付選択ボックスを生成してくれる。 よく見かけるf.submitが送信ボタンの生成してくれる様に。

form_forの使い方をマスターしよう!

セレクトボックスの書き方は以下

【開発メモ】Ruby on Railsのform_forでドロップダウンリストの選択ボックスを設置する方法 | FREE SWORDER

詳細(show)


最初は権限がわからなかった(後述) レイアウトは元々のapp/views/profiles/show.html.erbを参照し権限の表示部分を追加

・自分の回答 # views/admin/users/show.html.erb

<%= content_for(:title, t('.title')) %>
<div class="container pt-3">
  <div class="row">
    <div class="col-md-10 offset-md-1">
      <h1 class="float-left mb-5"><%= t('.title') %></h1>
      <%= link_to t('defaults.delete'), admin_user_path, id: 'button-delete-#{@user.id}', class: 'btn btn-danger float-right',
        method: :delete, data: {confirm: t('defaults.message.d_confirm')} %>  # この上下のlink_toは編集と削除ボタン
      <%= link_to t('defaults.edit'), edit_admin_user_path, id: "button-edit-#{@user.id}", class:"btn btn-success float-right" %>
      <table class="table">
        <tr>
          <th scope="row"><%= User.human_attribute_name(:id) %></th>
          <td><%= @user.id %></td>
        </tr>
        <tr>
          <th scope="row"><%= User.human_attribute_name(:role) %></th>
          <td><%= @user.role_i18n %></td>  # この権限を表示する部分に悩んだ
        </tr>

        <tr>
          <th scope="row"><%= User.human_attribute_name(:email) %></th>
          <td><%= @user.email %></td>
        </tr>
        <tr>
          <th scope="row"><%= User.human_attribute_name(:full_name) %></th>
          <td><%= @user.decorate.full_name %></td>
        </tr>
        <tr>
          <th scope="row"><%= User.human_attribute_name(:avatar) %></th>
          <td><%= image_tag @user.avatar.url, size: '50x50' %></td>
        </tr>
      </table>
    </div>
  </div>
</div>

権限(role)

今回追加になった権限という部分をセレクトボックスを使い、更にi18n対応させるという課題

Gemfileに

gem 'enum_help'

を追加しbundle install 詳しいやり方は以下

【Rails】enum_helpを用いてi18n対応セレクトボックスを作成 - Qiita

編集(edit)

・自分の回答 # views/admin/users/edit.html.erb

<%= content_for(:title, t('.title')) %>
<div class="container">
  <div class="row">
    <div class="col-lg-8 offset-lg-2">
      <h1><%= t('.title') %></h1>
      <%= form_with model: [:admin, @user], url: admin_user_path, local: true do |f| %>
        <%= render 'layouts/error_messages', object: f.object %>
        <div class="form-group">
          <%= f.label :email %>
          <%= f.email_field :email, class:"form-control" %>
        </div>
        <div class="form-group">
          <%= f.label :last_name %>
          <%= f.text_field :last_name, class: "form-control" %>
        </div>
        <div class="form-group">
          <%= f.label :first_name %>
          <%= f.text_field :first_name, class:"form-control" %>
        </div>
        <div class="form-group">
          <%= f.label :avatar %>
          <%= f.file_field :avatar, onchange: 'previewImage()', class: 'form-control mb-3', accept: 'image/*' %>
          <%= f.hidden_field :avatar_cache %>
        </div>
        <div class='mt-3 mb-3'>
          <%= image_tag @user.avatar.url,
                        id: 'preview',
                        size: '100x100' %>
        </div>
        <div class="form-group">
          <%= f.select :role, User.roles_i18n.invert, {}, class:"form-control" %>
        </div>
        <div class="actions">
          <%= f.submit class:"btn btn-primary" %>
        </div>
      <% end %>
    </div>
  </div>
</div>

モデル部分の記載が違ったがそれ以外はほぼ同じ。元々のadminじゃない方のeditフォームをコピペしたのであまり大差はない。

エラーメッセージのファイルは自分はlayoutsに入れてるのでエラーは出なかった

Railsで管理画面 - 掲示板 編

前回の続き

michimo-10.hatenablog.com

 

・コントローラー


adminの時はbase_controllerを使っているので、今まで使っていたApplicationControllerで作ったboards_controllerやuser_controller。。。などは引き継がれない(引っ張ってくることが出来ない)

つまり、もう一度boards_controllerやuser_controllerを定義し直さなければならない

とは言え一度作成しているので使いたい中身はほぼ同じ。ただ挙動を変える必要があるくらいだ。

また、今課題には今までに無かったuserの一覧画面の作成があるのでuserの情報を取って来なければならない。その辺りはコントローラーで明記し、ビューで表示させる。


rails g controller admin::Boards # この時オプションでビューも作っていいがビューは後から個別で作る

今まで作ってきた元々のboardsコントローラーの中身を上で作ったコントローラー内に移植し部分修正

・自分の回答 # controllers/admin/boards_controller

class Admin::BoardsController < Admin::BaseController
  before_action :set_params, only: %i[edit show update destroy]
  def index # ↓回答例と違う変数なのでindexのビューで呼ぶ時は@searchを使う
    @search = Board.ransack(params[:q])  #bookmarkは要らない↓と思ったのでuserだけ
    @boards = @search.result(distinct: true).includes(%i[user]).order(created_at: :desc).page(params[:page])
  end

  def show; end # 

  def edit; end

  def update
    if @board.update(board_params)
      redirect_to admin_board_path, success: t('defaults.message.updated', item: Board.model_name.human)
    else        #  ↑ パスを変更
      flash.now[:danger] = t('defaults.message.not_updated', item: Board.model_name.human)
      render :edit
    end
  end

  def destroy
    @board.destroy!
    redirect_to admin_boards_path, success: t('defaults.message.deleted', item: Board.model_name.human)
  end        #  ↑ パスを変更

  private

  def set_params
    @board = Board.find(params[:id]) # ここで詰まってた
  end

  def board_params
    params.require(:board).permit(:title, :body, :board_image, :board_image_cache)
  end
end
  • ・回答例 # controllers/admin/boards_controller.rb

    class Admin::BoardsController < Admin::BaseController
       before_action :set_board, only: %i[edit update show destroy]
     
       def index
         @q = Board.ransack(params[:q])
         @boards = @q.result(distinct: true).includes(:user).order(created_at: :desc).page(params[:page])
       end
     
       def edit; end
     
       def update
         if @board.update(board_params)
           redirect_to admin_board_path(@board), success: t('defaults.message.updated', item: Board.model_name.human)
         else
           flash.now['danger'] = t('defaults.message.not_updated', item: Board.model_name.human)
           render :edit
         end
       end
     
       def show; end
     
       def destroy
         @board.destroy!
         redirect_to admin_boards_path, success: t('defaults.message.deleted', item: Board.model_name.human)
       end
     
       private
     
       def set_board
         @board = Board.find(params[:id])
       end
     
       def board_params
         params.require(:board).permit(:title, :body, :board_image, :board_image_cache)
       end
     end
    

・ビュー


コントローラーでboardのidを取ってくることが出来たので、

・一覧(index),詳細(show),編集(edit)ページを作成する。 ・ファイルとフォルダは新たに作成しadmin下にすること。

一覧表示と言っても今までのページの表示ではなく管理者用のページなので画像は必要なく、リスト化された物で課題詳細にあったように

掲示板一覧では、ID、タイトル、作成者、作成日の項目を表示

が出来れば良い。

参考にするのは以下のBootstrap

Tables

Buttons

一覧画面(index)


・自分の回答 # views/admin/boards/index.html.erb

<%= content_for(:title, t('.title')) %>  # ページタイトル部分
  <h1><%= t('.title') %></h1> # タイトル部分は元々のビューから引っ張ってきた
<div class="col-lg-10 offset-lg-1">
  <!-- 検索フォーム -->
  <form>
  <%= render 'search_form', url: admin_boards_path, search: @search %>
  </form> # ↑ 検索フォームもパーシャルにした、パスも気をつける事 - 後述
</div>
<table class="table table-striped">
  <thead>
    <tr>
			<th scope="col">id</th>
      <th scope="col">タイトル</th>
      <th scope="col">作成者</th>
      <th scope="col">作成日時</th>
    </tr>
  </thead>
  <tbody>
    <%= render @boards %> # パーシャル作った
  </tbody>
</table>
  • ・回答例 # views/admin/boards/index.html.erb

    <% content_for(:title, t('.title')) %>
     <div class="container mb-5 pt-2">
       <h1><%= t('.title') %></h1>
       <div class="row">
         <div class="col-md-12 mb-3">
           <%= render 'search_form' %>
         </div>
       </div>
       <div class="row">
         <div class="col-sm-12">
           <table class="table table-striped">
             <thead>
             <tr>
               <th scope="col"><%= Board.human_attribute_name(:id) %></th>
               <th scope="col"><%= Board.human_attribute_name(:title) %></th>
               <th scope="col"><%= Board.human_attribute_name(:user) %></th>
               <th scope="col"><%= Board.human_attribute_name(:created_at) %></th>
               <th scope="col"></th>
             </tr>
             </thead>
             <tbody>
             <%= render @boards %>
             </tbody>
           </table>
         </div>
       </div>
       <div class="row">
         <div class="col-sm-12">
           <!-- ページネーション -->
           <%= paginate @boards %>
         </div>
       </div>
     </div>
    

回答例にはページネーションが付いてた。 また、<th>タグの中の文字もきちんとja.ymlに記載しているが故の書き方になっている(activerecordの方) 検索のパーシャル部分もスマートになっている

パーシャル部分


・自分の回答 # views/admin/boards/_board.html.erb

<tr>
  <th scope="row"><%= board.id %></th>
  <td>
  <%= link_to board.title, admin_board_path(board) %>
  </td>   # パスに注意
  <td>
  <%= board.user.decorate.full_name %>
  </td>
  <td>
  <%= l board.created_at, format: :long %>
  </td>
  <td>
  <%= link_to '編集', edit_admin_board_path(board), class:"btn btn-success", id: "button-edit-#{board.id}" %>
  </td>
  <td><%= link_to '削除', admin_board_path(board), id: "button-delete-#{board.id}", class:"btn btn-danger",
        method: :delete, data: {confirm: t('defaults.message.d_confirm')} %>
      </td>
</tr>
  • ・回答例 # views/admin/boards/_board.html.erb

    <tr>
       <td>
         <%= board.id %>
       </td>
       <td>
         <%= link_to board.title, admin_board_path(board) %>
       </td>
       <td>
         <%= board.user.decorate.full_name %>
       </td>
       <td>
         <%= l board.created_at, format: :long %>
       </td>
       <td>
         <%= link_to t('defaults.edit'), edit_admin_board_path(board), class: 'btn btn-success' %>
         <%= link_to t('defaults.delete'), admin_board_path(board), method: :delete, data: { confirm: t('defaults.message.delete_confirm') }, class: 'btn btn-danger' %>
       </td>
     </tr>
    

検索のパーシャル


・自分の書き方 # /views/admin/boards/_search_form.html.erb

<div class="input-group mb-3">
  <%= search_form_for search, url: url do |f| %>
    <div class="form-inline align-items-center mx-auto">
      <div class="col-auto">
        <%= f.search_field :title_or_body_cont, class:"form-control", placeholder:"検索ワード" %>
      </div>
      <div class="col-auto">
      <%= f.date_field :created_at_gteq, include_blank: true, class: 'form-conrol' %>
        <span>~</span>
      <%= f.date_field :created_at_lteq_end_of_day, include_blank: true, class: 'form-conrol' %>
      </div>
      <div class="col-auto">
      <%= f.submit class:"btn btn-primary input-group-append" %>
      </div>
    </div>
  <% end %>
</div>
  • ・回答例 # /views/admin/boards/_search_form.html.erb

    <%= search_form_for @q, url: admin_boards_path do |f| %>
       <div class="row">
         <div class="form-inline align-items-center mx-auto">
           <div class="col-auto">
             <%= f.search_field :title_or_body_cont, class: 'form-control', placeholder: t('defaults.search_word') %>
           </div>
           <div class="col-auto">
             <%= f.date_field :created_at_gteq, class: 'form-control' %>
             <span>〜</span>
             <%= f.date_field :created_at_lteq_end_of_day, class: 'form-control' %>
           </div>
           <div class="col-auto">
             <%= f.submit class: 'btn btn-primary' %>
           </div>
         </div>
       </div>
     <% end %>
    

後述するが :created_at_gteq と言う部分が、今回の検索機能の「〜から〜まで」の部分のメソッドになる

form_forで使用できるhtmlタグ

f.selectやf.date_selectを使うことで選択肢や日付選択ボックスを生成してくれる。 よく見かけるf.submitが送信ボタンの生成してくれる様に。

form_forの使い方をマスターしよう!

セレクトボックスの書き方は以下

【開発メモ】Ruby on Railsのform_forでドロップダウンリストの選択ボックスを設置する方法 | FREE SWORDER

詳細(show)


・自分の回答 # views/admin/boards/show.html.erb

<% content_for(:title, @board.title) %>
 <div class="container pt-5">
   <div class="row mb-3">
     <div class="col-lg-8 offset-lg-2">
       <h1><%= t('.title') %></h1>
       <!-- 掲示板内容 -->
       <article class="card">
         <div class="card-body">
           <div class='row'>
             <div class='col-md-3'>
               <%= image_tag @board.board_image.url, class: 'card-img-top img-fluid', size: '300x200' %>
             </div>
             <div class='col-md-9'>
               <h3 class="d-inline"><%= @board.title %></h3>
               <%= render 'crud_menus', board: @board %>  # 元々の掲示板同様にアイコンの編集削除のパーシャルを作った
               <ul class="list-inline">
                 <li class="list-inline-item">by <%= @board.user.decorate.full_name %></li>
                 <li class="list-inline-item"><%= l @board.created_at, format: :long %></li>
               </ul>
             </div>
           </div>
           <p><%= simple_format(@board.body) %></p>
         </div>
       </article>
     </div>
   </div>
</div>

※自作のパーシャル部分 # admin/boards/_crud_menus.html.erb

<ul class='crud-menu-btn list-inline float-right'>
  <li class="list-inline-item">
    <%= link_to t('defaults.edit'), edit_admin_board_path(board), id: "button-edit-#{board.id}" %>
  </li>
  <li class="list-inline-item">
    <%= link_to t('defaults.delete'), admin_board_path(board), id: "button-delete-#{board.id}", method: :delete, data: {confirm: t('defaults.message.d_confirm')} %>
  </li>
</ul>
  • ・回答例 # views/admin/boards/show.html.erb

    <% content_for(:title, @board.title) %>
     <div class="container">
       <div class="row">
         <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
           <h1><%= t('.title') %></h1>
           <div class="text-right mb-3">
             <%= link_to t('defaults.edit'), edit_admin_board_path(@board), class: 'btn btn-success' %>
             <%= link_to t('defaults.delete'), admin_board_path(@board), method: :delete, data: { confirm: t('defaults.message.delete_confirm') }, class: 'btn btn-danger' %>
           </div>
           <table class="table table-bordered bg-white">
             <tr>
               <th scope="row"><%= Board.human_attribute_name(:id) %></th>
               <td><%= @board.id %></td>
             </tr>
             <tr>
               <th scope="row"><%= Board.human_attribute_name(:title) %></th>
               <td><%= @board.title %></td>
             </tr>
             <tr>
               <th scope="row"><%= Board.human_attribute_name(:user) %></th>
               <td><%= @board.user.decorate.full_name %></td>
             </tr>
             <tr>
               <th scope="row"><%= Board.human_attribute_name(:body) %></th>
               <td><%= @board.body %></td>
             </tr>
             <tr>
               <th scope="row"><%= Board.human_attribute_name(:created_at) %></th>
               <td><%= l @board.created_at, format: :long %></td>
             </tr>
           </table>
         </div>
       </div>
     </div>
    

回答例の方が綺麗に書かれているし編集や削除ボタンもある。 レイアウトは参考程度にし、自分のも機能的には問題なかった。

・編集(edit)


これも元々の編集ファイルからコピーしたものにパスを変えただけの様なものになった

・自分の回答 # /views/admin/boards/edit.html.erb

<%= content_for(:title, @board.title) %>
  <h1><%= @board.title %></h1> # ↓ :adminを追加している
<%= form_with model: [:admin, @board], local: true do |f| %>
  <%= render 'layouts/error_messages', object: f.object %>
  <div class="form-group">
    <%= f.label :title %>
    <%= f.text_field :title, class: "form-control" %>
  </div>
  <div class="form-group">
    <%= f.label :body %>
    <%= f.text_area :body, rows: 10, class:"form-control" %>
  </div>
  <div class="form-group">
    <%= f.label :board_image %>
    <%= f.file_field :board_image, onchange: 'previewImage()', class: 'form-control mb-3', accept: 'image/*' %>
    <%= f.hidden_field :board_image_cache %>
  </div>
  <div class='mt-3 mb-3'>
    <%= image_tag @board.board_image.url,
                  id: 'preview',
                  size: '300x200' %>
  </div>
  <div class="actions">
  <%= f.submit class:"btn btn-primary" %>
  </div>
<% end %>
  • ・回答例 # views/admin/boards/edit.html.erb

    <% content_for(:title, @board.title) %>
     <div class="container">
       <div class="row">
         <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
           <h1><%= t('.title') %></h1>
           <%= form_with model: @board, url: admin_board_path(@board), local: true do |f| %>
             <%= render 'shared/error_messages', object: f.object %>
             <div class="form-group">
               <%= f.label :title %>
               <%= f.text_field :title, class: 'form-control' %>
             </div>
             <div class="form-group">
               <%= f.label :body %>
               <%= f.text_area :body, class: 'form-control', rows: 10 %>
             </div>
             <div class="form-group">
               <%= f.label :board_image %>
               <%= f.file_field :board_image, onchange: 'previewFileWithId(preview)', class: 'form-control mb-3', accept: 'image/*' %>
               <%= f.hidden_field :board_image_cache %>
             </div>
             <div class='mt-3 mb-3'>
               <%= image_tag @board.board_image.url,
                             id: 'preview',
                             size: '300x200' %>
             </div>
             <%= f.submit class: 'btn btn-primary' %>
           <% end %>
         </div>
       </div>
     </div>
    

機能的には問題なくレイアウト違い

エラーメッセージのファイルは自分はlayoutsに入れてるのでエラーは出なかった

範囲検索(ransack)


ハッキリ言ってgithubの公式を見ただけで実装はできなかった。

activerecord-hackery/ransack

上のリンクの中にある Custom Predicates というところをクリックすると以下のコードがある

# config/initializers/ransack.rb

Ransack.configure do |config|
  config.add_predicate 'equals_diddly', # Name your predicate
    # What non-compound ARel predicate will it use? (eq, matches, etc)
    arel_predicate: 'eq',
    # Format incoming values as you see fit. (Default: Don't do formatting)
    formatter: proc { |v| "#{v}-diddly" }, # 
    # Validate a value. An "invalid" value won't be used in a search.
    # Below is default.
    validator: proc { |v| v.present? },
    # Should compounds be created? Will use the compound (any/all) version
    # of the arel_predicate to create a corresponding any/all version of
    # your predicate. (Default: true)
    compounds: true,
    # Force a specific column type for type-casting of supplied values.
    # (Default: use type from DB column)
    type: :string,
    # Use LOWER(column on database).
    # (Default: false)
    case_insensitive: true
end

f:id:michimo_10:20210607133249p:plain

ransack



この時は翻訳通してもイマイチ言ってることが分からず。

あと上部にコメントアウトしてる # config/initializers/ransack.rb ってなんなん?と思って

config/initializersの中を見たけどそんなファイル無いし

f:id:michimo_10:20210607133322p:plain



arel_predicateをgithub内で検索したり

activerecord-hackery/ransack

'ransack 日付 範囲' とか 'ransack arel_predicate' でググってたら既にまとめてくれている人がいました( ありがたや🙏 )

以下のページで'arel_predicate'と検索すると上の公式とかも出てくることろのちょい下に欲しいものがあった{第5章 044部分}

 

一応、上の中で出てきたlteqをgithub内で検索

activerecord-hackery/ransack

あ〜。。。 以下より小さいか等しい って意味ね。。。

かなり深いとこまで行かないとわかりませんでした。

逆に、 は以下より大きいか等しい となるとgteqに訳が充てられていたので、lteqgteqを使えば良さそうです(以下参照)

activerecord-hackery/ransack


ということで

猫Railsさんのページを見てransackファイルから作成します

config/initializers/ransack.rbが存在しない場合は追加(猫Railsから参照)

# config/initializers/ransack.rb
Ransack.configure do |config|
                       # 述語名
  config.add_predicate 'lteq_end_of_day',
                       # Arelの述語を指定。<=で検索したいからlteqを使うよ。
                       arel_predicate: 'lteq',
                       # インプットの整形。その日の終わりまでを検索対象に含めるよ。
                       formatter: proc { |v| v.end_of_day }
end

predicateは述語という意味です

ここまでで実装は完了。

以下は考察




検索のパーシャルに書かれていたものの中の下のピンク文字部分

<div class="col-auto">
  <%= f.date_field :created_at_gteq, class: 'form-control' %>
  <span>〜</span>
  <%= f.date_field :created_at_lteq_end_of_day, class: 'form-control' %>
</div>

これによって範囲を指定しているんですね。??

formatterの上のコメントアウト部分を翻訳すると

		# 入力される値を好きなようにフォーマットします。(デフォルト: フォーマットを行わない)
    formatter: proc { |v| "#{v}-diddly" },
    # 値を検証します。無効」な値は検索に使用されません。
    # 以下はデフォルトです。
    validator: proc { |v| v.present? },

ransack.rbにあるprocがオブジェクト化してくれているおかげ、と解釈しています

formatter: proc { |v| v.end_of_day }

更に、created_at_lteqだと0:00が基準になってしまうので上記でカスタマイズしたということです。

end_of_day はなんで使えるの?

end_of_dayはrailsのメソッドです 23:59:999999までを認識してくれます

Time




沼にハマったので先に言っておくと

中の動きを知る事も大事だがその機能を使うことが出来れば良い



極論言えば、「動けばいい」=͟͟͞͞( •̀д•́)



変数 | v | の v は、正直なんでも良いが、

よく見る| i | や | f | などの i は index の i で、f はform の f というようになっている

肝心のvは忘れた。すまん


以下、殴り書き

|v|はformatterの引数

formatter: proc { |v| v.〇〇} # の〇〇に代入すれば、lteqをend_of_dayに置き換えることができる。

add_predicateで検索の動作、formatterで入力値の変換

config.add_predicate 'to_age_lt',
  arel_predicate: 'gteq',
  formatter: -> (v) { (v.years.ago + 1.day).to_date },
  type: :integer,
  compounds: false

Ransack で年齢を検索する - Qiita

 

formatterで入力値を変換するので、

User.search(birthday_to_age_gteq: 18).result

これで検索する値が

18.years.ago + 1.day

18年前の今日、

2003/05/09

になる

rails cで見る

select * from users
where birthday >= '2003/05/09'