railsコマンドやrubyコマンドが使えなくなってしまった。。。
railsコマンドやrubyコマンドが使えなくなってしまった
開発中に[railsコマンド]や[rubyコマンド]急にが使えなくなってしまってかなり焦って、ハマってしまったことがあったので、こちらにまとめます。
[libvips]gemをインストールするのに以下を実行しました。
$ brew install vips
そうすると、いきなり下記のような状態になってしまった。。。。
$ higmonta@higuchimiyukiyuunoMacBook-Pro fishing_cooking % rails s /Users/higmonta/.rbenv/shims/rails: line 21: /usr/local/Cellar/rbenv/1.1.2/libexec/rbenv: No such file or directory $ higmonta@higuchimiyukiyuunoMacBook-Pro fishing_cooking % rails c /Users/higmonta/.rbenv/shims/rails: line 21: /usr/local/Cellar/rbenv/1.1.2/libexec/rbenv: No such file or directory $ higmonta@higuchimiyukiyuunoMacBook-Pro fishing_cooking % rails -v /Users/higmonta/.rbenv/shims/rails: line 21: /usr/local/Cellar/rbenv/1.1.2/libexec/rbenv: No such file or directory $ higmonta@higuchimiyukiyuunoMacBook-Pro fishing_cooking % ruby -v /Users/higmonta/.rbenv/shims/ruby: line 21: /usr/local/Cellar/rbenv/1.1.2/libexec/rbenv: No such file or directory
エラーメッセージより、以下のようにファイルやディレクトリがあるのかを確認した。
$ higmonta@higuchimiyukiyuunoMacBook-Pro shims % pwd /Users/higmonta/.rbenv/shims $ higmonta@higuchimiyukiyuunoMacBook-Pro shims % ls brakeman erb nokogiri rdoc ruby-rewrite sprockets bundle erubis oauth reek sass tapp bundler faker pry resque sass-convert thor byebug gem puma resque-scheduler scss tilt cap htmldiff pumactl resque-web scss-lint whenever capify irb racc ri sdoc wheneverize code_climate_reek kwalify rackup rspec sdoc-merge yard coderay launchy rails rubocop slim-lint yardoc coffeelint.rb ldiff rails_best_practices ruby slimrb yri dotenv listen rake ruby-parse spring $ higmonta@higuchimiyukiyuunoMacBook-Pro rbenv % pwd /usr/local/Cellar/rbenv $ higmonta@higuchimiyukiyuunoMacBook-Pro rbenv % ls 1.2.0
[/Users/higmonta/.rbenv/shims/rails]に関しては、ファイルが存在しているのが確認できました。
[/usr/local/Cellar/rbenv/1.1.2/libexec/rbenv]に関しては、[/usr/local/Cellar/rbenv]ディレクトリの下に、[1.2.0]しか存在しませんでした。
[rails]コマンドと[ruby]コマンドが使えなくなってしまったので、パスが通っているかを以下のように確認しました。
$ higmonta@higuchimiyukiyuunoMacBook-Pro fishing_cooking % which rails ~/.rbenv/shims/rails $ higmonta@higuchimiyukiyuunoMacBook-Pro fishing_cooking % which ruby ~/.rbenv/shims/ruby
パスは、通っているようですね。
rbenvでrubyがインストールされているかを以下のようにして確認しました。
$ higmonta@higuchimiyukiyuunoMacBook-Pro fishing_cooking % rbenv versions system * 2.5.1 (set by /Users/higmonta/workspace/fishing_cooking/.ruby-version) 2.6.3 2.6.4 2.6.5 2.6.6 3.0.0
結論と解決策
$ brew install vips
上記の際にrbenvが1.1.2から1.2.0に更新され、[rails]コマンドや[ruby]コマンドが1.1.2に依存しているパスで動いていたため、コマンドが使えなくなってしまった。
以下のように[rbenv rehash]をすると直る(rbenvのバージョンが変わってしまい、以前のバージョンのパスに依存しているrailsコマンドやrubyコマンドをrbenv rehashすることにより、~/.rbenv/shims/
にrailsコマンドやrubyコマンドがコピーされる)かもと思い以下を実行しました。
※[rbenv rehash]をすると~/.rbenv/versions/*/bin/
以下のファイル(Gemは、ここに格納される)を~/.rbenv/shims/
以下にコピーしてくれる。
コピーする理由としては、通常パスが通っているのは~/.rbenv/shims/
以下になっている為、こちらにコピーしないとGemが提供するコマンドが使えない為です。
通常開発中にGemをインストールして、~/.rbenv/versions/*/bin/
にGemが格納され、~/.rbenv/shims/
以下にコピーしないと使えないはずが使えるのは、Gemをインストールした時にrbenv rehash
も行ってくれている為です。
higmonta@higuchimiyukiyuunoMacBook-Pro fishing_cooking % rbenv rehash rbenv: cannot rehash: /Users/higmonta/.rbenv/shims/.rbenv-shim exists
上記を実行するとまたエラーになってしまいました。
rbenv rehash
を実行すると/Users/higmonta/.rbenv/shims/.rbenv-shim
が既に存在するとなってしまったので、このファイルを削除してから再度rbenv rehash
をしました。
higmonta@higuchimiyukiyuunoMacBook-Pro shims % rm ~/.rbenv/shims/.rbenv-shim shims % rbenv rehash
上記を実行したら、やっと通常通り[rails]コマンドや[ruby]コマンドが使えるようになりました!!
ポイント
rbenv rehash
のやっていること
通常[rails]コマンドやGemの提供するコマンドは、以下の部分に格納されます。
higmonta@higuchimiyukiyuunoMacBook-Pro bin % pwd /Users/higmonta/.rbenv/versions/2.5.1/bin higmonta@higuchimiyukiyuunoMacBook-Pro bin % ls bootsnap coderay listen racc rubocop scss bundle erb nokogiri rackup ruby slimrb bundler erb2slim oauth rails ruby-parse spring byebug gem pry rake ruby-rewrite sprockets chromedriver-helper html2slim puma rdoc sass thor chromedriver-update irb pumactl ri sass-convert tilt
上記の場合は、Rubyのバージョンが2.5.1の場合にその下にこのバージョンでインストールしたGemが格納される。
Rubyのバージョンが違う場合は、それに対応しているディレクトリ~/.rbenv/versions/(Rubyのバージョン)/bin
以下にインストールしたGemが格納される
通常パスが通っているのは、~/.rbenv/shims/
な為、このディレクトリ以下にファイルをコピーすることで、Gemが提供しているコマンドが使えるようになる
higmonta@higuchimiyukiyuunoMacBook-Pro shims % pwd /Users/higmonta/.rbenv/shims higmonta@higuchimiyukiyuunoMacBook-Pro shims % ls bootsnap coffeelint.rb launchy rails_best_practices ruby spring brakeman dotenv ldiff rake ruby-parse sprockets bundle erb listen rbs ruby-rewrite tapp bundler erb2slim nokogiri rdoc sass thor byebug erubis oauth reek sass-convert tilt cap faker pry resque scss typeprof capify gem puma resque-scheduler scss-lint whenever chromedriver-helper html2slim pumactl resque-web sdoc wheneverize chromedriver-update htmldiff racc ri sdoc-merge yard code_climate_reek irb rackup rspec slim-lint yardoc coderay kwalify rails rubocop slimrb yri
eval "$(rbenv init -)"
のやっていること
・(1) ~/.rbenv/shims
を環境変数PATHの先頭に追加する
・(2) コマンドの補完用のシェルスクリプトの読み込み
・(3) rbenv rehash の実行
・(4) shディスパッチャーをインストールする
参考記事
いまさらですがLinuxで環境変数を設定したい - Qiita
zshのmacでPATHを通す方法(設定ファイルに環境変数を加える) | 素人エンジニアの苦悩
f.number_fieldの初期値
f.number_fieldの初期値
f.number_fieldの初期値を設定する際に調べたことを自分の備忘録としてこちらにまとめます。
下記のようなセレクトボックスの場合に検索後に、検索に選択したセレクトボックスの内容をそのままにする際は、[selected]オプションを付与することで、クライアントから送られてきたパラメーターをサーバー側で受け取りその値をselectedに渡すことで、最初からセレクトボックスが選択されたHTMLをレスポンスすることで実現できた。(コードは、以下参照)
= form_with url: search_calculate_cooking_time_cooking_informations_path, local: true do |f| .form-group = f.collection_select :fish_kind, @fishes, :kind, :kind, { include_blank: '魚の種類(必須)', selected: params[:fish_kind] }, {class: 'search_select_form'} = f.collection_select :cooking_name, @cookings, :name, :name, { include_blank: '料理の種類(必須)', selected: params[:cooking_name]}, {class: 'search_select_form'} = button_tag type: 'submit', class: 'btn btn-primary search_button' do i.fas.fa-search
数値入力ボックスの場合も以下のようにサーバーにリクエストを送った後にレスポンスされてブラウザに表示される場合も数値入力の欄に入力した数値が残ったままにしたいと思ったが、ドキュメントを見たり記事を調べても[f.collection_select]の[selected]オプションのように初期値を設定するやり方が見つからなかった。
数値入力ボックスに値を入れてサーバーに送りレスポンスされると以下のように数値入力ボックスがリセットされて最初に入れた数値が残らない状態になる。
= form_with url: calculate_cooking_time_cooking_informations_path do |f| = render partial: 'shared/flash_message' .form-group = f.label :count, '調理する魚の数:' = f.number_field :count, min: 1, placeholder: '料理する魚の数(必須)', class: 'calculate-fish-count-form text-center' .form-group = f.label :let_foodstuff_capacity, '食材を寝かせられるキャパシティ:' = f.number_field :let_foodstuff_capacity, min: 1, step: 0.5, placeholder: '匹数(必須)', class: 'calculate-cooking-capacity-form mt-2 text-center' .form-group = f.label :cookware_capacity, '調理機材のキャパシティ:' = f.number_field :cookware_capacity, min: 1, step: 0.5, placeholder: '匹数(必須)', class: 'calculate-cooking-capacity-form mt-2 text-center' .form-group = hidden_field_tag :fish_kind, @fish_kind = hidden_field_tag :cooking_name, @cooking_name = f.submit '合計の料理時間は?', class: 'btn btn-primary calculate-cooking-time-button mt-2'
そもそも数値入力ボックスに値が入ったHTMLは、どのようになっているのか開発者ツールで確認したところ以下のようになっていた。
<input min="1" placeholder="料理する魚の数(必須)" value="5" class="calculate-fish-count-form text-center" type="number" name="count" id="count">
[value]属性に値を渡すことで、数値入力ボックスに値が入った状態になる。
上記のことから下記のように[value]オプションにクライアントから送られてきたパラメーターを渡すことで、[value]に値が入ったHTMLをレスポンスしてブラウザ上では、入力した数値が入ったままの状態になる。
= form_with url: calculate_cooking_time_cooking_informations_path do |f| = render partial: 'shared/flash_message' .form-group = f.label :count, '調理する魚の数:' = f.number_field :count, min: 1, placeholder: '料理する魚の数(必須)', value: params[:count], class: 'calculate-fish-count-form text-center' .form-group = f.label :let_foodstuff_capacity, '食材を寝かせられるキャパシティ:' = f.number_field :let_foodstuff_capacity, min: 1, step: 0.5, placeholder: '匹数(必須)', value: params[:let_foodstuff_capacity], class: 'calculate-cooking-capacity-form mt-2 text-center' .form-group = f.label :cookware_capacity, '調理機材のキャパシティ:' = f.number_field :cookware_capacity, min: 1, step: 0.5, placeholder: '匹数(必須)', value: params[:cookware_capacity], class: 'calculate-cooking-capacity-form mt-2 text-center' .form-group = hidden_field_tag :fish_kind, @fish_kind = hidden_field_tag :cooking_name, @cooking_name = f.submit '合計の料理時間は?', class: 'btn btn-primary calculate-cooking-time-button mt-2'
参考にした記事
セレクトボックスについて
セレクトボックスについて
セレクトボックスを実装する際にセレクトボックスについて詳しく調べたので、備忘録としてこちらにまとめます。
下記のようなセレクトボックスを実装した。
コードは、以下のようになっている。
[app/views/cooking_informations/_search_time_form.html.slim] = form_with url: search_calculate_cooking_time_cooking_informations_path, local: true do |f| .form-group = f.collection_select :fish_kind, @fishes, :kind, :kind, { include_blank: '魚の種類(必須)'}, {class: 'search_select_form'} = f.collection_select :cooking_name, @cookings, :name, :name, { include_blank: '料理の種類(必須)'}, {class: 'search_select_form'} = button_tag type: 'submit', class: 'btn btn-primary search_button' do i.fas.fa-search
セレクトボックスを下記のように検索すると検索後にセレクトボックスの状態が選択した状態にならずに、初期状態になってしまう。
これは、HTTPはステートレスなプロトコルな為である。
検索後にセレクトボックスを選択された状態にする為には、以下のように初期値を選択する[selected]を記述すればOKである。
[app/views/cooking_informations/_search_time_form.html.slim] = form_with url: search_calculate_cooking_time_cooking_informations_path, local: true do |f| .form-group = f.collection_select :fish_kind, @fishes, :kind, :kind, { include_blank: '魚の種類(必須)', selected: params[:fish_kind] }, {class: 'search_select_form'} = f.collection_select :cooking_name, @cookings, :name, :name, { include_blank: '料理の種類(必須)', selected: params[:cooking_name]}, {class: 'search_select_form'} = button_tag type: 'submit', class: 'btn btn-primary search_button' do i.fas.fa-search
上記のように記述することで、検索をした時にサーバーに検索の条件が投げられてHTMLファイルをレスポンスする時に、selectedの値がparamsで受け取った値(検索した時の条件)に初期状態が選択されてレスポンスされる。
ちなみに、セレクトボックスのそれぞれの箇所は以下のような意味になっている。
= f.collection_select :fish_kind, @fishes, :kind, :kind, { include_blank: '魚の種類(必須)', selected: params[:fish_kind] }, {class: 'search_select_form'}
最初の[:fish_kind]の部分:HTMLだと以下のname部分(サーバーで受け取る時には、params[:fish_kind]になる)
<select class="search_select_form" name="fish_kind" id="fish_kind"><option value="">魚の種類(必須)</option>
@fishesの部分:ブラウザの画面上に表示したりDBに送る値を配列やハッシュで記述する。
※配列かハッシュで記載する。
<option value="アジ">アジ</option> <option value="マメアジ">マメアジ</option> <option value="イカ">イカ</option> <option value="サバ">サバ</option> <option value="マハゼ">マハゼ</option> <option value="カサゴ">カサゴ</option> <option value="タチウオ">タチウオ</option> <option value="カワハギ">カワハギ</option> <option value="イシモチ">イシモチ</option> <option value="メジナ">メジナ</option> <option value="タコ">タコ</option>
最初の[:kind]の部分:DBに送られる値(@fishesのように配列を渡している部分の配列やハッシュの値(キーetc)から選択)
HTMLファイルだと以下のvalue部分になる。
<option value="アジ">アジ</option> <option value="マメアジ">マメアジ</option> <option value="イカ">イカ</option> <option value="サバ">サバ</option> <option value="マハゼ">マハゼ</option> <option value="カサゴ">カサゴ</option> <option value="タチウオ">タチウオ</option> <option value="カワハギ">カワハギ</option> <option value="イシモチ">イシモチ</option> <option value="メジナ">メジナ</option> <option value="タコ">タコ</option>
[:kind]の部分をidに変えるとHTMLが以下のようになり、fishのid値がvalueになり、この値がDBに送られる。
<option value="1">アジ</option> <option value="2">マメアジ</option> <option value="3">イカ</option> <option value="4">サバ</option> <option value="5">マハゼ</option> <option value="6">カサゴ</option> <option value="7">タチウオ</option> <option value="8">カワハギ</option> <option value="9">イシモチ</option> <option value="10">メジナ</option> <option value="11">タコ</option>
2番目の[:kind]の部分:ブラウザの画面上に実際に表示する部分(@fishesのように配列を渡している部分の配列やハッシュの値(キーetc)から選択)
[:kind]の部分を[:created_at]にするとHTMLは、以下のようになる。
<option value="アジ">2021-11-01 14:12:48 UTC</option> <option value="マメアジ">2021-11-01 14:12:48 UTC</option> <option value="イカ">2021-11-01 14:12:48 UTC</option> <option value="サバ">2021-11-01 14:12:48 UTC</option> <option value="マハゼ">2021-11-01 14:12:48 UTC</option> <option value="カサゴ">2021-11-01 14:12:48 UTC</option> <option value="タチウオ">2021-11-01 14:12:48 UTC</option> <option value="カワハギ">2021-11-01 14:12:48 UTC</option> <option value="イシモチ">2021-11-01 14:12:48 UTC</option> <option value="メジナ">2021-11-01 14:12:48 UTC</option> <option value="タコ">2021-11-01 14:12:48 UTC</option>
{ include_blank: '魚の種類(必須)', selected: params[:fish_kind] }の部分:オプション部分(セレクトボックスの最初の値を選んだり、最初に選択されている値を選択したりなどを決められる)
※include_blank: trueにすると以下のように先頭の値が空欄になる。(include_blankで文字列などを入れるとそれがセレクトボックスの先頭になる。)
※selectedで値を選択すると、最初からそれが選択された状態になる。(以下参照)
HTML上では、以下のように[selected]になっている。
<option selected="selected" value="アジ">アジ</option> <option value="マメアジ">マメアジ</option> <option value="イカ">イカ</option> <option value="サバ">サバ</option> <option value="マハゼ">マハゼ</option> <option value="カサゴ">カサゴ</option> <option value="タチウオ">タチウオ</option> <option value="カワハギ">カワハギ</option> <option value="イシモチ">イシモチ</option> <option value="メジナ">メジナ</option> <option value="タコ">タコ</option>
{class: 'search_select_form'}の部分:HTMLオプション
idやclassを付与できる部分
参考記事:
jQueryのhtmlメソッドとtextメソッドでハマったこと
jQueryのhtmlメソッドとtextメソッドでハマったこと
jQueryのhtmlメソッドとtextメソッドでハマったことがあったので、こちらにまとめます。
上記の[合計の料理時間は?]をクリックすると、非同期通信により以下ファイルがブラウザにレスポンスされるようにしている。
[app/views/cooking_informations/calculate_cooking_time.js.erb] let result_calculate_html = ` 料理時間の合計: <%= @handle_total_time %><br> 捌く時間の合計: 10<br> 調理時間の合計: 30<br> `; $("#js-result-calculate-cooking-time").text(result_calculate_html);
実際にクリックすると以下のようになり、htmlタグが文字として読み込まれてしまう。
これは、jQueryでtextメソッドを使っているため、htmlタグも文字として認識してしまう為である。
下記のようにjQueryでhtmlメソッドを使うと、引数の中がhtmlタグとして認識される為、期待通りの動きとなる。
[app/views/cooking_informations/calculate_cooking_time.js.erb] let result_calculate_html = ` 料理時間の合計: <%= @handle_total_time %><br> 捌く時間の合計: 10<br> 調理時間の合計: 30<br> `; $("#js-result-calculate-cooking-time").html(result_calculate_html);
参考記事
renderで指定したファイルがレンダリングする際にエラーになってしまう
renderで指定したファイルがレンダリングする際にエラーになってしまう現象
renderで指定したファイルがレンダリングする際にエラーになってしまうことがあり、ハマったのでこちらにまとめます。
以下のようなコードを記述していました。
[app/controllers/cooking_informations_controller.rb] def search_time ① @fishes = Fish.all @cookings = Cooking.all end def search_calculate_cooking_time ② @fishes = Fish.all @cookings = Cooking.all search_time_format = CookingSearchTimeForm.new(fish_kind: params[:fish_kind], cooking_name: params[:cooking_name]) if search_time_format.save ④ @fish_kind = params[:fish_kind] @cooking_name = params[:cooking_name] @handle_pattern = search_time_format.handle_search @cooking_information = search_time_format.cooking_information_search else flash.now[:notice] = "検索フォームを全て入力してくだい" render :search_time end end def calculate_cooking_time ③ calculate_cooking_time_format = CalculateCookingTimeForm.new(let_foodstuff_capacity: params[:let_foodstuff_capacity], cookware_capacity: params[:cookware_capacity], count: params[:count]) if calculate_cooking_time_format.save render template: "static_pages/top" else flash.now[:notice] = "計算フォームを全て入力してください" render :search_time end end
[app/forms/cooking_time_form.rb] include ActiveModel::Model include ActiveModel::Attributes attribute :fish_kind, :string attribute :cooking_name, :string validates :fish_kind, presence: true validates :cooking_name, presence: true def save invalid? ? false : true end def handle_search cooking_id = Cooking.find_by(name: cooking_name).id fish_id = Fish.find_by(kind: fish_kind).id handle_id = CookingInformation.find_by(cooking_id: cooking_id, fish_id: fish_id).handle_id Handle.find(handle_id).pattern end def cooking_information_search cooking_id = Cooking.find_by(name: cooking_name).id fish_id = Fish.find_by(kind: fish_kind).id CookingInformation.find_by(cooking_id: cooking_id, fish_id: fish_id) end
[app/forms/calculate_cooking_time_form.rb] include ActiveModel::Model include ActiveModel::Attributes attribute :let_foodstuff_capacity, :integer attribute :cookware_capacity, :integer attribute :count, :integer validates :let_foodstuff_capacity, presence: true validates :cookware_capacity, presence: true validates :count, presence: true def save invalid? ? false : true end
[app/views/cooking_informations/search_time.html.slim] .search-wrapper .container .row.justify-content-md-center .col-md-auto.mt-5 = render partial: 'search_time_form'
[app/views/cooking_informations/search_caluculate_cooking_time.html.slim] .search-wrapper .container .row.justify-content-md-center .col-md-auto.mt-5 = render partial: 'search_time_form' ⑤ .row.justify-content-md-center .col .mt-5.fs-3.cooking-list .cooking-information-title ' 料理名: = @cooking_name | ( = @fish_kind | )<br> ' 捌き方: = @handle_pattern .cooking-information-overview.mt-4 .d-inline-block = image_tag cooking_image_path(@cooking_information), size: '400x300', class: 'rounded-circle' .d-inline-block.float-end = form_with url: calculate_cooking_time_cooking_informations_path, local: true do |f| .form-group = f.label :count, '調理する魚の数:' = f.number_field :count, min: 1, placeholder: '料理する魚の数(必須)', class: 'calculate-fish-count-form text-center' .form-group = f.label :let_foodstuff_capacity, '食材を寝かせられるキャパシティ:' = f.number_field :let_foodstuff_capacity, min: 1, step: 0.5, placeholder: '匹数(必須)', class: 'calculate-cooking-capacity-form text-center' .form-group = f.label :cookware_capacity, '調理機材のキャパシティ:' = f.number_field :cookware_capacity, min: 1, step: 0.5, placeholder: '匹数(必須)', class: 'calculate-cooking-capacity-form text-center' = f.submit '合計の料理時間は?', class: 'btn btn-primary calculate-cooking-time-button' .d-flex.justify-content-center.mt-5#js-result-calculate-cooking-time ' 料理時間の合計: | ?<br> ' 捌く時間の合計: | ?<br> ' 調理時間の合計: | ? .cooking-information-details.mt-4 .d-inline-block | 捌き方の動画 .mt-3 iframe width="400" height="300" src="#{FishHandleInformation.find_by(fish_id: @cooking_information.fish_id, handle_id: @cooking_information.handle_id).handle_url}" .d-inline-block.float-end | 料理のレシピサイト<br> .mt-5 = link_to '調理レシピ', "#{@cooking_information.cooking_url}"
[app/views/cooking_informations/_search_time_form.html.slim] = form_with url: search_calculate_cooking_time_cooking_informations_path, local: true do |f| .form-group = f.collection_select :fish_kind, @fishes, :kind, :kind, { include_blank: '魚の種類(必須)' }, {class: 'search_select_form'} = f.collection_select :cooking_name, @cookings, :name, :name, { include_blank: '料理の種類(必須)' }, {class: 'search_select_form'} = button_tag type: 'submit', class: 'btn btn-primary search_button' do i.fas.fa-search
最初に①のアクションにより以下のような画面がレンダリングされる。
上記の画像の魚の種類と料理の種類を入力して検索すると、②のアクションにより以下のような画面がレンダリングされる。
上記の画像の(調理する魚の数)と(食材を寝かせられるキャパシティ)と(調理機材のキャパシティ)を入力して検索すると、③のアクションの下記より、
render template: "static_pages/top"
下記のような画面がレンダリングされる。
(調理する魚の数)と(食材を寝かせられるキャパシティ)と(調理機材のキャパシティ)のいずれかを入力しない場合は、④のバリデーションに引っかかり、以下の処理がされる。
flash.now[:notice] = "計算フォームを全て入力してください" render :search_calculate_cooking_time
renderで[search_calculate_cooking_time]アクションを指定しているので、以下の画面がレンダリングされるのを期待していたが、
実際は、以下のようなエラーになってしまう。
これは、HTTPプロトコルはステートレスによる通信による為である。
流れは以下のようになります。
上記の画面から(調理する魚の数)と(食材を寝かせられるキャパシティ)と(調理機材のキャパシティ)を入力してHTTPリクエストを送る。
↓
サーバー側では、レンダリングにより③のアクションが動く。
↓
バリデーションに引っかからない場合は、以下が処理される。
render template: "static_pages/top"
アプリケーションのトップページがレンダリングされる。
↓
バリデーションに引っかかる場合は、以下が処理される。
render :search_calculate_cooking_time
上記記述により、[app/views/cooking_informations/search_caluculate_cooking_time.html.slim]をレンダリングするファイルに指定する。
↓
[app/views/cooking_informations/search_caluculate_cooking_time.html.slim]の⑤の部分(以下参照)により、
= render partial: 'search_time_form'
[app/views/cooking_informations/_search_time_form.html.slim]がレンダリングするファイルに指定されているが、
このファイルの中には、
= form_with url: search_calculate_cooking_time_cooking_informations_path, local: true do |f| .form-group = f.collection_select :fish_kind, @fishes, :kind, :kind, { include_blank: '魚の種類(必須)' }, {class: 'search_select_form'} = f.collection_select :cooking_name, @cookings, :name, :name, { include_blank: '料理の種類(必須)' }, {class: 'search_select_form'} = button_tag type: 'submit', class: 'btn btn-primary search_button' do i.fas.fa-search
上記のように[@fishes]や[@cookings]などの配列が記載されているが、(調理する魚の数)と(食材を寝かせられるキャパシティ)と(調理機材のキャパシティ)を入力する際にHTTPリクエストを送るさいは、以下のアクションを通るため、
def calculate_cooking_time calculate_cooking_time_format = CalculateCookingTimeForm.new(let_foodstuff_capacity: params[:let_foodstuff_capacity], cookware_capacity: params[:cookware_capacity], count: params[:count]) if calculate_cooking_time_format.save render template: "static_pages/top" else flash.now[:notice] = "計算フォームを全て入力してください" render :search_calculate_cooking_time end end
[@fishes]や[@cookings]を取得していないので、エラーになる。
バリデーションに引っかからない際に、以下のレンダリングがエラーにならないのはトップページのビューファイルにインスタンス変数が記載されていない為である。
render template: "static_pages/top"
参考記事
collection_selectにインスタンス変数を渡すと、undefined method `map' for nil:NilClass が発生するエラー - Qiita
form_withの非同期通信
form_withの非同期通信
form_withの処理でハマってしまったことがあったので、こちらにまとめます。
以下のようなコードを記述していました。
[app/controllers/cooking_informations_controller.rb] def search_time @fishes = Fish.all @cookings = Cooking.all search_time_format = CookingSearchTimeForm.new(fish_kind: params[:fish_kind], cooking_name: params[:cooking_name], count: params[:count]) @fish = Fish.find_by(kind: params[:fish_kind]) end
[app/views/cooking_informations/search_time.html.slim] .search-wrapper .container .row.justify-content-md-center .col-md-auto.mt-5 = form_with url: search_time_cooking_informations_path do |f| .form-group = f.collection_select :fish_kind, @fishes, :kind, :kind, { include_blank: '魚の種類(必須)' }, {class: 'search_select_form'} = f.collection_select :cooking_name, @cookings, :name, :name, { include_blank: '料理の種類(必須)' }, {class: 'search_select_form'} = f.number_field :count, min: 1, placeholder: '料理する魚の数(必須)', class: 'search_form text-center' = button_tag type: 'submit', class: 'btn btn-primary search_button' do i.fas.fa-search .row.justify-content-md-center .col - if @fish.present? = @fish.kind - else | テスト
上記のように記述していて、魚の種類を選ばずに検索をするとブラウザに以下のように表示される。
ログは、以下のようになっている。
Started POST "/cooking_informations/search_time" for ::1 at 2021-12-24 00:23:52 +0900 Processing by CookingInformationsController#search_time as JS ① Parameters: {"authenticity_token"=>"tD4Ldg9RT6FXji1IyvjpdOYyIzeo2SOTe0YRvYkP4UWbsnr0OY//FpWzamJBl6j7ZDDait9hI6uvxYE1i+nDng==", "fish_kind"=>"", "cooking_name"=>"", "count"=>"", "button"=>""} Fish Load (0.4ms) SELECT `fishes`.* FROM `fishes` WHERE `fishes`.`kind` = '' LIMIT 1 ↳ app/controllers/cooking_informations_controller.rb:13:in `search_time' Rendering cooking_informations/search_time.html.slim within layouts/application Fish Load (0.4ms) SELECT `fishes`.* FROM `fishes` ↳ app/views/cooking_informations/search_time.html.slim:7 Cooking Load (0.3ms) SELECT `cookings`.* FROM `cookings` ↳ app/views/cooking_informations/search_time.html.slim:8 Rendered cooking_informations/search_time.html.slim within layouts/application (Duration: 5.5ms | Allocations: 4009) [Webpacker] Everything's up-to-date. Nothing to do Rendered shared/_header.html.slim (Duration: 0.3ms | Allocations: 158) Completed 200 OK in 37ms (Views: 33.4ms | ActiveRecord: 1.1ms | Allocations: 13916)
ログの①を確認すると[JS]となっており、JavaScript形式で取得している。
魚の種類を選んで検索をするとブラウザに以下のように表示される。
ログは、以下のようになっている。
Started POST "/cooking_informations/search_time" for ::1 at 2021-12-24 00:33:53 +0900 Processing by CookingInformationsController#search_time as JS ① Parameters: {"authenticity_token"=>"tD4Ldg9RT6FXji1IyvjpdOYyIzeo2SOTe0YRvYkP4UWbsnr0OY//FpWzamJBl6j7ZDDait9hI6uvxYE1i+nDng==", "fish_kind"=>"アジ", "cooking_name"=>"", "count"=>"", "button"=>""} ② Fish Load (0.5ms) SELECT `fishes`.* FROM `fishes` WHERE `fishes`.`kind` = 'アジ' LIMIT 1 ↳ app/controllers/cooking_informations_controller.rb:13:in `search_time' Rendering cooking_informations/search_time.html.slim within layouts/application Fish Load (0.6ms) SELECT `fishes`.* FROM `fishes` ↳ app/views/cooking_informations/search_time.html.slim:7 Cooking Load (0.4ms) SELECT `cookings`.* FROM `cookings` ↳ app/views/cooking_informations/search_time.html.slim:8 Rendered cooking_informations/search_time.html.slim within layouts/application (Duration: 8.0ms | Allocations: 4012) [Webpacker] Everything's up-to-date. Nothing to do Rendered shared/_header.html.slim (Duration: 0.4ms | Allocations: 158) Completed 200 OK in 36ms (Views: 31.0ms | ActiveRecord: 1.5ms | Allocations: 13935)
ログの①を確認すると[JS]となっており、JavaScript形式で取得している。
また、②より[アジ]がパラメーターで渡っている。
コントローラで以下のように記述があるので、@fishに値が入り、
@fish = Fish.find_by(kind: params[:fish_kind])
viewファイルの条件分岐により、
- if @fish.present? = @fish.kind - else | テスト
ブラウザには、魚の種類が表示されるはずだがブラウザには[テスト]と表示される。。。。
開発者ツールの[Network]の[Preview]と[Response]を確認すると以下のようになる。
開発者ツールの[Elements]を確認すると、以下のように[テスト]となっている。
原因は、ログの①よりJavaScript形式で取得しているが、そもそもHTML形式しか用意していない為。
開発者ツールの[Network]の[Preview]と[Response]では、想定通りのものが表示されているのに、開発者ツールの[Elements]やブラウザには、想定外のものが表示されるのは、この為である。
今回このようになってしまったのは、[form_with]に[local: true]の記述を忘れて非同期通信になってしまった為。
以下のように[local: true]を記述すると、
[app/views/cooking_informations/search_time.html.slim] .search-wrapper .container .row.justify-content-md-center .col-md-auto.mt-5 = form_with url: search_time_cooking_informations_path, local: true do |f| .form-group = f.collection_select :fish_kind, @fishes, :kind, :kind, { include_blank: '魚の種類(必須)' }, {class: 'search_select_form'} = f.collection_select :cooking_name, @cookings, :name, :name, { include_blank: '料理の種類(必須)' }, {class: 'search_select_form'} = f.number_field :count, min: 1, placeholder: '料理する魚の数(必須)', class: 'search_form text-center' = button_tag type: 'submit', class: 'btn btn-primary search_button' do i.fas.fa-search .row.justify-content-md-center .col - if @fish.present? = @fish.kind - else | テスト
魚の種類を選択して検索するとブラウザに想定通りのものが表示される。
ログも以下のように、[HTML]形式で取得している。
Started POST "/cooking_informations/search_time" for ::1 at 2021-12-24 00:56:13 +0900 Processing by CookingInformationsController#search_time as HTML Parameters: {"authenticity_token"=>"2VztlBEwgxv2rzN1ukmXjWnIP8X3HTIkgJ0lLnr9Wxr20JwWJ+4zrDSSdF8xJtYC68rGeIClMhxUHrWmeBt5wQ==", "fish_kind"=>"アジ", "cooking_name"=>"", "count"=>"", "button"=>""} Fish Load (0.3ms) SELECT `fishes`.* FROM `fishes` WHERE `fishes`.`kind` = 'アジ' LIMIT 1 ↳ app/controllers/cooking_informations_controller.rb:13:in `search_time' Rendering cooking_informations/search_time.html.slim within layouts/application Fish Load (0.5ms) SELECT `fishes`.* FROM `fishes` ↳ app/views/cooking_informations/search_time.html.slim:7 Cooking Load (0.3ms) SELECT `cookings`.* FROM `cookings` ↳ app/views/cooking_informations/search_time.html.slim:8 Rendered cooking_informations/search_time.html.slim within layouts/application (Duration: 6.8ms | Allocations: 4004) [Webpacker] Everything's up-to-date. Nothing to do Rendered shared/_header.html.slim (Duration: 0.2ms | Allocations: 158) Completed 200 OK in 25ms (Views: 21.6ms | ActiveRecord: 1.2ms | Allocations: 13911)
参考記事
ActiveModel::ModelモジュールとActiveModel::Attributesモジュール
[ActiveModel::Model]モジュールと[ActiveModel::Attributes]モジュールの違いがいまいち分からなかったので、こちらにまとめます。
・[ActiveModel::Model]モジュール
[ActiveModel::Model]モジュールをincludeしてできるようになる主な事は、以下のような事になります。(ActiveRecordのDBに連携しないようなモデルになる。)
モデル名の調査: モデル名からモデル名の複数形の予測などの機能が使えるようになる。(以下のような機能)
[1] pry(main)> Fish.model_name.name => "Fish" [2] pry(main)> Fish.model_name.singular => "fish" [3] pry(main)> Fish.model_name.plural => "fishes" [4] pry(main)> Fish.model_name.element => "fish" [5] pry(main)> Fish.model_name.human => "魚" [6] pry(main)> Fish.model_name.collection => "fishes" [7] pry(main)> Fish.model_name.param_key => "fish" [8] pry(main)> Fish.model_name.i18n_key => :fish [9] pry(main)> Fish.model_name.route_key => "fishes" [10] pry(main)> Fish.model_name.singular_route_key => "fish"
変換: Railsのモデルの変換メソッド[ to_model , to_key , to_param , to_partial_path ]が利用できるようになる。(form_with modelなどが利用できるようになる。)
翻訳: i18nによる翻訳機能が使えるようになる。
バリデーション: 検証用の機能を提供する。
ヘルパーメソッド: [form_with]や[render]などのヘルパーメソッドが使えるようになる。
[attr_accessor]メソッド: インスタンスにインスタンス変数を持たせることができ、インスタンス変数を呼び出せる。(以下のように検証)
・[ActiveModel::Model]モジュールをinclude
[app/forms/cooking_search_time_form.rb] class CookingSearchTimeForm include ActiveModel::Model attr_accessor :name end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト") => #<CookingSearchTimeForm:0x00007fb3684acc48 @name="テスト"> [2] pry(main)> a => #<CookingSearchTimeForm:0x00007fb3684acc48 @name="テスト">
・[ActiveModel::Model]モジュールをincludeしない
[app/forms/cooking_search_time_form.rb] class CookingSearchTimeForm attr_accessor :name end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト") ActiveModel::UnknownAttributeError: unknown attribute 'name' for CookingSearchTimeForm. from /Users/higmonta/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activemodel-6.0.4.1/lib/active_model/attribute_assignment.rb:52:in `_assign_attribute' [2] pry(main)> a => nil
・[ActiveModel::Attributes]モジュール
[ActiveModel::Attributes]モジュールをincludeすると[attribute]メソッドが使えるようになる。
[attribute]メソッドを使うと、[attr_accessor]メソッドと同じようにインスタンス変数が使えるようになり且つ、型の指定もできる。(以下のように検証)
・[ActiveModel::Model]モジュールをincludeして、[attr_accessor]メソッドを使ってインスタンス変数を設定(いろんな型でインスタンス変数を設定できる)
[app/forms/cooking_search_time_form.rb] class CookingSearchTimeForm include ActiveModel::Model attr_accessor :name end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト") => #<CookingSearchTimeForm:0x00007f9de0544900 @name="テスト"> [2] pry(main)> b = CookingSearchTimeForm.new(name: 11) => #<CookingSearchTimeForm:0x00007f9de58e6d60 @name=11> [3] pry(main)> a => #<CookingSearchTimeForm:0x00007f9de0544900 @name="テスト"> [4] pry(main)> b => #<CookingSearchTimeForm:0x00007f9de58e6d60 @name=11>
・[ActiveModel::Attributes]モジュールをincludeしない
[app/forms/cooking_search_time_form.rb] class CookingSearchTimeForm attribute :name, :string end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト") NoMethodError: undefined method `attribute' for CookingSearchTimeForm:Class Did you mean? attr_writer from /Users/higmonta/workspace/fishing_cooking/app/forms/cooking_search_time_form.rb:2:in `<class:CookingSearchTimeForm>'
・[ActiveModel::Attributes]モジュールをincludeする([ActiveModel::Model]モジュールは、includeしない)
[ActiveModel::Model]モジュールをincludeしないと、そもそもモデルのように使えないのでエラーになる。
[app/forms/cooking_search_time_form.rb] class CookingSearchTimeForm include ActiveModel::Attributes attribute :name, :string end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト") ArgumentError: wrong number of arguments (given 1, expected 0) from /Users/higmonta/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activemodel-6.0.4.1/lib/active_model/attributes.rb:79:in `initialize'
・[ActiveModel::Attributes]モジュールと[ActiveModel::Model]モジュールをincludeする。
[attribute]メソッドで型指定をしているので、[11]という数字でインスタンス変数を設定しても文字列型に変換されている。
[app/forms/cooking_search_time_form.rb] class CookingSearchTimeForm include ActiveModel::Model include ActiveModel::Attributes attribute :name, :string end
[1] pry(main)> a = CookingSearchTimeForm.new(name: "テスト") => #<CookingSearchTimeForm:0x00007fc441e03690 @attributes= #<ActiveModel::AttributeSet:0x00007fc441e035f0 @attributes= {"name"=> #<ActiveModel::Attribute::FromUser:0x00007fc441e03348 @name="name", @original_attribute= #<ActiveModel::Attribute::WithCastValue:0x00007fc441e03578 @name="name", @original_attribute=nil, @type=#<ActiveModel::Type::String:0x00007fc445894ea8 @limit=nil, @precision=nil, @scale=nil>, @value_before_type_cast=nil>, @type=#<ActiveModel::Type::String:0x00007fc445894ea8 @limit=nil, @precision=nil, @scale=nil>, @value_before_type_cast="テスト">}>> [2] pry(main)> b = CookingSearchTimeForm.new(name: 11) => #<CookingSearchTimeForm:0x00007fc44597afc0 @attributes= #<ActiveModel::AttributeSet:0x00007fc44597af48 @attributes= {"name"=> #<ActiveModel::Attribute::FromUser:0x00007fc44597ade0 @name="name", @original_attribute= #<ActiveModel::Attribute::WithCastValue:0x00007fc44597aed0 @name="name", @original_attribute=nil, @type=#<ActiveModel::Type::String:0x00007fc445894ea8 @limit=nil, @precision=nil, @scale=nil>, @value_before_type_cast=nil>, @type=#<ActiveModel::Type::String:0x00007fc445894ea8 @limit=nil, @precision=nil, @scale=nil>, @value_before_type_cast=11>}>> [3] pry(main)> a.name => "テスト" [4] pry(main)> b.name => "11"
参考記事
ActiveModelを使ってDBと関係ないFormの構築してみた - その辺にいるWebエンジニアの備忘録
ActiveModel::Model で DBに依存しないモデルを作ろう - What is it, naokirin?
form object 検索機能を追加する - mmm_stの日記
Formオブジェクト~1つのフォームで複数モデルとやりとりをする画期的なヤツ~ - Qiita
【Ruby on Rails】フォームオブジェクトを使って検索キーワードをcontrollerに送る - Qiita
ActiveModel::Model で DBに依存しないモデルを作ろう - What is it, naokirin?
ActiveModel::Attributesを使う - Qiita
[Rails] ActiveModel::Attributesの使い方(配列化やネストしたhashの取り扱いなども) - Qiita
RailsでDBと連携しないModelを作成する - Qiita
Railsの技: Attributes APIでPOROの属性を自動的にキャストする(翻訳)|TechRacho by BPS株式会社