- 投稿日:2020-09-29T23:31:43+09:00
もう迷わない!RSpec導入の流れ
RSpecとは
Railsにおいて、単体テストコードを記述するときに用いるGemのこと。
テストコードを記述する直前までの大まかな流れ
- Gemfileに追記
- Gemをインストール
- rspecをインストール
- テストコードをターミナル上で可視化できるようにする
- FactoryBotとFakerのGemをGemfileに追記
- Gemのインストール
- (テスト用の画像を用意)
1. Gemfileに追記
gem 'rspec-rails'と記述する。
必ず、group :development, :test do ~ endのグループ内にGemfilegroup :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails' end2. Gemをインストール
ターミナルでカレントディレクトリがテストコードを使用するアプリケーションのディレクトリであることを確認の上
bundle install3. rspecをインストール
rails g rspec:installこれによって、「specディレクトリ」や「.rspecファイル」が生成される
4. テストコードをターミナル上で可視化できるようにする
.rspec--format documentationを追記する。
5. FactoryBotとFakerのGemをGemfileに追記
必要に応じてGemを導入。今回はチャットアプリを想定しているため、導入。
Gemfilegroup :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails' gem 'factory_bot_rails' gem 'faker' end先ほどと同じgroupの中に記述。
6. Gemのインストール
bundle install7. (テスト用の画像を用意)
public/imagesにファイル名「test_image.png」で配置。
ポイント
- rspecは「bundle install」だけでなく、「rails g rspec:install」をして初めて使える。
- 記述するのは、group :development, :test do ~ endのグループ内。
最後に
bundle installはRailsに取り込んだだけ!
rails g rspec:installで解凍するイメージ!細かいテストコードはまた別の機会に。
- 投稿日:2020-09-29T22:15:23+09:00
rails routesからパスを作る
環境
- AWS cloud9 EC2
- ruby 2.6.3
- Rails 5.2.4.3
アクションのパスを作る方法
ルーティングの確認
rails routesを実行する
Terminalvocstartsoft:~/environment/***** $ rails routes Prefix Verb URI Pattern books GET /books(.:format) books#index POST /books(.:format) books#create new_book GET /books/new(.:format) books#new edit_book GET /books/:id/edit(.:format) books#edit book GET /books/:id(.:format) books#show PATCH /books/:id(.:format) books#update PUT /books/:id(.:format) books#update DELETE /books/:id(.:format) books#destroyアクションのパスを作成
1. 一番右の列を見る
飛びたいアクションを確認する
(例:book#edit
)2.一番左の列を見る(Prefix)
1で確認した行のPrefixの列を確認する
※もし空白の場合は、直近の上のものを使用する。
(例:edit_book
)3.
_path
をつける2で確認したものに
_path
をつける。
(例:edit_book_path
)4.URI Patternの列を見る
URI Patternの列を見て
:id
があるか確認する。
id
がなかったら
3で作成したものがパスになるid
があったら
3で作成したものに引数をしてして:id
を指定する
(例:edit_book_path(@book.id)
)5.HTTPメソッドを確認する
link_toなどではデフォルトでGETが行われるようになっている。
=>そのため、GET以外の場合は指定する必要がある。
- 指定方法
pathの後に、, method: :httpメソッド
を付け加える
(例:book_path(@book.id), method: :delete
)何故
:id
が必要なのか表示したViewページが
:id
によって違う内容にする必要があるかどうかを考える
:id
がないbooks#index
books#new
などは、一覧の表示や新規作成のため、特定の:id
を必要としない:id
があるbooks#edit
books#show
などは、特定の:id
についての表示をする必要があるため、表示させたい:id
を引数として指定する
- 投稿日:2020-09-29T20:36:32+09:00
【Ruby on Rails】Webpackerにselect2導入メモ
jQuery導入
select2を使う為に、jQueryが必要です。
select2導入
terminalyarn add select2app/javascript/packs/application.jsimport 'select2' import 'select2/dist/css/select2.css' document.addEventListener('turbolinks:load', () => { $('.js-select').select2({ placeholder: 'Select an option', allowClear: true }) })カスタマイズスタイル
sass$select2-height: 38px .select2-container .select2-selection--single height: $select2-height line-height: $select2-height .select2-selection__arrow, .select2-selection__rendered height: $select2-height line-height: $select2-height .select2-search--dropdown .select2-search__field font-size: 16px padding: 4px以上でRuby on Railsアプリケーションにselect2が導入できました。
select
タグにjs-select
クラスを付けたら動くはずです。
- 投稿日:2020-09-29T18:57:48+09:00
ヘルパーで部分テンプレート(パーシャル)を返す
ヘルパーで部分テンプレート(パーシャル)を返す方法
if文を使って条件ごとに異なるパーシャルを返したい場合があると思います。
しかし"html.erb"ファイルの中でif文を書くとどうしてもコードが長くなってしまいますよね。
例えばこんな風に、、、sample.html.erb<% if A == A %> <%= render 'follow_button' %> <% else %> <%= render 'unfollow_button' %> <% end %>こんな時はヘルパーメソッドに処理をまとめるとスッキリします。⬇︎
users_helper.rbmodule UsersHelper def follow_unfollow_button(user) if A == A render 'follow_button' else render 'unfollow_button' end end endビューでhelperメソッドを使うと、、
sample.html.erb<%= follow_unfollow_button(@user) %>1行でスッキリしました。
まとめ
slimやhamlを使用していればif文も多少は短くなるのですが、いずれにせよビューの中にロジックが書かれているとやはりコードの可読性が下がってしまいます。
できるだけif文などのロジックやhelperやdecoratorにまとめた方が良いと思いました。
- 投稿日:2020-09-29T18:26:28+09:00
【Ruby on Rails】便利なヘルパーメソッド
はじめに
ヘルパーメソッドについて、パーフェクトRuby on Rails 【増補改訂版】を読んで個人的に学んだ内容をまとめます。
ヘルパーメソッドとは
ビューテンプレートで開発の手助けとなる便利なメソッド。HTML生成を行うものやURL生成するものなどがある。
パスとURLヘルパーメソッド
ルーティングを表示する際のPrefixの値+
_path
でドメインやポートなどを除いた/から始まるパスを、Prefixの値+_url
とするとドメインなども含んだ完全なURLを返す。
パスに「:id」といった変動する値がある場合、引数にハッシュ形式で値を指定する。
url_forやlink_to、redirect_toでパスやURLを指定する際に使う。以下のルーティングの場合、次のようなパスやURLが返る。
ルーティング
users GET /users(.:format) users#index user GET /users/:id(.:format) users#show edit_user GET /users/:id/edit(.:format) users#edit返るパスやURL
- users_path →
/users
- user_path(id: 1) →
/users/1
- edit_user_url(id: 1) →
http://www.example.com/users/1/edit
url_for
Webアプリケーションのパスを構築するためのヘルパーメソッド。文字列を直接書かずにルーティング上の定義を活用できる。
単にURLを取得したいときに使用する。url_for(users_path) #=> /userslink_to
link_toはaタグを生成するヘルパーメソッド。
_path
や_url
でルーティング上の定義を活用して、画面にリンクを設置したいときに使用する。link_to('編集', edit_user_path(id: 1)) #=> <a href="/users/1/edit">編集</a>time_ago_in_words
ある時刻と現在時間のあいだにどの程度空きがあるかを、人間にとってわかりやすく表示するヘルパーメソッド。
最終ログイン日時などを表示するサービスで活用できそう。time_ago_in_words(Time.current) #=> less than a minutetime_ago_in_words(Time.current + 3.days) #=> 3 daysnumber_with_delimiter
長い数字をカンマ区切りするヘルパーメソッド。オプションで区切り文字を変更することもできる。
number_with_delimiter 1234567890 #=> 1,234,567,890number_with_delimiter 1234567890, delimiter: '@' #=> 1@234@567@890その他にも様々なヘルパーメソッドがRailsには用意されているので、自分で実装する前にRailsガイドを参考にすると良さそう。
独自ヘルパーメソッド
Rails組み込みのヘルパーメソッドでは用途に合わない場合は自分でヘルパーメソッドを実装する。
ヘルパーメソッドを定義するためのモジュールは「app/helpers」というディレクトリの配下に定義する。
アプリケーション全体で使うものであれば「app/helpers/application_helper.rb」に、コントローラ毎に使うようなものであれば「app/helpers/users_helper.rb」のようにコントローラに対応するhelper用のファイルを用意する。例)全角の英文字を半角に変換するヘルパーメソッド
app/helper/application_helper.rbmodule ApplicationHelper def to_hankaku(str) str.tr('A-Za-z', 'A-Za-z') end endto_hankaku('Ruby') #=> Ruby参考
- 投稿日:2020-09-29T17:13:00+09:00
ajaxを使ってタグの追加と削除、(成功・エラー)メッセージの表示を行う。
環境
Ruby 2.5.7
Rails 5.2.4ライブラリ
jQuery 1.12.4
前提
今回はTagテーブルを使って解説していきます。
まずはajaxを使わなくてもタグの追加と削除ができることを事前にご確認ください。
エラーメッセージの日本語化やアソシエーションについては触れませんので必要な方はご自身でお願い致します。
turbolinksは無効にしています。(有効時の動作は確認していません。)
手順
タイトルにもある通り、次の手順でajaxを利用してタグの追加・削除・エラーメッセージの表示を実装していきます。
1.tag.rbにバリデーションの設定
2.部分テンプレートの用意(views/layouts/_error_messages.html.erb
,views/layouts/_flash_messages.html.erb
,views/tags/_tag.html.erb
)
3.タグの一覧画面、兼新規作成フォームを作成(views/tags/index.html.erb)
4.アクションごとのjs.erbを用意(views/tags/create.js.erb, views/tags/destroy.js.erb)
5.tags_controller.rb(:index, :create, :destroy)の用意イメージとしては、送信ボタンを押すとコントローラーのcreateアクションが実行
createアクション内でcreate.js.erbを呼び出すメソッドが実行
create.js.erb内に書かれているJavaScript(jQuery)が発火
DOM操作により、エラーメッセージの要素とタグ一覧の要素が上書き(jQueryの.html()
メソッド)
削除時のdestroyアクションも同様です。完成形の流れとしては
1.index画面でフォームに値を入力する。
1-1.存在しないタグなら新規保存(サクセスメッセージ)
1-2.存在するタグなら保存失敗(バリデーションエラーメッセージ)
1-3.空白なら保存失敗(バリデーションエラーメッセージ)2.個別のタグを削除する
1-1.サクセスメッセージを表示全てajaxのため、ページの全体の更新はありません。
1.tag.rbにバリデーションの設定
tag.rbclass Tag < ApplicationRecord ... # 空白の禁止と一意性(重複NG)を付与します。 validates :name, presence: true, uniqueness: true end2.部分テンプレートの用意(
layouts/_error_messages.html.erb
,layouts/_flash_messages.html.erb
,_tag.html.erb
)ここで作る3つの部分テンプレートを、アクションに応じてDOM操作で上書きしていく形です。
jQueryで書くと1行1行DOM操作をすることになって冗長になってしまうので、部分テンプレートを利用して後述するjQueryが<%= render ... %>
1行で済むようになっています。バリデーションエラーメッセージ用の部分テンプレート
すでにある方は流用してもらって大丈夫です。layouts/_error_messages.html.erb<% if model.errors.any? %> <div id="validation-errors"> <p><%= model.errors.count %>件のエラーがあります。</p> <ul class="error-messages"> <% model.errors.full_messages.each do |message| %> <li class="error-message"><%= message %></li> <% end %> </ul> </div> <% end %>jQueryのDOM操作によって上書きされた時にバリデーションエラーが発生していれば、エラーメッセージが表示されます。
ローカル変数のmodel
は後述するindex.html.erbで@tag
(Tag.new)に置き換えます。次にサクセスメッセージ(フラッシュメッセージ)用の部分テンプレート
こちらもすでにある方は流用してください。layouts/_flash_messages.html.erb<% flash.each do |key, value| %> <p class="alert alert-<%= key %>"> <%= value %> </p> <% end %>eachで配列から取得することになっていますが、これは複数のフラッシュメッセージを取得するというよりも、どのようなキー(
flash[キー]
)でもこの一文で再表示ができるようになっています。
今回使うキーは[:success]
,[:warning]
の2種類です。(ちなみにこの2種類はbootstrapでデザインも用意されているため、今回選択しました。)
この二つを使う場合は部分テンプレートを下記のように書き換えることもできます。layouts/_flash_messages.html.erb<% case flash.keys[0] %> <% when "success" %> <p class="alert alert-<%= flash.keys[0] %>"> <%= flash[:success] %> </p> <% when "warning" %> <p class="alert alert-<%= flash.keys[0] %>"> <%= flash[:warning] %> </p> <% end %>flashに格納されるキーは配列のため、
keys[0]
とする必要があります。
case文でkeyパターンに応じて表示するフラッシュメッセージを変化させます。
p要素のclassでkeys[0]
を使用している理由は、キーによって(今回の場合はsuccessかwarning)cssのデザインを変更するためです。しかしこれでは、キーの種類(キーの名前は必要に応じて任意でつけることができます。)が増えた時にその都度行を増やしていく必要があるため、冗長になってしまいます。
そこで今回はeach文を使用することで、その手間をなくした形です。
また部分テンプレート化することで、バリデーションエラーメッセージと同様に、他のviewでも使い回すことができます。次にタグ一覧を表示する部分テンプレート
tags/_tag.html.erb<div class="tags-index__tags--tag"> <%= tag.name %>(<%= tag.menu_tag_ids.count %>) <%= link_to 'X', tag_path(tag), method: :delete, remote: true %> </div>こちらも部分テンプレートを使わなければ後述するjQueryで4行分書く必要がり、jQueryでのDOM操作は極力シンプルに管理したいので部分テンプレート化しています。
ローカル変数のtag
はindex.html.erbでインスタンス変数@tags
(Tag.all)を指定することで、部分テンプレート内では個別のローカル変数tag
として自動で置き換わります。(index.html.erbのところで詳しく解説します。)
(<%= tag.menu_tag_ids.count %>)
はタグ名の横にそのタグが使われているメニューの数を表示しています。(メニューテーブル(割愛) 1:多 メニュータグテーブル(中間テーブル)(割愛) 多:1 タグテーブル(今回))
<%= link_to 'X', tag_path(tag), method: :delete, remote: true %>
はX
を押すとdestroyアクションが発動します。remote: true
とすることで、ajax通信であることを明示します。
"remote: true"の仕組み
通常のリクエスト(リンク)ではHTMLを取得しますが、remote: true
(html変換後はdata-remote="true"
属性)が付与されたリクエストの場合はJSファイルのみを取得します。
Railsの場合はlayouts/application.html.erbの<head>
タグ内にjsを読みこむタグがあるので、通常はHTMLを取得するとjsも自動で再読み込みがされます。なお、turbolinksを使っている場合はこの限りではありません。3.タグの一覧画面、兼新規作成フォームを作成(views/tags/index.html.erb)
タグの一覧画面とその中に新規入力のフォームも作成していきます。
views/tags/index.html.erb<div class="contents tags-index"> <div class="tags-index--messages"><!--メッセージの表示エリア--></div> <h2>タグ一覧</h2> <div class="tags-index__list" id="tags-index--tag-list"> <%= render @tags %> </div> <div class="tags-index__form"> <%= form_with model: @tag, url: tags_path(@tag) do |f| %> <%= f.text_field :name %> <%= f.submit %> <% end %> </div> </div>タグ一覧の下に新規追加フォームがくっついてる状態です。
新規登録フォームはform_withを利用します。
form_withはデフォルトがremote: true
属性を持っているので省略可能です。(form_forを使う場合はremote: true
属性を明示する必要があります。)
form_withでajaxを使わない場合はlocal: true
属性を明示する必要があります。
フォームヘルパーはremote: true
の場合はJSのみを、local: true
の場合はHTMLをアクションに伝えます。
また、フォームヘルパーはデフォルトのHTTPメソッドがPOSTでもあるのでmethod: :post
も省略可能です。
<%= render @tags %>
のインスタンス変数@tags
は後述するコントローラー側でTag.all
を取得しています。
見慣れない部分テンプレートの書き方ですが、この略記法の部分テンプレートは変数名に対応する_変数名.html.erb
が呼び出され、その部分テンプレート内ではローカル変数(今回でいう部分テンプレート内のtag
)として使えるようになります。
今回の@tags
ように配列で取得した場合は、部分テンプレート内ではその配列の数だけ、繰り返し表示がされます。
これは書き換えると以下のようになります。views/tags/index.html.erb<%= render partial: 'tag', locals: {tags: @tags} %>views/tags/_tag.html.erb<% tags.each do |tag| %> <div class="tags-index__tags--tag"> <%= tag.name %>(<%= tag.menu_tag_ids.count %>) <%= link_to 'X', tag_path(tag), method: :delete, remote: true %> </div> <% end %>今回の記述方法はこれらを略記した形です。
Railsガイド - レイアウトとレンダリング4.アクションごとのjs.erbを用意(views/tags/create.js.erb, views/tags/destroy.js.erb)
コントローラーから呼び出すjs(.erb)を作成します。
ここにjsを記述することで、コントローラーからcreate, destroyがそれぞれ呼び出された時に発火するjQueryでDOM操作が行われます。
ここで使われている.html()
はそこに要素が何もなければ追加となり、すでに別の要素があれば書き換えとなります。createアクション時のjs(.erb)
views/tags/create.js.erb<% if @tag.errors.any? %> $(".tags-index--messages").html("<p><%= j(render partial: 'layouts/error_messages', locals: {model: @tag}) %></p>") <% else %> $(".tags-index--messages").html("<p><%= j(render partial: 'layouts/flash_messages', locals: {model: @tag}) %></p>") $("#tags-index--tag-list").html("<%= j(render @tags) %>") $("#tag_name").val('') <% end %>
<% if @tag.errors.any? %>
はsubimitボタンが押された時に@tag
にエラーが発生しているかどうかをチェックします。
"オブジェクト.errors.any?"と"オブジェクト.invalid?"の違い
今回の場合、コントローラー側で@tag.save
が試された後にcreate.js.erbを呼んでいるため@tag
にはすでにerrorが発生しているかどうかがわかる状態でjs.erbに渡って来ています。
他方、invalid?
メソッドは保存前のオブジェクトに対して手動でバリデーションチェックを行うものです。
通常はオブジェクトが保存されようとした時(.save
を試みた時)に自動でバリデーションチェックが実行され、今回は.save
の成否によってアクションを分けるため、改めて手動でinvalid?
をするよりは、.save
の時点ですでにエラーが発生しているかどうか(.errors.any?
)を確認した方がDRYに則っていると言えます。エラーが発生している場合(空白やタグ名の重複)
・バリデーションエラーメッセージ(error_messages.html.erb)を$(".tags-index--messages")
に追加または書き換えエラーが発生していない場合(正常に保存ができた)は
・フラッシュメッセージ(flash_message.html.erb)を$(".tags-index--messages")
に追加または書き換え
・タグ一覧($("#tags-index--tag-list")
)を書き換え
・フォームに入力されたタグ名をクリア($("#tag_name").val('')
)が、実行されます。
destroyアクション時のjs(.erb)
views/tags/destroy.js.erb$(".tags-index--messages").html("<p><%= j(render partial: 'layouts/flash_messages', locals: {model: @tag}) %></p>") $("#tags-index--tag-list").html("<%= j(render @tags) %>")create時と同じです。
・タグが削除されたらフラッシュメッセージの追加または書き換え
・タグ一覧の書き換えが、実行されます。
"partial: "の必要性
renderを使ってオプションの追加(今回でいうlocals: {tag: @tag}
)が必要な場合はファイル名(パス)の前にpartial:
を明示する必要があります。
Railsガイド - レイアウトとレンダリング5.tags_controller.rb(:index, :create, :destroy)の用意
コントローラーに今回必要なアクションを記載していきます
tags_controller.rbclass TagsController < ApplicationController def index @tags = Tag.all @tag = Tag.new end def create @tags = Tag.all @tag = Tag.new(tag_params) respond_to do |format| if @tag.save format.js { flash.now[:success] = "保存しました。" } else format.js end end end def destroy @tags = Tag.all @tag = Tag.new Tag.find(params[:id]).destroy flash.now[:warning] = "削除しました。" end private def tag_params params.require(:tag).permit(:name) end end今回はcreateのform_with、destroyのlink_to、それぞれで
remote: true
(form_withの場合はデフォルトなので省略)を明示しているため、どちらもJSのみをリクエストしていることになります。tags_controller.rbdef create ... respond_to do |format| if @tag.save format.js { flash.now[:success] = "保存しました。" } else format.js end end end
respond_to do |format|
はリクエストの形式によって処理を分けるブロックパラメータです。
今回、タグの追加・削除についてはHTMLで要求が来る(local: true
)ことは想定していませんのでformat.js {処理}
のみを記載しています。
HTMLでの要求が来た時に処理を分けたい場合はformat.html {処理}
を書けば、リクエストの方式に応じで処理を分けることができます。
if @tag.save
が成功した場合はflash[:success]
キーに"保存しました。"
というメッセージを載せてcreate.js.erbに渡します。
失敗した場合はバリデーションエラーメッセージのみを表示するため、フラッシュメッセージは使いませんので、format.js
の後ろに処理は書かず、表示のみを行います。
前述しましたが、この時にバリデーションエラーが発生している@tag
をcreate.js.erbに渡して、そこでエラー処理(バリデーションエラーメッセージの表示)をしています。destroyアクションでは
flash[:warning]
キーに"削除しました。"
というメッセージを載せてdestroy.js.erbに渡します。
"format.js"を書く場合と書かない場合
通常、コントローラーにはviewファイルに対応する同名のアクション名を作ります。(def index end
ならindex.html.erb)
アクション名の中身で特にrender
やredirect_to
などのに指定がなければアクションの最後にrender :アクション名
が省略されて(暗示的に)実行されています。
(def index end
など、中身を書かなくてもviewがレンダリングされるのはこのためです。)
今回、createアクションでは、条件によってフラッシュメッセージを渡したいので明示的にformat.js
を記述し、その中で処理を分けていましたが、destroyアクションでは今のところ処理を分ける必要はないので、respond_to
やformat.js
、render :destroy
は記述していません。
render :destroy
に関してはすでに暗示的に記述されているため、書く必要がないということです。最初に書きましたが、暗示的にrender :アクション名
が発動するのはアクションの中で他にrender :別のアクション名
やredirect_to
がない場合に限るので、他方createアクションのように明示的にformat.jsを記述し実行された場合、アクションの最後に暗示的なrender :create
が実行されることはありません。(DoubleRenderingErrorにならない理由です。)
Railsガイド - Action Controller の概要
Pikawaka - resoond_to
Qiita - 【Rails】flashメッセージを使用して簡易メッセージを表示させる詳しい方法と解説まとめ
質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。
最後まで読んでいただきありがとうございました。
参考サイト
Railsガイド - レイアウトとレンダリング
Railsガイド - Action Controller の概要
Pikawaka - resoond_to
Qiita - 【Rails】flashメッセージを使用して簡易メッセージを表示させる詳しい方法と解説
- 投稿日:2020-09-29T17:13:00+09:00
【Ruby on Rails】ajaxを使ってタグの追加と削除、(成功・エラー)メッセージの表示を行う。
環境
Ruby 2.5.7
Rails 5.2.4ライブラリ
jQuery 1.12.4
前提
今回はTagテーブルを使って解説していきます。
まずはajaxを使わなくてもタグの追加と削除ができることを事前にご確認ください。
エラーメッセージの日本語化やアソシエーションについては触れませんので必要な方はご自身でお願い致します。
turbolinksは無効にしています。(有効時の動作は確認していません。)
手順
タイトルにもある通り、次の手順でajaxを利用してタグの追加・削除・エラーメッセージの表示を実装していきます。
1.tag.rbにバリデーションの設定
2.部分テンプレートの用意(views/layouts/_error_messages.html.erb
,views/layouts/_flash_messages.html.erb
,views/tags/_tag.html.erb
)
3.タグの一覧画面、兼新規作成フォームを作成(views/tags/index.html.erb)
4.アクションごとのjs.erbを用意(views/tags/create.js.erb, views/tags/destroy.js.erb)
5.tags_controller.rb(:index, :create, :destroy)の用意イメージとしては、送信ボタンを押すとコントローラーのcreateアクションが実行
createアクション内でcreate.js.erbを呼び出すメソッドが実行
create.js.erb内に書かれているJavaScript(jQuery)が発火
DOM操作により、エラーメッセージの要素とタグ一覧の要素が上書き(jQueryの.html()
メソッド)
削除時のdestroyアクションも同様です。完成形の流れとしては
1.index画面でフォームに値を入力する。
1-1.存在しないタグなら新規保存(サクセスメッセージ)
1-2.存在するタグなら保存失敗(バリデーションエラーメッセージ)
1-3.空白なら保存失敗(バリデーションエラーメッセージ)2.個別のタグを削除する
1-1.サクセスメッセージを表示全てajaxのため、ページの全体の更新はありません。
1.tag.rbにバリデーションの設定
tag.rbclass Tag < ApplicationRecord ... # 空白の禁止と一意性(重複NG)を付与します。 validates :name, presence: true, uniqueness: true end2.部分テンプレートの用意(
layouts/_error_messages.html.erb
,layouts/_flash_messages.html.erb
,_tag.html.erb
)ここで作る3つの部分テンプレートを、アクションに応じてDOM操作で上書きしていく形です。
jQueryで書くと1行1行DOM操作をすることになって冗長になってしまうので、部分テンプレートを利用して後述するjQueryが<%= render ... %>
1行で済むようになっています。バリデーションエラーメッセージ用の部分テンプレート
すでにある方は流用してもらって大丈夫です。layouts/_error_messages.html.erb<% if model.errors.any? %> <div id="validation-errors"> <p><%= model.errors.count %>件のエラーがあります。</p> <ul class="error-messages"> <% model.errors.full_messages.each do |message| %> <li class="error-message"><%= message %></li> <% end %> </ul> </div> <% end %>jQueryのDOM操作によって上書きされた時にバリデーションエラーが発生していれば、エラーメッセージが表示されます。
ローカル変数のmodel
は後述するindex.html.erbで@tag
(Tag.new)に置き換えます。次にサクセスメッセージ(フラッシュメッセージ)用の部分テンプレート
こちらもすでにある方は流用してください。layouts/_flash_messages.html.erb<% flash.each do |key, value| %> <p class="alert alert-<%= key %>"> <%= value %> </p> <% end %>eachで配列から取得することになっていますが、これは複数のフラッシュメッセージを取得するというよりも、どのようなキー(
flash[キー]
)でもこの一文で再表示ができるようになっています。
今回使うキーは[:success]
,[:warning]
の2種類です。(ちなみにこの2種類はbootstrapでデザインも用意されているため、今回選択しました。)
この二つを使う場合は部分テンプレートを下記のように書き換えることもできます。layouts/_flash_messages.html.erb<% case flash.keys[0] %> <% when "success" %> <p class="alert alert-<%= flash.keys[0] %>"> <%= flash[:success] %> </p> <% when "warning" %> <p class="alert alert-<%= flash.keys[0] %>"> <%= flash[:warning] %> </p> <% end %>flashに格納されるキーは配列のため、
keys[0]
とする必要があります。
case文でkeyパターンに応じて表示するフラッシュメッセージを変化させます。
p要素のclassでkeys[0]
を使用している理由は、キーによって(今回の場合はsuccessかwarning)cssのデザインを変更するためです。しかしこれでは、キーの種類(キーの名前は必要に応じて任意でつけることができます。)が増えた時にその都度行を増やしていく必要があるため、冗長になってしまいます。
そこで今回はeach文を使用することで、その手間をなくした形です。
また部分テンプレート化することで、バリデーションエラーメッセージと同様に、他のviewでも使い回すことができます。次にタグ一覧を表示する部分テンプレート
tags/_tag.html.erb<div class="tags-index__tags--tag"> <%= tag.name %>(<%= tag.menu_tag_ids.count %>) <%= link_to 'X', tag_path(tag), method: :delete, remote: true %> </div>こちらも部分テンプレートを使わなければ後述するjQueryで4行分書く必要がり、jQueryでのDOM操作は極力シンプルに管理したいので部分テンプレート化しています。
ローカル変数のtag
はindex.html.erbでインスタンス変数@tags
(Tag.all)を指定することで、部分テンプレート内では個別のローカル変数tag
として自動で置き換わります。(index.html.erbのところで詳しく解説します。)
(<%= tag.menu_tag_ids.count %>)
はタグ名の横にそのタグが使われているメニューの数を表示しています。(メニューテーブル(割愛) 1:多 メニュータグテーブル(中間テーブル)(割愛) 多:1 タグテーブル(今回))
<%= link_to 'X', tag_path(tag), method: :delete, remote: true %>
はX
を押すとdestroyアクションが発動します。remote: true
とすることで、ajax通信であることを明示します。
"remote: true"の仕組み
通常のリクエスト(リンク)ではHTMLを取得しますが、remote: true
(html変換後はdata-remote="true"
属性)が付与されたリクエストの場合はJSファイルのみを取得します。
Railsの場合はlayouts/application.html.erbの<head>
タグ内にjsを読みこむタグがあるので、通常はHTMLを取得するとjsも自動で再読み込みがされます。なお、turbolinksを使っている場合はこの限りではありません。3.タグの一覧画面、兼新規作成フォームを作成(views/tags/index.html.erb)
タグの一覧画面とその中に新規入力のフォームも作成していきます。
views/tags/index.html.erb<div class="contents tags-index"> <div class="tags-index--messages"><!--メッセージの表示エリア--></div> <h2>タグ一覧</h2> <div class="tags-index__list" id="tags-index--tag-list"> <%= render @tags %> </div> <div class="tags-index__form"> <%= form_with model: @tag, url: tags_path(@tag) do |f| %> <%= f.text_field :name %> <%= f.submit %> <% end %> </div> </div>タグ一覧の下に新規追加フォームがくっついてる状態です。
新規登録フォームはform_withを利用します。
form_withはデフォルトがremote: true
属性を持っているので省略可能です。(form_forを使う場合はremote: true
属性を明示する必要があります。)
form_withでajaxを使わない場合はlocal: true
属性を明示する必要があります。
フォームヘルパーはremote: true
の場合はJSのみを、local: true
の場合はHTMLをアクションに伝えます。
また、フォームヘルパーはデフォルトのHTTPメソッドがPOSTでもあるのでmethod: :post
も省略可能です。
<%= render @tags %>
のインスタンス変数@tags
は後述するコントローラー側でTag.all
を取得しています。
見慣れない部分テンプレートの書き方ですが、この略記法の部分テンプレートは変数名に対応する_変数名.html.erb
が呼び出され、その部分テンプレート内ではローカル変数(今回でいう部分テンプレート内のtag
)として使えるようになります。
今回の@tags
ように配列で取得した場合は、部分テンプレート内ではその配列の数だけ、繰り返し表示がされます。
これは書き換えると以下のようになります。views/tags/index.html.erb<%= render partial: 'tag', locals: {tags: @tags} %>views/tags/_tag.html.erb<% tags.each do |tag| %> <div class="tags-index__tags--tag"> <%= tag.name %>(<%= tag.menu_tag_ids.count %>) <%= link_to 'X', tag_path(tag), method: :delete, remote: true %> </div> <% end %>今回の記述方法はこれらを略記した形です。
Railsガイド - レイアウトとレンダリング4.アクションごとのjs.erbを用意(views/tags/create.js.erb, views/tags/destroy.js.erb)
コントローラーから呼び出すjs(.erb)を作成します。
ここにjsを記述することで、コントローラーからcreate, destroyがそれぞれ呼び出された時に発火するjQueryでDOM操作が行われます。
ここで使われている.html()
はそこに要素が何もなければ追加となり、すでに別の要素があれば書き換えとなります。createアクション時のjs(.erb)
views/tags/create.js.erb<% if @tag.errors.any? %> $(".tags-index--messages").html("<p><%= j(render partial: 'layouts/error_messages', locals: {model: @tag}) %></p>") <% else %> $(".tags-index--messages").html("<p><%= j(render partial: 'layouts/flash_messages', locals: {model: @tag}) %></p>") $("#tags-index--tag-list").html("<%= j(render @tags) %>") $("#tag_name").val('') <% end %>
<% if @tag.errors.any? %>
はsubimitボタンが押された時に@tag
にエラーが発生しているかどうかをチェックします。
"オブジェクト.errors.any?"と"オブジェクト.invalid?"の違い
今回の場合、コントローラー側で@tag.save
が試された後にcreate.js.erbを呼んでいるため@tag
にはすでにerrorが発生しているかどうかがわかる状態でjs.erbに渡って来ています。
他方、invalid?
メソッドは保存前のオブジェクトに対して手動でバリデーションチェックを行うものです。
通常はオブジェクトが保存されようとした時(.save
を試みた時)に自動でバリデーションチェックが実行され、今回は.save
の成否によってアクションを分けるため、改めて手動でinvalid?
をするよりは、.save
の時点ですでにエラーが発生しているかどうか(.errors.any?
)を確認した方がDRYに則っていると言えます。エラーが発生している場合(空白やタグ名の重複)
・バリデーションエラーメッセージ(error_messages.html.erb)を$(".tags-index--messages")
に追加または書き換えエラーが発生していない場合(正常に保存ができた)は
・フラッシュメッセージ(flash_message.html.erb)を$(".tags-index--messages")
に追加または書き換え
・タグ一覧($("#tags-index--tag-list")
)を書き換え
・フォームに入力されたタグ名をクリア($("#tag_name").val('')
)が、実行されます。
destroyアクション時のjs(.erb)
views/tags/destroy.js.erb$(".tags-index--messages").html("<p><%= j(render partial: 'layouts/flash_messages', locals: {model: @tag}) %></p>") $("#tags-index--tag-list").html("<%= j(render @tags) %>")create時と同じです。
・タグが削除されたらフラッシュメッセージの追加または書き換え
・タグ一覧の書き換えが、実行されます。
"partial: "の必要性
renderを使ってオプションの追加(今回でいうlocals: {tag: @tag}
)が必要な場合はファイル名(パス)の前にpartial:
を明示する必要があります。
Railsガイド - レイアウトとレンダリング5.tags_controller.rb(:index, :create, :destroy)の用意
コントローラーに今回必要なアクションを記載していきます
tags_controller.rbclass TagsController < ApplicationController def index @tags = Tag.all @tag = Tag.new end def create @tags = Tag.all @tag = Tag.new(tag_params) respond_to do |format| if @tag.save format.js { flash.now[:success] = "保存しました。" } else format.js end end end def destroy @tags = Tag.all @tag = Tag.new Tag.find(params[:id]).destroy flash.now[:warning] = "削除しました。" end private def tag_params params.require(:tag).permit(:name) end end今回はcreateのform_with、destroyのlink_to、それぞれで
remote: true
(form_withの場合はデフォルトなので省略)を明示しているため、どちらもJSのみをリクエストしていることになります。tags_controller.rbdef create ... respond_to do |format| if @tag.save format.js { flash.now[:success] = "保存しました。" } else format.js end end end
respond_to do |format|
はリクエストの形式によって処理を分けるブロックパラメータです。
今回、タグの追加・削除についてはHTMLで要求が来る(local: true
)ことは想定していませんのでformat.js {処理}
のみを記載しています。
HTMLでの要求が来た時に処理を分けたい場合はformat.html {処理}
を書けば、リクエストの方式に応じで処理を分けることができます。
if @tag.save
が成功した場合はflash[:success]
キーに"保存しました。"
というメッセージを載せてcreate.js.erbに渡します。
失敗した場合はバリデーションエラーメッセージのみを表示するため、フラッシュメッセージは使いませんので、format.js
の後ろに処理は書かず、表示のみを行います。
前述しましたが、この時にバリデーションエラーが発生している@tag
をcreate.js.erbに渡して、そこでエラー処理(バリデーションエラーメッセージの表示)をしています。destroyアクションでは
flash[:warning]
キーに"削除しました。"
というメッセージを載せてdestroy.js.erbに渡します。
"format.js"を書く場合と書かない場合
通常、コントローラーにはviewファイルに対応する同名のアクション名を作ります。(def index end
ならindex.html.erb)
アクション名の中身で特にrender
やredirect_to
などのに指定がなければアクションの最後にrender :アクション名
が省略されて(暗示的に)実行されています。
(def index end
など、中身を書かなくてもviewがレンダリングされるのはこのためです。)
今回、createアクションでは、条件によってフラッシュメッセージを渡したいので明示的にformat.js
を記述し、その中で処理を分けていましたが、destroyアクションでは今のところ処理を分ける必要はないので、respond_to
やformat.js
、render :destroy
は記述していません。
render :destroy
に関してはすでに暗示的に記述されているため、書く必要がないということです。最初に書きましたが、暗示的にrender :アクション名
が発動するのはアクションの中で他にrender :別のアクション名
やredirect_to
がない場合に限るので、他方createアクションのように明示的にformat.jsを記述し実行された場合、アクションの最後に暗示的なrender :create
が実行されることはありません。(DoubleRenderingErrorにならない理由です。)
Railsガイド - Action Controller の概要
Pikawaka - resoond_to
Qiita - 【Rails】flashメッセージを使用して簡易メッセージを表示させる詳しい方法と解説まとめ
質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。
最後まで読んでいただきありがとうございました。
参考サイト
Railsガイド - レイアウトとレンダリング
Railsガイド - Action Controller の概要
Pikawaka - resoond_to
Qiita - 【Rails】flashメッセージを使用して簡易メッセージを表示させる詳しい方法と解説
- 投稿日:2020-09-29T17:05:45+09:00
Ubuntuでrails6環境構築時、bundle installで引っ掛かる
環境構築を終えて、bundle installするときに、色々引っ掛かる
OSはWSL1 Ubuntu(18.04)
こちらを参考に進めていきました。nokogiriが通らない
An error occurred while installing nokogiri (1.10.10), and Bundler cannot continue. Make sure that `gem install nokogiri -v '1.10.10' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: rails was resolved to 6.0.3.3, which depends on actioncable was resolved to 6.0.3.3, which depends on actionpack was resolved to 6.0.3.3, which depends on actionview was resolved to 6.0.3.3, which depends on rails-dom-testing was resolved to 2.0.3, which depends on nokogiribundle configを変更するといけるらしいので
bundle installをしている場合
gem install nokogiri -- --use-system-librariesbundle install --path vendor/bundleをしている場合
アプリの.bundleの中のconfigに.bundleBUNDLE_BUILD__NOKOGIRI: "--use-system-libraries"を追記する。
bootsnapが通らない
An error occurred while installing bootsnap (1.4.8), and Bundler cannot continue. Make sure that `gem install bootsnap -v '1.4.8' --source 'https://rubygems.org/'` succeeds before bundling.どうやらバージョンを下げるといいようで
Gemfilegem 'bootsnap', '<= 1.1.0', require: falseこれでやるといけた。
msgpackが通らない
An error occurred while installing msgpack (1.3.3), and Bundler cannot continue. Make sure that `gem install msgpack -v '1.3.3' --source 'https://rubygems.org/'` succeeds before bundling.どうやら
Permission denied @ rb_file_s_rename.....ということなのでアプリのvenfor/bundle内のgemsの権限をいじる
sudo chown -R username /home/username/testrails/railsapp/vendor/bundle/ruby/2.7.0/gemsパスは環境によって変わります。
Pumaが入らない
bundle/configBUNDLE_BUILD__PUMA: "--with-cflags=-Wno-error=implicit-function-declaration"を.bundle/configに書き込むか
$ bundle config build.puma --with-cflags="-Wno-error=implicit-function-declaration"どちらかでいけるはず。
ほかに何か出てきたとき
権限関係の原因が多いと思うのでまず確認するといいと思います。
- 投稿日:2020-09-29T15:41:13+09:00
Rack::Timeout でパスごとにタイムアウトを指定する
rack-timeout を使用しておりパスごとにタイムアウトの指定を変更するため以前(rails 5.2.3、 rack-timeout 0.5.1 まで)は以下のように指定していました(多少簡略化してあります)。
Gemfilegem 'rack-timeout', require: 'rack/timeout/base'config/initializers/rack_timeout.rbmodule Rack class DynamicTimeout < Rack::Timeout def call(env) # URL が /admin から始まる場合は 30 秒、それ以外は 10 秒となるように @service_timeout = env['REQUEST_URI'].start_with?('/admin') ? 30 : 10 super(env) end end end timeout_params = { service_timeout: 10, wait_timeout: 5.minutes.to_i, wait_overtime: 5.minutes.to_i, service_past_wait: false, } Rails.application.config.middleware.insert_before Rack::Runtime, Rack::DynamicTimeout, timeout_paramsところが Rails 6、あるいは rack-timeout 0.6.0 からタイムアウトが動的に指定した
service_timeout
の値よりも短くなっていました。
以下のようにログに出力されている値(30秒)よりも実際は短かった(10秒)です。severity:ERROR message:source=rack-timeout id=0284947e-c24c-4024-a9ba-c542b3eb58f9 timeout=30000ms service=10000ms state=timed_out色々ログを仕込んだりして原因を調べたところ、どうやら
Rack::DynamicTimeout
のcall
とは別にRack::Timeout
自体のcall
が呼ばれているようでした。都合2回
call
メソッドが実行されて最初はRack::DynamicTimeout
で次がRack::Timeout
になっており、最終的にタイムアウトの値はRack::Timeout
で指定された値が使われるという状態です。
どこで呼ばれているか、それを変更できるかというところまで調べていくとちょっと時間がかかりそうだったので、今回はRack::Timeout
を上書きするパッチで対応することにしました。Gemfilegem 'rack-timeout'config/initializers/rack_timeout.rbmodule Rack module DynamicTimeout def call(env) # URL が /admin から始まる場合は 30 秒、それ以外は 10 秒となるように @service_timeout = env['REQUEST_URI'].start_with?('/admin') ? 30 : 10 super(env) end end end Rack::Timeout.prepend Rack::DynamicTimeout
Module#prepend
でモジュールを差し込んでRack::Timeout
のcall
が呼ばれても対応できるようにしました。
ひとまずこれで。
- 投稿日:2020-09-29T14:46:02+09:00
ルーティングのネストによって起きていること
動作環境
Ruby 2.6.5
Rails 6.0.3.2ルーティングのネストによって何が起きているのかがわかりづらかったので、自分の確認を兼ねて投稿してみました。
ルーティングのネストをした場合
routes.rbRails.application.routes.draw do resources :hoges do resources :fugas, only: [:index] end end上記のコードでは、hogesに対してfugasがネストされており、ターミナル上でrails routesを実行すると、以下のようになります。
Prefix ➡︎ hoge_fugas
Verb ➡︎ GET
URI Pattern ➡︎ /hoges/:hoge_id/fugas(.:format)
Controller#Action ➡︎ fugas#indexもちろん、上記以外も表示されますが今回はネストの箇所のみを扱います。
これだけだといまいちわかりづらいので、ルーティングのネストをしていない場合と比較していきます。ルーティングのネストをしない場合
routes.rbRails.application.routes.draw do resources :hoges resources :fugas, only: [:index] end上記のコードでは先ほどのコードと違い、ネストされていません。では先ほどと同様にターミナル上でrails routesを実行して、同じ箇所を確認してみましょう。
Prefix ➡︎ fugas
Verb ➡︎ GET
URI Pattern ➡︎ /fugas(.:format)
Controller#Action ➡︎ fugas#index先ほどと比べてPrefixとURI Patternが異なっていることがわかります。
PrefixはURI Patternを簡単に書いたものなので、URI Patternのみに注目していきます。ルーティングのネストをした場合としない場合の違い
ネストした場合
URI Pattern ➡︎ /hoges/:hoge_id/fugas(.:format)
ネストしない場合
URI Pattern ➡︎ /fugas(.:format)つまり、ルーティングのネストとはURI Patternを変更するために行っているものと言えると思います。しかし、この言い方だとわかりづらいと思うので、もう少し詳しく説明します。
ネストすることによってURI Patternが変更したためにfugasコントローラーのindexアクションを作動するには、hoge_idを指定する必要が出てきます。
そのため、paramsの中にhoge_idを入れることができるようになります。
つまり、先ほどはルーティングのネストをURI Patternを変更するためのものと言いましたが、paramsの中にネストする側(今回はhoge)のid情報を入れるためのものと言い換えることができます。paramsにネストする側(今回はhoge)のid情報を入れることにより、ネストされた側(今回はfuga)のコントローラー内でネストする側の情報を扱うことができるということになります。
- 投稿日:2020-09-29T11:33:47+09:00
Railsコマンドが正常に操作しない時疑うべきはspring
動機
Railsを用いてテストコードを作成中に以下のコマンドを打つと正常に動作しなくなったので備忘録としてどのように解決したかを記述
$ rails g rspec:request article Running via Spring preloader in process 15349 Deprecation warning: Expected boolean default value for '--orm'; got :active_record (string). This will be rejected in the future unless you explicitly pass the options `check_default_type: false` or call `allow_incompatible_default_type!` in your code You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION. Could not find generator 'rspec:request'. Maybe you meant "rspec:decorator"? Run `rails generate --help` for more options.springとは?
ザックリですが
「開発をよりスピードアップさせるため開発しているアプリケーションをバックグラウンドで保持し、「テスト・rakeタスク・マイグレーション」を毎回起動しなくてもいいようにしてくれるRailsアプリケーションのプリローダー」といいやつに見えて、ここのプロセスが残っていたりするのが問題の可能性があります。
解決手順
まずはspringのstatusを確認
$ spring status Spring is running: 3729 spring server | blogapp | started 17 hours agoやはり止まっていました。
次にspringの停止を行います。
$ spring stop Spring stopped.再度状態確認
$ spring status Spring is not running.再度Railsコマンド実行
$ rails g rspec:request article Running via Spring preloader in process 15565 create spec/requests/articles_spec.rb成功しました!
再度コマンド操作を行うとspringも実行されますのでご心配なく
身に覚えの無いエラーが起きた際はspringを疑ってみましょう
- 投稿日:2020-09-29T11:09:36+09:00
[Rails]カスタムフォントの設定
はじめに
アプリ開発でフリーフォントを使用してデザインを変えたのでその使用方法をまとめました。
目次
1 フリーフォントの準備
2 フォントの読み込み1 フリーフォントの準備
まず、フリーフォントサイトから自分が使用したいものをダウンロードします。
その後、ダウンロードしたファイル(拡張子ttfまたはotf)をapp/assets/fonts下に配置します。注意事項
フォントはそれぞれ利用条件が異なります。商用可能であっても利用できる範囲に制限があったり、使用時に報告が必要なものもありますので、ご利用の際には必ず配布ページをご確認ください。
2 フォントの読み込み
CSSファイルに以下を記述します。
@font-face { font-family: “abcdefg”; src: url('/assets/abcdefg.ttf') format("truetype"); font-weight: normal; font-style: normal; }フォントを適用したい箇所にfont-familyを指定します。
例
p { font-family; “abcdefg”; }これでフォントを適用することができました。
3 本番環境への適応
このままでは本番環境でフォントが適用されなかったため、以下を変更しました。
/config/production.rb# 変更前 config.assets.compile = false # 変更後 config.assets.compile = trueこれにより本番環境で読み込まれていなかった画像ファイルも表示することができました。
参考リンク
https://qiita.com/dir_sh0606/items/ce605b70f1cb333f9d59
https://qiita.com/kinpin/items/bd7c9a7d7a739e297742
- 投稿日:2020-09-29T08:09:39+09:00
[初学者向け]削除機能の実装方法
投稿内容
今回は削除機能の実装方法について投稿します。
以下のようなボタンを押した際にdestroyアクション
を発動させます。
コントローラーのアクション
items_controller.rbclass ItemsController < ApplicationController #省略 def destroy if @item.destroy redirect_to root_path else render :show end end end解説) destroyアクションが呼ばれたらインスタンス変数
@item
を削除します。無事削除できた場合はroot_path
へ遷移。削除できなかった場合はshowアクション
へ遷移するようハンドリングします。Rubyの記述
show.html.haml.item-show-page__destroy-btn = link_to "商品を削除する", item_path(@item), method: :delete, class: "item-show-page__destroy-link"解説)
link_to
で@item
を指定し、メソッドはdelete
を指定します。これでボタンをクリックすると削除できると思います。
最後に
今回の削除機能は至ってシンプルなものなので、初学者の方でもとっつきやすいかと思われます。是非参考にしてください!
- 投稿日:2020-09-29T03:40:37+09:00
【Ruby】メソッドジャンプをしたい!
はじめに
環境macOS Catalina10.15.5
Rubyで他の人が書いたソースコードを読むときにメソッドの定義場所がすぐわかるようにメソッドジャンプがしたいなと思い、いろいろ試してたどり着いた方法を書き記します
私はテキストエディタはAtomを主に使っていたのですが、RubyのコードをAtomでメソッドジャンプするのは、いろいろ試しましたがうまくいきませんでした、なので
結論 VSCodeを使うのが簡単
という事になりまりました
導入手順
1.こちらでVSCodeをダウンロード
https://azure.microsoft.com/ja-jp/products/visual-studio-code/
2.日本語化
- メニューバーからviewを選択
- command palette を選択
- configure display languageを選択
- install additional languageを選択
- Japanese Language Pack for Visual Studio Codeのパッケージを探す
- もう一度configure display languageを選択
- jaを選択
- VSCode再起動
3.パッケージのRubyをインストール
拡張機能の検索ボックスで検索できます
4.ジャンプの機能を有効化
インストールしたRubyの拡張機能の設定を開いて
Ruby:Intellisenseのところをfalseからrubylocateに変更5.メソッドジャンプのやりかた
⌘を押しながらジャンプしたいメソッドをクリックすると定義場所にとべます!
ここまで15分程度だったのでとても簡単でした!
- 投稿日:2020-09-29T02:03:56+09:00
今更ながら graphql-code-generator の便利さを痛感する その2
はじめに
今日も今日とて、フロントReact + バックRailsの...
ということで、今回は今更ながら graphql-code-generator の便利さを痛感するの続きとして、登録、削除等の
Mutation
の部分をやっていきます。※構成は前回と同じです。
とりあえずApolloの公式に沿ってやってみる
まずはTODOを追加する仕組みを作っていきます。
追加用のテキストフィールドを用意しました。
ここに値を入力して、エンターで追加処理が実行される仕組みとします。src/App.tsx+import { gql, useMutation } from "@apollo/client"; ... +const ADD_TODO = gql` + mutation addTodo($name: String!) { + addTodo(input: { name: $name }) { + todo { + id + name + } + } + } +`; const App = () => { const { loading, data } = useTodosQuery(); + const [addTodo] = useMutation(ADD_TODO); ... </p> + <input + type="text" + onKeyPress={(e) => { + if (e.key === "Enter") { + addTodo({ variables: { name: e.currentTarget.value } }); + } + }} + /> {loading ? ( ...文字を入力して、エンターで追加処理が実行できる仕組みができました。
エンター押下後、画面をリロードすると、TODOが追加されていることがわかります。
リロードしないと動きがわからないので、ついでに、追加処理に加え、入力欄のクリア、一覧情報の再取得処理をやっておきます。
src/App.tsx... - const { loading, data } = useTodosQuery(); + const { loading, data, refetch } = useTodosQuery(); - const [addTodo] = useMutation(ADD_TODO); + const [addTodo] = useMutation(ADD_TODO, { + update() { + refetch(); + }, + }); ... onKeyPress={(e) => { if (e.key === "Enter") { addTodo({ variables: { name: e.currentTarget.value } }); + e.currentTarget.value = ""; } }}特に型関連でエラーは起きませんでした。
もう少し便利になるよう作り込んでみる
何も入力せずに、登録処理を実行し、エラーが出るような仕組みを入れます。
Todo
にバリデーションエラーのフィールドを追加して、以下のような値がレスポンスに含まれるようにしました。"errors":[{"field":"name","error":"blank"}]また、処理の結果が正常に行われたかの
result
フィールド(Boolean)を追加しました。
result
がtrue
なら一覧をリロード、false
ならエラー情報をalert
で表示する仕組みを作ってみます。src/App.tsxconst ADD_TODO = gql` mutation addTodo($name: String!) { addTodo(input: { name: $name }) { todo { id name + errors { + field + error + } } + result } } `;src/App.tsxconst [addTodo] = useMutation(ADD_TODO, { - update() { - refetch(); - }, - }); + update( + _cache, + { + data: { + addTodo: { + todo: { errors }, + result, + }, + }, + } + ) { + if (result) { + refetch(); + } else { + errors.forEach(({ field, error }) => { + alert(`${field} ${error}`); + }); + } + }, + });
errors
の型が宣言されていないので、エラーが発生しました。型宣言をしてあげます。
type ValidationErrorType = { field: string; error: string; };-errors.forEach(({ field, error }) => { +errors.forEach(({ field, error }: ValidationErrorType) => { alert(`${field} ${error}`); });エラーは解消され、バリデーションエラーメッセージが
alert
で確認できるようになりました。前回同様、
graphql-code-generator
を使って、綺麗にしていこうと思います。graphql-code-generatorを使う
まずは設定から
queries
とは別にmutations
用のディレクトリを作成して、今回作成したクエリは、そちらに格納します。codegen.yml-documents: ./graphql/queries/*.graphql +documents: + - ./graphql/mutations/*.graphql + - ./graphql/queries/*.graphqlgraphql/mutations/add_todo.graphqlmutation addTodo($name: String!) { addTodo(input: { name: $name }) { todo { id name errors { field error } } result } }これで
yarn generate
を実行すると、src/types.d.ts
にTODOを追加する用のuseAddTodoMutation
が用意されました。
useAddTodoMutation
を使って追加処理を書き直してみます。-import { useTodosQuery } from "./types.d"; +import { useTodosQuery, useAddTodoMutation } from "./types.d"; +const [addTodo] = useMutation(ADD_TODO, { +const [addTodo] = useAddTodoMutation({ん?エラーが起きました。
どうやら
data
はnull
かundefined
の可能性があるそうです。それらを考慮して、少し書き直します。
const [addTodo] = useAddTodoMutation({ update(_cache, { data }) { const result = data?.addTodo?.result || false; const errors = data?.addTodo?.todo.errors || []; if (result) { refetch(); } else { errors.forEach((e) => { if (e) alert(`${e.field} ${e.error}`); }); } }, });少し手直しが必要でしたが、クエリと、型宣言の用意が不要になったので、ソース的にはスッキリしました
![]()
同様に削除処理を実装してみる
雑ですが、各TODOの隣に、削除ボタンを用意して、削除処理を実行できるようにします。
graphql/mutations/del_todo.graphqlmutation delTodo($id: ID!) { delTodo(input: { id: $id }) { todo { id } } }削除対象のTODOを特定するために、IDを取得する必要があります。
graphql/queries/todos.graphql
では、TODOのname
だけ取得しているので、id
もとるように修正します。graphql/queries/todos.graphqlquery todos { todos { + id name } }
yarn generate
を実行するとuseDelTodoMutation
関数がsrc/types.d.ts
に生まれました。この
useDelTodoMutation
を使って、削除
ボタンクリック時に削除処理が実行できるようにします。src/App.tsx-import { useTodosQuery, useAddTodoMutation } from "./types.d"; +import { useTodosQuery, useAddTodoMutation, useDelTodoMutation } from "./types.d"; +const [delTodo] = useDelTodoMutation({ + update() { + refetch(); + } +}); -{data && data.todos.map(({ name }, i) => <li key={i}>{name}</li>)} +{data && data.todos.map(({ id, name }, i) => <li key={i}>{name}<button onClick={() => delTodo({ variables: { id } })}>削除</button></li>)}お手軽に削除処理が実装できました
最後に
一つの画面、コンポーネントで、一覧取得、追加処理、削除処理を実装したので、少しボリュームの大きめなファイルとなってしまったかもしれません。
別のファイルに型定義やクエリ情報を定義して、
import
でも良いかもしれないですが、人力で且つ、複数人となると、管理が次第に煩雑になったりする可能性があると思います。これが、ある意味
graphql-code-generator
のルールに則って開発をしていると、その問題が解消されるのではないかと思いました。今回は、かなり小規模な例として、実装したので、導入にあたって障害となる箇所をあまり感じられませんでした。
LGTM なツールと思います![]()