- 投稿日:2020-03-20T23:27:59+09:00
breadcrumbs_on_railsを使ったパンくず実装 その② オリジナルviewを使おう
パンくず実装の続きです。
といっても備忘録としての記事ですので、ほぼほぼサイトを参照してやったことを思い返すつもりで書いています。ちなみに今回は、lib直下をいじりますので、少し習ったことがないことをしました。
ではでは、やっていきましょうかまずは、custom builder を作りましょう。
といってもこの名称はなんでも良いです。
ただし、ほぼほぼコードの直打ちで作りますよ。/lib/custom_breadcrumbs_builder.rbclass CustomBreadcrumbsBuilder < BreadcrumbsOnRails::Breadcrumbs::Builder def render @context.render '/shared/breadcrumbs', elements: @elements end endこれは、views/sharedフォルダにオリジナルのviewを呼びにいっているという記述です。
render形式のため、elements:@elementsの要素をbreadcrumbs.html.hamlへ送っています。
shardフォルダにrenderさせるファイルを纏めているので、そこを呼んでいますが、別に場所はどこでも良いです。/view/breadcrumbs.html.haml- if elements.present? %ul.l-gretel-brock - elements.each do |element| %li.l-gretel-inline - if element.path.present? = link_to element.name ,element.path, class: 'l-gretel-link u-deco-none' = fa_icon 'chevron-right', class: 'l-gretel-icon' - else = element.name = elements.last.nameと書きますわね。CSSは適当煮付けてくださいな。
(gretelって名称使ってるけどむしろわかりづらいような・・・)そして、使いたいところに、以下の一文を追加する。
test.html.haml= render_breadcrumbs builder: ::CustomBreadcrumbsBuilder最後に、config/application.rbに以下の一文を追加しよう。
config/application.rbmodule Test class Application < Rails::Application config.autoload_paths += %W(#{config.root}/lib) #ここの一文だけだよ! endこれはローカル上で、lib直下にあるファイルを呼びに行きますよということです。
ここまでできれば一安心、早速みてみよう!
いかのように表示されました。
fa_iconが良い感じです。ここまでやってみてダメだった人は、
rails s
して再起動、もしくはbandle install
を試してみよう。
私は意外と忘れるアプリケーションサーバーの再起動は盲点でした。あ、あと本番環境は、以下の一文追加必要ですので、ご注意ください。
いくつか文章探しましたが、これでやっと通った感じです。config/environments/production.rbRails.application.configure do ###いっぱい文章ありましたが省略 config.eager_load_paths += %W( #{config.root}/lib ) endこれで、デプロイしても問題ないはず!
試してみてください。
ちなみに参考した記事は以下の通り、私もtech:expert生です。
https://pg-happy.jp/rails-pankuzu-list.html
- 投稿日:2020-03-20T21:06:43+09:00
【Rails】bundler-auditの使い方
bundler-audit
とは
bundler-audit
はプロジェクトで利用しているGemの脆弱性の有無をチェックしてくれるGemです。
システムの脆弱性チェックの一つとして利用できますね。検証した環境
- Ruby 2.7.0
- bundler-audit 0.6.1
準備
bundle install
Gemfileに以下を追加して
bundle install
Gemfilegroup :development do gem 'bundler-audit' end$ bundle install脆弱性チェックに使用するDBを更新する
脆弱性チェックを実行する前に、
bundler-audit
が使用するDBの更新を行いましょう。$ bundle exec bundler-audit updateこのコマンドでは、実際には
$HOME/.local/share
ディレクトリ配下にrubysec/ruby-advisory-dbをgit clone
しているようです。脆弱性の存在するGemをチェックする
実際に
bundler-audit
を実行してGemの脆弱性をチェックしてみましょう。
以下のコマンドで実行できます。
脆弱性が見つからなかった場合はNo vulnerabilities found
と出力されます。$ bundle exec bundler-audit No vulnerabilities foundDBの更新と脆弱性チェックを同時に実行する
以下のコマンドで脆弱性チェックに使用するDBの更新と脆弱性チェックを同時に実行できます。
DBの更新忘れを防ぐためにも、基本的には以下のコマンドを利用するほうがいいでしょう。$ bundle exec bundler-audit check --update実際に脆弱性があるGemをインストールして検証してみる
bundler-audit
の出力を確認するために、実際に脆弱性が存在するGemのインストール~脆弱性チェック~修正までやってみます。脆弱性があるGemをインストール
実際に脆弱性が存在するGemをインストールしてみます。
ここでは試しにbootstrap
のv4.3.0
をインストールしてみます。
Gemfile
に以下を追記してbundle install
。Gemfilegem 'bootstrap', '4.3.0'$ bundle install脆弱性をチェックする
bundler-audit
を実行してみます。$ bundle exec bundler-audit Name: bootstrap Version: 4.3.0 Advisory: CVE-2019-8331 Criticality: Medium URL: https://blog.getbootstrap.com/2019/02/13/bootstrap-4-3-1-and-3-4-1/ Title: XSS vulnerability in bootstrap Solution: upgrade to >= 4.3.1 Vulnerabilities found!脆弱性が見つかったGemとその内容、解決するバージョンまでわかりやすく出力してくれます。
脆弱性を無視する
場合によっては
bundler-audit
によって見つかった脆弱性を無視したいこともあるかと思います。
そういうときは--ignore
オプションに無視したいAdvisoryを指定しましょう。$ bundle exec bundler-audit --ignore CVE-2019-8331 No vulnerabilities found脆弱性のあるGemのバージョンを上げる
先ほど
bundler-audit
で出力された内容にはSolution: upgrade to >= 4.3.1と書いてあったので、それに従って
Gemfile
を修正してみます。
(※実際にはGemfile
にバージョンを指定していない場合の方が多いと思うので、その場合はbundle update
コマンド等を利用してください。)Gemfile-gem 'bootstrap', '4.3.0' +gem 'bootstrap', '4.3.1'$ bundle install
bootstrap
のバージョンを上げたのでもう一度bundler-audit
を実行してみます。$ bundle exec bundler-audit No vulnerabilities found警告が出なくなりました!
参考
- 投稿日:2020-03-20T20:41:25+09:00
TDD(テスト駆動開発)って??
TDDについてのアウトプットを行います。
TDDとは?
簡単に言うと、
①テストコード ②実装、リファクタリングの順で多なっていく事
なんで、先にテストコードを書くん?
実装と修正のサイクルを定期的に行うため
その必要性は、常にエラーを意識しながら実装していくためです。
結果、その都度エラーを発見することができると言うことですね。どんな方法で行うん?
①失敗のコードを書く
②成功のコードを書く
③リファクタリング
失敗のコードを書く意味って何なのでしょうか?
『エラーになると思っていたコードがエラーにならない!?』ってな事が起きないようにするためですね。
例えが合っているか怪しいですが、
(例)1+1=4とエラーコードを書きました。しかし、返ってきた答えがエラーではなく『成功』してしまう。ってな異常事態を防ぐためですね。成功のコードを書くときは?
この時に注意することは、最低限のコードでいいからテストを成功させる事。
消して整ったコードを書いて成功させなければならないと言うわけではないと言う事です。終わりに
最低限のアウトプットではありますが、ここからアップデートして行きます。
- 投稿日:2020-03-20T20:30:36+09:00
bundle install でNokorigiインストール時に「Gem::Ext::BuildError: ERROR: Failed to build gem native extension.」エラーが出た場合の対処方法
bundle install
でnokogiriインストール時に下記のようなエラーGem::Ext::BuildError: ERROR: Failed to build gem native extension.が出てしまった場合
gem install時であれば
--use-system-libraries
オプションを付けて再実行してみるところですがbundlerの場合はどうすればいいかというと
bundle config --local build.nokogiri --use-system-libraries
というような感じでコマンドを実行します。
--local
オプションはapp/.bundle/config
へ設定を書きます。
--global
オプションは~/.bundle/config
に書くようです。まあよほどの事情がない限り、プロジェクトごとの
.bundle/config
に書いておくほうが良いと思います。その他、環境変数にも定義できます。bundle configの優先順位
bundle configですが、mysqlなどと同様、複数の箇所に設定を保持しておけます。用途によって適切な場所に設定を書いておくのがいいと思いますが、bundleコマンドのhelpによると
DESCRIPTION This command allows you to interact with Bundler´s configuration system. Bundler loads configuration settings in this order: 1. Local config (app/.bundle/config) 2. Environmental variables (ENV) 3. Global config (~/.bundle/config) 4. Bundler default configという優先順位で参照されます。
- 投稿日:2020-03-20T18:33:46+09:00
インクリメンタルサーチ
インクリメンタルサーチとは
文字の入力のたび、自動的に検索が行われる検索方法のことです。
インデックスでデータの検索を高速化
テーブル内で検索が頻繁に行われるカラムにインデックスを設定することで検索の高速化します。
今回はtweetsテーブルのtextカラムにインデックスを貼ることで、データの検索を高速化します。
インデックスはデータベースの機能の一つで、テーブル内のデータ検索を高速化することができます。インデックスはカラムに対して設定することができ、設定したカラムでの検索が高速になります。
ちなみにインデックスを設定することを、「インデックスを貼る」と言います。インデックスを設定
textカラムに対するインデックスを設定するので、tweetsテーブルに対してインデックスを貼るためのマイグレーションファイルを作成します。
ターミナル$ rails g migration AddIndexToTweets次に作成したマイグレーションファイルを編集します。
2020xxxxxxxxxxx_add_index_to_tweets.rbclass AddIndexToTweets < ActiveRecord::Migration def change add_index :tweets, :text, length: 32 end endターミナル
rails db:migrateマイグレーションを実行したらtweetsテーブルのtextカラムに対してインデックスが設定できます。
次からはインクリメンタルサーチの実装をしていきます。ルーティングなどAPI側の準備
アクションの中でHTMLとJSONなどのフォーマット毎に条件分岐する記述を追加します。
フォーマット毎に処理を分けるには、respond_toを使用します。
【例】app/controlers/tweets_controller.rb〜省略〜 def index @tweets = Tweet.includes(:user).order("created_at DESC").page(params[:page]).per(5) end 〜省略〜 def search @tweets = Tweet.search(params[:keyword]) respond_to do |format| format.html format.json end end 〜省略〜投稿情報を取得したら、jbuilderを使ってJavaScript側に返します。
検索結果は、複数の投稿情報を表示させ、格納された配列を返すようなjbuilderの記述します。
app/views/tweets/に「search.json.jbuilder」ファイルを作成します。
【例】作成したsearch.json.jbuilderを編集します。今回はすでにtweetカラムに対してtextやimageなど設定済みですapp/views/tweets/search.json.jbuilderjson.array! @tweets do |tweet| json.id tweet.id json.text tweet.text json.image tweet.image json.user_id tweet.user_id json.nickname tweet.user.nickname json.user_sign_in current_user endJSON形式のデータを配列で返したい場合は、array!を使用します。
jbuilder:array!メソッド
jbuilderという拡張子を持つテンプレートでは、JSONという名前のJbuilderオブジェクトが自動的に利用できるようになります。
Jbuilderオブジェクトは、JSON形式の配列で返したい場合はarray!を使用します。
【例】JavaScript側に送る配列[{ id: 1, image: "https://~.jpg", nickname: "たかし", text: "プログラミングの初学者です", user_id: 1, user_sign_in: {created_at: "2020-03-19T01:23:45.000Z", email: "aaa@gmail.com", id: 1, nickname: "たかし", updated_at: "2020-03-19T01:23:45.000Z"} }]jbuilderを使用するとより少ない記述でJSON形式のデータを作ることができます。
テキストフィールドを作成
【例】
app/views/tweets/index.html.erb<%= form_with(url: search_tweets_path, local: true, method: :get, class: "xxxx") do |form| %> <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "yyyy" %> <%= form.submit "検索", class: "zzzz" %> <% end %> <div class="contents row"> <% @tweets.each do |tweet| %> <%= render partial: "tweet", locals: { tweet: tweet } %> <% end %> <%= paginate(@tweets) %> </div>テキストフィールドが入力されるたびにイベントが発火できるように
文字を打ち込み終わって処理をさせたいときはkeyupメソッドを使用します。
keyupメソッドはjQueryオブジェクトで指定した要素にフォーカスがあるとき、キーが離されたら引数のfunctionを実行します。引数にfunctionを設定しない場合は、要素に設定されたfunctionを実行します。
app/assets/javascripts/に「search.js」ファイルを作成します。'''app/assets/javascripts/search.js
$(function() {
$(".search-input").on("keyup", function() {
var input = $(".yyyy").val();
});
});
```
今回の実装で使用するテキストフィールドは先ほどの部分になります。app/views/tweets/index.html.erb<%= form_with(url: search_tweets_path, local: true, method: :get, class: "xxxx") do |form| %> <%= form.text_field :keyword, placeholder: "投稿を検索する", class: "yyyy" %> <%= form.submit "検索", class: "zzzz" %> <% end %>テキストフィールドのclass名はyyyyです。
クラス名が".yyyy”の部分のテキストフィールドがkeyupしたら、テキストフィールドの文字を取得して変数inputに代入します。
フォームの値を取得するときはval()を使います。イベント時に非同期通信できるように
app/assets/javascripts/search.js$(function() { $(".search-input").on("keyup", function() { var input = $(".search-input").val(); $.ajax({ #追加〜 type: 'GET', url: '/tweets/search', data: { keyword: input }, dataType: 'json' }) #〜追加 }); });HTTPメソッドはGETで、/tweets/searchのURLに{ keyword: input }を送信します。サーバーから値を返す際は、JSONになります。
ターミナルでrails routesを実行すると、上記のリクエストによって、tweets_controller.rbのsearchアクションが動きます。app/controllers/tweets_controller.rb〜省略〜 respond_to do |format| format.html format.json end 〜省略〜$.ajaxのdataTypeでJSONを指定しているので、サーバーはJSON形式で値を返します。
普段のレスポンス(html形式のレスポンス)ではtweets_controller.rbのsearchアクションが実行されたら、app/views/tweets/search.html.erbが読まれますが、JSON形式の場合は、app/views/tweets/search.json.jbuilderが読まれます。非同期通信の結果を得て、HTMLを作成
非同期通信の結果をdoneの関数の引数から受取り、ビューに追加するためのHTMLを作成します。
app/assets/javascripts/search.js$(function() { $(".search-input").on("keyup", function() { var input = $(".search-input").val(); $.ajax({ type: 'GET', url: '/tweets/search', data: { keyword: input }, dataType: 'json' }) .done(function(tweets) { #追加〜 $(".contents.row").empty(); if (tweets.length !== 0) { tweets.forEach(function(tweet){ appendTweet(tweet); }); } else { appendErrMsgToHTML("一致するツイートがありません"); } }) #〜追加 }); });.done(function(tweets) { $(".contents.row").empty(); })インクリメンタルサーチでは、検索をする直前に投稿情報のリスト(テキストや画像)を削除してあげる必要があります。
app/views/tweets/index.html.erb〜省略〜 <div class="contents row"> <% @tweets.each do |tweet| %> <%= render partial: "tweet", locals: { tweet: tweet } %> <% end %> <%= paginate(@tweets) %> </div>検索結果欄で出力されている投稿情報をemptyメソッドを使用して削除します。
emptyメソッド
指定したDOM要素の子要素のみを削除するメソッドです。
投稿情報をすべて削除したいので、の要素を取得します。
なのでsearch.jsの$(".contents.row").empty();で投稿の情報を削除できます。app/assets/javascripts/search.jsif (tweets.length !== 0) { tweets.forEach(function(tweet){ appendTweet(tweet); }); } else { appendErrMsgToHTML("一致するツイートがありません"); }上記の関数は、jbuilderから得られた値を投稿情報のリストに追加するものです。
tweetsが空ではない場合はtweets.length !== 0を記述します。
forEachメソッドを用いて、tweetsの中身の数だけappendTweet関数を呼び出します。forEachメソッド
与えられた関数を配列に含まれる各要素に対して一度ずつ呼び出します。
tweetsが空の場合
”一致するツイートがありません”という引数を与え、appendErrMsgToHTML関数を呼び出します。
tweetsに投稿の情報が入っている場合のappendTweet関数を定義します。【例】_tweet.html.erb<div class="content_post" style="background-image: url(<%= tweet.image %>);"> <div class="more"> <span><%= image_tag 'arrow_top.png' %></span> <ul class="more_list"> <li> <%= link_to "詳細", tweet_path(tweet.id), method: :get %> </li> <% if user_signed_in? && current_user.id == tweet.user_id %> <li> <%= link_to '編集', "/tweets/#{tweet.id}/edit", method: :get %> </li> <li> <%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %> </li> <% end %> </ul> </div> <%= simple_format(tweet.text) %> <span class="name"> <a href="/users/<%= tweet.user_id %>"> <span>投稿者</span><%= tweet.user.nickname %> </a> </span> </div>app/assets/javascripts/search.$(function() { var search_list = $(".contents.row"); #追加〜 function appendTweet(tweet) { if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){ var current_user = `<li> <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a> </li> <li> <a href="/tweets/${tweet.id}" data-method="delete" >削除</a> </li>` } else { var current_user = "" } var html = `<div class="content_post" style="background-image: url(${tweet.image});"> <div class="more"> <span><img src="/assets/arrow_top.png"></span> <ul class="more_list"> <li> <a href="/tweets/${tweet.id}" data-method="get" >詳細</a> </li> ${current_user} </ul> </div> <p>${tweet.text}</p><br> <span class="name"> <a href="/users/${tweet.user_id}"> <span>投稿者</span>${tweet.nickname} </a> </span> </div>` search_list.append(html); } #〜追加 $(".search-input").on("keyup", function() { var input = $(".search-input").val(); $.ajax({ type: 'GET', url: '/tweets/search', data: { keyword: input }, dataType: 'json' }) .done(function(tweets) { search_list.empty(); #追加 if (tweets.length !== 0) { tweets.forEach(function(tweet){ appendTweet(tweet); }); } else { appendErrMsgToHTML("一致するツイートがありません"); } }) }); });上記の追加の部分は削除した投稿情報のhtmlをもう一度作成しています
違う部分は<%= %>で出力しているものをjbuilderで取得した値に変えています。
<%= %>で出力しているものは、jbulider取得した値を${}で出力することができます。
次に、tweetsに投稿の情報が入っていない場合のappendErrMsgToHTML関数を定義します。app/assets/javascripts/search.js$(function() { var search_list = $(".contents.row"); function appendTweet(tweet) { if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){ var current_user = `<li> <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a> </li> <li> <a href="/tweets/${tweet.id}" data-method="delete" >削除</a> </li>` } else { var current_user = "" } var html = `<div class="content_post" style="background-image: url(${tweet.image});"> <div class="more"> <span><img src="/assets/arrow_top.png"></span> <ul class="more_list"> <li> <a href="/tweets/${tweet.id}" data-method="get" >詳細</a> </li> ${current_user} </ul> </div> <p>${tweet.text}</p><br> <span class="name"> <a href="/users/${tweet.user_id}"> <span>投稿者</span>${tweet.nickname} </a> </span> </div>` search_list.append(html); } function appendErrMsgToHTML(msg) { #追加〜 var html = `<div class='name'>${ msg }</div>` search_list.append(html); } #〜追加 $(".search-input").on("keyup", function() { var input = $(".search-input").val(); $.ajax({ type: 'GET', url: '/tweets/search', data: { keyword: input }, dataType: 'json' }) .done(function(tweets) { search_list.empty(); if (tweets.length !== 0) { tweets.forEach(function(tweet){ appendTweet(tweet); }); } else { appendErrMsgToHTML("一致するツイートがありません"); } }) }); });追加した部分もtweetsに値が入っている場合とやっていることが同じです。
コントローラーで検索をかけ、その投稿情報がなかった場合は、「一致するツイートがありません」という文字列を引数に渡してHTML要素を作成しビューに追加しています。エラー時の処理
最後に、通信に失敗した場合の処理を実装します。
アラートで「投稿検索に失敗しました」と表示します。app/assets/javascripts/search.js$(function() { var search_list = $(".contents.row"); function appendTweet(tweet) { if(tweet.user_sign_in && tweet.user_sign_in.id == tweet.user_id){ var current_user = `<li> <a href="/tweets/${tweet.id}/edit" data-method="get" >編集</a> </li> <li> <a href="/tweets/${tweet.id}" data-method="delete" >削除</a> </li>` } else { var current_user = "" } var html = `<div class="content_post" style="background-image: url(${tweet.image});"> <div class="more"> <span><img src="/assets/arrow_top.png"></span> <ul class="more_list"> <li> <a href="/tweets/${tweet.id}" data-method="get" >詳細</a> </li> ${current_user} </ul> </div> <p>${tweet.text}</p><br> <span class="name"> <a href="/users/${tweet.user_id}"> <span>投稿者</span>${tweet.nickname} </a> </span> </div>` search_list.append(html); } function appendErrMsgToHTML(msg) { var html = `<div class='name'>${ msg }</div>` search_list.append(html); } $(".search-input").on("keyup", function() { var input = $(".search-input").val(); $.ajax({ type: 'GET', url: '/tweets/search', data: { keyword: input }, dataType: 'json' }) .done(function(tweets) { search_list.empty(); if (tweets.length !== 0) { tweets.forEach(function(tweet){ appendTweet(tweet); }); } else { appendErrMsgToHTML("一致するツイートがありません"); } }) .fail(function() { #追加〜 alert('error'); }); #〜追加 }); });サーバーエラーの場合、このfailの関数が呼ばれます。
- 投稿日:2020-03-20T18:13:19+09:00
超最低限のRailsアプリをテストする(モデル編)
この記事の基本的な方針
テストは重要です。
なぜなら、人間は不完全であるからです。
ではもし自分が完全であったら、テストは不要でしょうか?
いいえ。なぜなら他人にコードの妥当性を証明しなくてはならないからです。テストの目的以下です。 ①コードの妥当性を自分で確認すること ②コードの妥当性を他人に示すことここでは別記事、超最低限のRailsアプリを丁寧に作る(もう一度きちんと復習して初心者を卒業しよう)で作ったアプリをテストします。
手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。
Terminal$ git clone https://github.com/annaPanda8170/minimum_rails_application.git $ bundle install $ bundle exec rake db:create $ bundle exec rake db:migrate基本解説はしません。手順のみ示します。
想定する読み手
既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。具体的な手順
①登録条件確認
deviseでデフォルトで設定されたEmailとPasswordの制約を確認します。
見るべきは、(1)devise公式GitHub内のバリデーションに関するファイルと(2)
config/initializers/devise.rb
です。(1)devise公式GitHub内のバリデーションに関するファイルには、「Passwordは空ではならない」、「Emailはユニークで空ではならない」とあります。
(2)
config/initializers/devise.rb
にはconfig/initializers/devise.rb#省略 config.password_length = 6..128 #省略 config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ #省略とあります。
よって今回は、
Passwordは、 6文字から128文字である。Emailは、 ユニークで かつ @の前後に@と空白以外が1文字以上ずつ」である。を確認すべく、テストします。
②準備
Gemfile#省略 group :development, :test do #省略 gem 'rspec-rails' gem 'factory_bot_rails' end group :development do #省略 gem 'spring-commands-rspec' end #省略※spring-commands-rspecは起動時間を速くするためのものでなくても問題はありません。
Terminal$ bundle install $ rails g rspec:install $ rails g rspec:model user $ rails g factory_bot:model user $ bundle exec spring binstub rspec control + c $ rails s.rspec--format documentation※これでRspecの出力が読みやすくなるそうです。
ここで空っぽのまま一度起動してみます。
Terminal$ bundle exec rspecOutput#省略 Finished in 0.00255 seconds (files took 1.69 seconds to load) 1 example, 0 failures, 1 pending※1個のテストに対して0個の失敗があり、1個保留ですという意味です。
③テスト構築
spec/models/user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる" it "Passwordが5文字で登録できない" #パスワード文字数上限の方は省きます it "passwordとpassword_confirmationが異なっていると登録できない" it "Emailが@がないと登録できない" it "Emailが@が二つあると登録できない" it "Emailが途中に空白があると登録できない" it "2人のユーザーについて、Emailがユニークであれば登録できる" it "2人のユーザーについて、Emailがユニークでなければ登録できない" endTerminal$ bundle exec rspecOutput2020-03-20 00:51:43 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead. User Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる (PENDING: Not yet implemented) Passwordが5文字で登録できない (PENDING: Not yet implemented) passwordとpassword_confirmationが異なっていると登録できない (PENDING: Not yet implemented) Emailが@がないと登録できない (PENDING: Not yet implemented) Emailが@が二つあると登録できない (PENDING: Not yet implemented) Emailが途中に空白があると登録できない (PENDING: Not yet implemented) 2人のユーザーについて、Emailがユニークであれば登録できる (PENDING: Not yet implemented) 2人のユーザーについて、Emailがユニークでなければ登録できない (PENDING: Not yet implemented) Pending: (Failures listed here are expected and do not affect your suite's status) 1) User Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる # Not yet implemented # ./spec/models/user_spec.rb:4 2) User Passwordが5文字で登録できない # Not yet implemented # ./spec/models/user_spec.rb:5 3) User passwordとpassword_confirmationが異なっていると登録できない # Not yet implemented # ./spec/models/user_spec.rb:7 4) User Emailが@がないと登録できない # Not yet implemented # ./spec/models/user_spec.rb:8 5) User Emailが@が二つあると登録できない # Not yet implemented # ./spec/models/user_spec.rb:9 6) User Emailが途中に空白があると登録できない # Not yet implemented # ./spec/models/user_spec.rb:10 7) User 2人のユーザーについて、Emailがユニークであれば登録できる # Not yet implemented # ./spec/models/user_spec.rb:11 8) User 2人のユーザーについて、Emailがユニークでなければ登録できない # Not yet implemented # ./spec/models/user_spec.rb:12 Finished in 0.00215 seconds (files took 2.4 seconds to load) 8 examples, 0 failures, 8 pendingspec/factories/users.rbFactoryBot.define do factory :user do email {"a@a"} password {"111111"} password_confirmation {"111111"} end endspec/models/user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる" do expect(FactoryBot.build(:user)).to be_valid end it "Passwordが5文字で登録できない" #パスワード文字数上限の方は省きます it "passwordとpassword_confirmationが異なっていると登録できない" it "Emailが@がないと登録できない" it "Emailが@が二つあると登録できない" it "Emailが途中に空白があると登録できない" it "2人のユーザーについて、Emailがユニークであれば登録できる" it "2人のユーザーについて、Emailがユニークでなければ登録できない" endTerminal$ bundle exec rspecOutput#省略 8 examples, 0 failures, 7 pendingエラー文は以下のように確認します。
Terminalirb(main):001:0> user = User.new(email: "a@a", password: "11111", password_confirmation: "11111") (0.9ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483 => #<User id: nil, email: "a@a", created_at: nil, updated_at: nil> irb(main):002:0> user.valid? User Exists (0.4ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'a@a' LIMIT 1 => false irb(main):003:0> user.errors => #<ActiveModel::Errors:0x00007fd9f12cc6f8 @base=#<User id: nil, email: "a@a", created_at: nil, updated_at: nil>, @messages={:password=>["is too short (minimum is 6 characters)"]}, @details={:password=>[{:error=>:too_short, :count=>6}]}>④テスト本番
spec/models/user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる" do expect(FactoryBot.build(:user)).to be_valid end it "Passwordが5文字で登録できない" do user = FactoryBot.build(:user, password: "11111", password_confirmation: "11111") user.valid? expect(user.errors[:password]).to include("is too short (minimum is 6 characters)") end #パスワード文字数上限の方は省きます it "passwordとpassword_confirmationが異なっていると登録できない" do user = FactoryBot.build(:user, password: "111111", password_confirmation: "211111") user.valid? expect(user.errors[:password_confirmation]).to include("doesn't match Password") end it "Emailが@がないと登録できない" do user = FactoryBot.build(:user, email: "aaa") user.valid? expect(user.errors[:email]).to include("is invalid") end it "Emailが@が二つあると登録できない" do user = FactoryBot.build(:user, email: "a@@a") user.valid? expect(user.errors[:email]).to include("is invalid") end it "Emailが途中に空白があると登録できない" do user = FactoryBot.build(:user, email: "a @a") user.valid? expect(user.errors[:email]).to include("is invalid") end it "2人のユーザーについて、Emailがユニークであれば登録できる" do FactoryBot.create(:user) expect(FactoryBot.build(:user, email: "b@b")).to be_valid end it "2人のユーザーについて、Emailがユニークでなければ登録できない" do FactoryBot.create(:user) user = FactoryBot.build(:user) user.valid? expect(user.errors[:email]).to include("has already been taken") end endTerminal$ bundle exec rspecOutput2020-03-20 02:01:54 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead. User Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる Passwordが5文字で登録できない passwordとpassword_confirmationが異なっていると登録できない Emailが@がないと登録できない Emailが@が二つあると登録できない Emailが途中に空白があると登録できない 2人のユーザーについて、Emailがユニークであれば登録できる 2人のユーザーについて、Emailがユニークでなければ登録できない Finished in 0.06768 seconds (files took 2.31 seconds to load) 8 examples, 0 failuresまとめ
もし、パスワード全パターンを試すようなことができれば完璧なテストですが、それはできません。
例えば6桁に限定したとしても、数字10種とアルファベット26文字の大文字と小文字で計算すると、
(10 + 26 + 26)^6 = 56,800,235,584
つまり500億パターン以上で、これを128桁まで考えると気が遠くなるパターンがあることがわかります。
そもそも全パターン試せるようなパスワードがあればパスワードとしての価値がありません。
物理的に全パターンを試すことが出来ない我々は、限界値の両端をうまくテストしなくてはなりません。
- 投稿日:2020-03-20T18:05:49+09:00
【Rails】スレッドのレス投稿機能
プログラミング初心者です。
Ruby on Railsで掲示板のスレッドにレスを紐付けて投稿していく機能を作成しました。
結構苦戦したので、備忘のため貼り付けます。【スレッド(親、tree)controller】
def show @tree = Tree.find(params[:id]) @response = Response.new(:tree_id => params[:id]) #ここを投稿用に使う @responses = @tree.responses.all end【レス(子、response)controller】
def create @response = current_user.responses.new(response_params) if @response.save redirect_to tree_url(@response.tree_id), notice: "投稿「#{@response.text}」を登録しました" else render tree_url(@response.tree_id) end end private def response_params params.require(:response).permit(:text, :user_id, :tree_id) end【スレッド(親、tree)viewのshow】
= form_with model: @response, url_for: { controller: :responses, action: :create }, local: true do |f| .form-group = f.label :text, "コメント" = f.text_field :text, class: "form-control", id: "response_text" = f.hidden_field :tree_id = f.submit "投稿", class: "btn btn-primary"【routes.rb】
post "/responses", to: "responses#create" resources :trees感想
特にルーティングエラーが多く発生しました。肝となるコードは以下の2点です。
# 入力フォーム url_for: { controller: :responses, action: :create }# ルーティング post "/responses", to: "responses#create"
- 投稿日:2020-03-20T18:04:48+09:00
GoogleMapsAPIの経度緯度の取得について
前提
GoogleMapsAPIにより、GoogleMapが表示できていること。
検索した場所の保存ができる一連の流れ(コントローラ、データベース等)を作成していること。やりたいこと
上記APIを使用し、webアプリの機能の一つとして実装したいことは下記のとおり。
①検索フォームに、店名や地名などの名称を入力し検索すると、地図上にマーカーが立つ。
②その場所を保存する。
【詳細】
別途用意した、保存フォームに検索フォームに入力した名称が自動入力される。そして①の検索の際、取得した緯度経度で場所の保存を行う。環境
開発環境 AWScloud9
rails 5.1.6①について
https://qiita.com/yoshi_yast/items/521c1f36306a180f45dd を参考にさせていただき、名称検索でマーカーを立てるところまで実装できた。
ちなみに、マーカーは、http://foonyan.sakura.ne.jp/wisteriahill/google_icons/index.html から使用した。②について
①に追加する。
<script> ... geocoder.geocode({ address: place }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { var bounds = new google.maps.LatLngBounds(); for (var i in results) { if (results[0].geometry) { ... //以降追加。id="lat"と"lng","name"をそれぞれ取得 document.getElementById('lat').value=results[0].geometry.location.lat(); document.getElementById('lng').value=results[0].geometry.location.lng(); document.getElementById('name').value=place;取得できた経度、緯度そして検索した名称(id=name)を保存フォームに渡す。
〇〇.html.erb<%= form_for(place) do |f| %> <%= f.text_field :name, id:"name" %> <%= f.hidden_field :latitude, id:"lat" %> <%= f.hidden_field :longitude, id:"lng" %> <%= f.submit %> <% end %>以上で取得から保存までができた。
- 投稿日:2020-03-20T17:31:23+09:00
[学習ログ]CVE-2020-5267について簡単に調べてみた[Rails]
tl;dr
CVE-2020-5267とは、バージョン6.0.2.2および5.2.4.2以前のActionViewに存在する脆弱性である。
該当バージョンのActionViewにおけるjおよびescape_javascriptメソッドにXSSの脆弱性が存在する可能性がある。railsの場合はGemfile中のrailsをバージョンアップすることで対策が可能
(6.0.2.2 or 5.2.4.2)
。
昨日、Githubから依存関係の脆弱性を指摘する以下のようなアラートが通知された。
We found potential security vulnerabilities in your dependencies.
Only the owner of this repository can see this message.
Manage your notification settings or learn more about vulnerability alerts.セキュリティアラートを確認すると、actionviewに関してアラートが出ていることが分かった。
Remediation
Upgrade actionview to version 5.2.4.2 or later. For example:
gem "actionview", ">= 5.2.4.2"
Always verify the validity and compatibility of suggestions with your codebase.CVE-2020-5267とは?
CVE-2020-5267についてググると、どうやらXSSに関する脆弱性らしい(私の認識が間違っていたら申し訳ないです)。
以下、コピペCVE-2020-5267 Detail
Description
In ActionView before versions 6.0.2.2 and 5.2.4.2, there is a possible XSS vulnerability in ActionView's JavaScript literal escape helpers. Views that use thej
orescape_javascript
methods may be susceptible to XSS attacks. The issue is fixed in versions 6.0.2.2 and 5.2.4.2.There is a possible XSS vulnerability in ActionView's JavaScript literal
escape helpers. Views that use thej
orescape_javascript
methods
may be susceptible to XSS attacks.
Versions Affected: All.
Not affected: None.
Fixed Versions: 6.0.2.2, 5.2.4.2補足:XSS(クロスサイトスクリプティング)とは?
クロスサイトスクリプティングとは、攻撃者の作成したスクリプトを脆弱性のある標的サイトのドメインの権限において閲覧者のブラウザで実行させる攻撃一般を指す。
CVE-2020-5267による影響
この脆弱性を利用した際のコードの一例も載せておきます
<script>let a = `<%= j unknown_input %>`</script> <script>let a = `<%= escape_javascript unknown_input %>`</script>CVE-2020-5267の対処方法(Railsの場合)
対処方法については二種類ある。
1.Railsのバージョンを更新する
冒頭で述べた通り、Railsのバージョンを6.0.2.4か5.2.4.2に更新することで、Railsに依存しているactionviewのバージョンを更新する。
一例Gemfile - gem 'rails' ~>"5.1.4.2" + gem 'rails' ~>"5.2.4.2"https://weblog.rubyonrails.org/2020/3/19/Rails-6-0-2-2-and-5-2-4-2-has-been-released/
2.モンキーパッチをあてる
Railsのバージョンを更新できない場合は、以下のようなモンキーパッチをあてて対処する。
ActionView::Helpers::JavaScriptHelper::JS_ESCAPE_MAP.merge!( { "`" => "\\`", "$" => "\\$" } ) module ActionView::Helpers::JavaScriptHelper alias :old_ej :escape_javascript alias :old_j :j def escape_javascript(javascript) javascript = javascript.to_s if javascript.empty? result = "" else result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u, JS_ESCAPE_MAP) end javascript.html_safe? ? result.html_safe : result end alias :j :escape_javascript endhttps://github.com/rails/rails/security/advisories/GHSA-65cv-r6x7-79hv
- 投稿日:2020-03-20T17:06:16+09:00
Railsのtext_fieldにCSSをあてる方法
text_fieldにCSSってどうやってあてるんだっけ?
Railsアプリケーション作成時にフロント部分を作成していた時に
example.haml.html.item__name 商品名 .name--input = f.text_field :name, placeholder: "40文字まで"のようなhamlを作成していざscssを記述しようとしたときに
このtext_filed
にどうやってCSSをあてるのかが
ふと考えると分からなかったんです。同じような
number_field
やsubmit
などにも使えるやり方なので
是非覚えておいたほうがよいです。そもそも
text_field
で作られるHTMLは何なのか?上の
= f.text_field :name, placeholder: "40文字まで"
で作られるHTMLを
chromeの検証で確認してみると
<input placeholder="40文字まで" type="text" name="item[name]" id=item_name>
というものが作成されているのがわかるかと思います。text_fieldとかはRailsがviewを簡潔に記述するために用意してくれているヘルパーメソッドのため
簡単な記述で実際はこういうHTML文の作成もしてくれています。
この作成されたHTMLにあてるようにCSSを記述すればOKです。今回はSCSSを使い記述しました。
CSSをあててみよう
example.scss.item__name{ input[type="text"]{ width: 100%; height: 20px; font-size: 14px; } ::placeholder{ padding: 5px 5px; } }と指定して記述すると、
text_field
にCSSをあてることができます。
placholder
はCSSの擬似要素のため上記のような記述をする必要があります。ヘルバーメソッドを使って記述した場合にどのようなHTML文が作成されているのかを確認すると解決できる部分でしたね。
参考先
- 投稿日:2020-03-20T16:47:10+09:00
Railsチュートリアルメモ - 第12章
サマリ
- パスワードの再設定
- パスワード再設定メールの送信
- トークンによる認証とパスワードの再設定
ポイント
- パスワードリセット機能はUserモデルを拡張して使用するため、MVCのうち新たに作成するのはViewとControllerのみで良い
- 処理のコントローラーのアクションがいろいろ登場するので、流れを整理しないと混乱する
sessions/new.html.erb →password_resets_controller#new →password_resets/new.html.erb →password_resets_controller#create →メール送信 →password_resets_controller#edit →password_resets/edit.html.erb →password_resets#update
@user.authenticated?(:reset, params[:id])
でresetトークンとDBに保存されたダイジェストを照合する- パスワード再設定用メールを送信する際に、パスワードリセットトークンとダイジェストを生成する。
- トークンはメール内のリンクに埋め込んでユーザーに送付する
- ダイジェストはDBに保存し、リンクが開かれた際に両者を照合して本人であることを確認する
- #edit時点では、メール内のリンクに埋め込んだURLパラメーターからメールアドレスを取得できるが、#updateは画面から呼び出すので、同じ方法でメールアドレスを取得することができない。そのため、edit.html.erbの中の隠しフィールドにメールアドレスを持たせて#updateに引き渡す。
<%= hidden_field_tag :email, @user.email %>
感想
- 11章と内容的にはほぼ同じだったが、その分説明が少なくて少し混乱した
- herokuでの動作は問題なかったが、ローカル動作時にパスワード再設定用リンクを開こうとすると画面に
このサイトは安全に接続できませんlocalhostから無効な応答が送信されました。
と表示され、ログに以下が吐かれてしまった2020-03-18 02:53:45 +0900: HTTP parse error, malformed request (): #<Puma::HttpParserError: Invalid HTTP format, parsing fails.>
- 原因はメールリンクがhttpsになっているためで、httpでリンクを開くと画面が正常に表示された
- localhostにhttpsで繋ぐ場合は少し追加の設定が必要 参考記事
- 投稿日:2020-03-20T16:36:27+09:00
【Rails】Action Mailer
Action Mailer
Action MailerとはRuby on Railsに標準で組み込まれているメール送信機能です。
Railsからメールが送信されるようになります。実装
Inquiryモデル作成
Action Mailerを実装するために、ユーザーの問い合わせ内容を保存するモデルを作成します。
$ bin/rails g model inquiry name:string message:string ~~ 省略 ~~ $ rails db:migrateInquiryモデルは氏名(name)、メッセージ(message)の属性を持ちます。
ApplicationMailerを継承するクラスを作成
ApplicationMailerを継承するクラスは
bin/rails generate mailer 任意のメイラー名
で作成できます。$ bin/rails generate mailer inquiryapp/mailers/inquiry_mailer.rbclass InquiryMailer < ApplicationMailer endメール宛先、件名等作成
send_mailメソッドをInquiryMailerクラスに記載してメールを送信できるようにします。
app/mailers/inquiry_mailer.rbdef send_mail(inquiry) @inquiry = inquiry mail( from: 'system@example.com', to: 'manager@example.com', subject: 'お問い合わせ通知' ) endそれぞれ下記のように設定されています。
・from :
→ 送信元のメールアドレス
・to :
→ 送信先のメールアドレス
・subject :
→ 件名
他には下記があります。
・cc :
→ CCのメールアドレス
・bcc :
→ BCCのメースアドレスメール本文のレイアウト
メール本文のレイアウトを作成するためには命名規則に従ってerbファイルを作成します。
新規でapp/views/inquiry_mailer/send_mail.text.erb
ファイルを作成しメール内容を記載していきます。app/views/inquiry_mailer/send_mail.text.erb<%= @inquiry.name %> 様 から問い合わせがありました。 ・お問い合わせ内容 <%= @inquiry.message %>このようにerbのタグを使用してコードを記載することが可能です。
メールをプレビューで確認
メール本文のレイアウトを確認するためにAction Mailerにはプレビュー機能があります。
先ほど
generateコマンド
で生成したActionMailer::Preview
を継承したinquiry_mailer_preview.rb
があるので、こちらにコーティングしていきます。test/mailers/previews/inquiry_mailer_preview.rb# Preview all emails at http://localhost:3000/rails/mailers/inquiry_mailer class InquiryMailerPreview < ActionMailer::Preview def inquiry inquiry = Inquiry.new(name: "侍 太郎", message: "問い合わせメッセージ") InquiryMailer.send_mail(inquiry) end end実際に確認をしみましょう。
rails s
でサーバを起動しhttp://ホスト名:3000/rails/mailers/inquiry_mailer
にアクセスすると
このように表示されるので、・ inquiry
をクリックすると
※inquiryのリンクは、InquiryMailerPreviewクラスに作成したinquiryメソッドに対応して表示されています。
が表示されて、メール本文のレイアウトを確認することができます。
こちらメールが送信されることはないので何度でも確認することができます。メールサーバの設定
実際にメール送信を行うために設定をしていきます。
なお、Gmailでは2段階認証等があるためセキュリティの状態によっては送信できない可能性があります。config/environments/development.rbconfig.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: 587, domain: 'gmail.com', user_name: '<gmailのメールアドレス>', password: '<gmailのパスワード>', authentication: 'plain', enable_starttls_auto: true }上記のようにメールサーバの設定をコーディングします。
メール送信
メールを送信するためにdeliver_nowメソッドを使用する。
本記事のサンプルプログラムで実行するためには、InquiryMailer.send_mail(モデル変数).deliver_now
で実行します。コントローラに設定すれば実行できますが今回はコンソールから実行します。
app/mailers/inquiry_mailer.rb
のto :
やfrom :
を変更し実行します。$ bin/rails c ~~ 省略 ~~ irb(main):001:0> inquiry = Inquiry.new(name: "侍 太郎", message: "問い合わせメッセージ") irb(main):002:0> InquiryMailer.send_mail(inquiry).deliver_now実行後に設定したメールアドレスにメールが送信されていれば成功です。
応用的なメール送信方法
HTML形式なレイアウトのメール送信
Action MailerでHTML形式のメールを送信するためには、
app/views/メイラー名_mailer/メイラークラスのメソッド名.html.erb
とファイル名を記載しメール本文もHTML形式で記載します。app/views/inquiry_mailer/send_mail.html.erb:<!DOCTYPE html> <html> <head> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' /> </head> <body> <h1><%= @inquiry.name %> 様 から問い合わせがありました。</h1> <p>・お問い合わせ内容</p> <p><%= @inquiry.message %></p> </body> </html>プレビュー機能でレイアウトを確認します。
HTML形式のメールレイアウトであることを確認できました。また下記の2つのようにテキストメールとHTMLメールの2つが存在する場合はRailsはマルチパートメールを自動生成して送信します。
・app/views/メイラー名mailer/メイラークラスのメソッド名.text.erb
・app/views/メイラー名mailer/メイラークラスのメソッド名.html.erbマルチパートメールとはテキスト、HTMLメールの両方を送信してメールクライアント側で自動判定してメールを表示する仕組みです。
添付ファイル付きのメール送信
添付ファイルをメールに付ける機能を追加するには下記をメイラークラスに追記します。
attachments[添付ファイル名] = 添付するファイルデータ
app/mailers/inquiry_mailer.rbclass InquiryMailer < ApplicationMailer def send_mail(inquiry) @inquiry = inquiry # 追記 attachments['sample.jpg'] = ..File.read(‘./tmp/sample.jpg') mail( from: 'system@example.com', to: 'manager@example.com', subject: 'お問い合わせ通知' ) end end
- 投稿日:2020-03-20T16:33:21+09:00
railsのサーバーの強制終了
railsのサーバーの強制終了について
railsを使って自宅で開発しているときに、
$ bin/rails sを使ってローカルホストのサーバを立てる。
そのとき、間違って
サーバーを起動した状態のターミナルのタブを消してしまって、ctrl + c
での終了ができなくなってしまい、困ったという経験がある。その時の対処法、すなわち強制終了の方法をメモしておく。
kill コマンドを使って強制終了
実際に実験してみると、
まず、$ bin/rails sを打ってローカルのサーバを立ち上げる。
その後確認してほしいのは
プロジェクト名/tmp/pids/server.pid
のファイル。
実際に見てみると、
project/tmp/pids/server.pid1 1159 #ここに何らかの数字が入っているはず 2これはサーバーを立ち上げたときに生成される数字で、サーバーを終了させたときに自動で消去される。
この数字を使って、
$ kill 1159とコマンドに打ち込めばサーバーは強制終了される。
- 投稿日:2020-03-20T15:50:47+09:00
Rails5 CSV import 一括ユーザー登録
Rails5でCSV読み込んで一括ユーザー登録機能作成
CSVの読み込みでちょっとつまずいたのでメモ。
ほんとーに初歩的なミスでした。
基本的にコチラを参考にさせてもらいました。
https://qiita.com/seitarooodayo/items/c9d6955a12ca0b1fd1d4さて出てきたエラーはコチラ
ActiveRecord::RecordInvalid (Validation failed: Name can't be blank, Password can't be blank, Password can't be blank, Password is too short (minimum is 6 characters)):
よくあるバリデーションに引っかかったやつですね。
blankってことはnilになっちゃってると言うことか。
と言うことはcsvファイルが読み込まれてないのか?
Parameters: {"utf8"=>"✓", "authenticity_token"=>"lzZScpvbb+vbmrJaxYMW3SweQrA0ISM0ssFX9GKIIL1gU6EMWD3IutOb0x5QnaLQwVcW1hUnNMTnZSxTgsHAvw==", "file"=>#<ActionDispatch::Http::UploadedFile:0x00007fe4b41926e8 @tempfile=#<Tempfile:/tmp/RackMultipart20200320-11805-1j4vvvo.csv>, @original_filename="csvimporttest.csv", @content_type="text/csv", @headers="Content-Disposition: form-data; name=\"file\"; filename=\"csvimporttest.csv\"\r\nContent-Type: text/csv\r\n">, "commit"=>"ファイル読み込み登録"}
users
User Load (0.2ms) SELECT.* FROM
usersWHERE
users.
idIS NULL LIMIT 1
パラメータとしては渡っている。。
しかしSELECT
users.* FROM
usersWHERE
users.
idIS NULL
なんでだ、、binding.pryしてみよう。
11: def self.import(file)
12: CSV.foreach(file.path, headers: true) do |row|
13: # IDが見つかれば、レコードを呼び出し、見つかれなければ、新しく作成
14: user = find_by(id: row["id"]) || new
15: # CSVからデータを取得し、設定する
16: binding.pry
=> 17: user.attributes = row.to_hash.slice(*updatable_attributes)
18: # 保存する
19: user.save!
20: end
21: end
[1] pry(User)> puts user.attributes
{"id"=>nil, "name"=>nil, "firstchild"=>nil, "secondchild"=>nil, "thirdchild"=>nil, "password_digest"=>nil, "email"=>nil, "admin"=>false, "created_at"=>nil, "updated_at"=>nil}
=> nil
[2] pry(User)> puts user
=> nil
[3] pry(User)> puts row
Id
9
Name
シーエスブイ太郎1
Password
Csvtest1
password_confirmation
Csvtest1
あ、rowには値が入ってる。。
その先に値が渡ってない。
ん?IdとかNameとか大文字入ってるジャン。ひょっとして、、、あーやっぱり。
と言うことでMacbookのnumbersで作成したcsvファイルだったのですがデフォルトで入力値の頭文字が大文字になる仕様でした。。
気づかずそのまま使ってたのが原因でしたね。
初歩的な原因でしたけど、こう言う風に原因追求するのってプログラミングの面白さの一つですよね。
binding.pryはよく使うようにしてます。
- 投稿日:2020-03-20T15:11:31+09:00
【Rails】小数点以下の不要な0を制御する number_with_precision
小数点の表示で0の場合だけなくすという仕様で困ったことはありませんか?
具体的には以下のような仕様です。1.000 => 1
1.100 => 1.1
1.110 => 1.11解決方法
ActionView::Helpers::NumberHelperクラスのnumber_with_precision(optionの strip_insignificant_zeros: true)を使います。
number_with_precision([数値],precision: [有効にしたい小数点桁数], strip_insignificant_zeros: true)
precisionを省略した場合、デフォルトは3です。
コードで仕様説明
小数点以下の不要な0が全て切捨てられています。
画像の[6]をみていただくとわかりますがprecisionが3のため小数点第3位の数字までしか表示されていません。画像の[7]はpricisionを4にしているため小数点第4位まで表示されています。
- 投稿日:2020-03-20T15:11:31+09:00
【Rails】小数点以下の不要な0を削除する number_with_precision
小数点の表示で0の場合だけなくすという仕様で困ったことはありませんか?
具体的には以下のような仕様です。1.000 => 1
1.100 => 1.1
1.110 => 1.11解決方法
ActionView::Helpers::NumberHelperクラスのnumber_with_precision(optionの strip_insignificant_zeros: true)を使います。
number_with_precision([数値],precision: [有効にしたい小数点桁数], strip_insignificant_zeros: true)
precisionを省略した場合、デフォルトは3です。
コードで仕様説明
小数点以下の不要な0が全て切捨てられています。
画像の[6]をみていただくとわかりますがprecisionが3のため小数点第3位の数字までしか表示されていません。画像の[7]はpricisionを4にしているため小数点第4位まで表示されています。
- 投稿日:2020-03-20T14:57:51+09:00
Excon::Error::Socket: getaddrinfo: Name or service not known (SocketError)というエラー文の対応
環境
$ ruby -v
ruby 2.6.5
$ rails -v
Rails 5.2.4Amazon Linux 2
状況
本番環境としてデプロイした後に、画像をCarriorwave,fogを通じてS3にアップロードしようとすると、エラーが発生。
エラー文は以下の2種類。Excon::Error::Socket: getaddrinfo: Name or service not known (SocketError)SocketError: getaddrinfo: Name or service not known本件の原因
config/initializer/carrior_wave.rbif Rails.env.production? CarrierWave.configure do |config| config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', region: 'ap-northeast-1a', # 問題箇所 aws_access_key_id: 'アクセスキー', aws_secret_access_key: 'シークレットキー' } config.fog_directory = 'バケット名' config.cache_storage = :fog end end
'ap-northeast-1a'
なんてリージョンはなく、ap-northeast-1
の間違いであった。解決した対応
問題箇所の
ap-northeast-1a
をap-northeast-1
にしてpumaを再起動した。こんなしょうもないミスで2時間潰しました笑
- 投稿日:2020-03-20T14:10:51+09:00
[scss]hamlへの反映方法は2種類あった 備忘録
今までscssのhamlへの反映方法といったら
application.scss
へ@import記述のみと思っていましたが。実はそんなことをしなくてもコマンド入力で作成したscssには
自動的に反映される記述が入っているんです。
知りませんでした。。。
コメントアウトされ得た以下の3行があるとOKらしいscss// Place all the styles related to the reset controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ですので、scssの反映方法を以下2種類をまとめたいと思います
コマンドで作成する方法
importいらないコマンド存在したー!!!
ターミナル$ rails g assets 作成したいファイル名以上です。
あとはscssに記述したものは自動的にhamlに反映されます。手動でファイルを作成する方法
右クリックでファイル作成をした場合scssにimport記述は自動生成されませんので
application.scss
にimportしてhamlにscssが当たるようにしましょう。やり方
・hamlに反映させたいscssのファイル名をアンダーバー「 _ 」始まりにします。
・application.scss
にimportします。
・終わり記述参考は下記
application.scss@import "home"; @import "いれたいscssファイル名(アンダーバーは入れない)"; ⬅︎終わりは必ずセミコロンを入れる #assetsの中でもフォルダの異なるものはURLをちゃんと記載しないと反映されない @import "config/reset"; @import "フォルダ名/ファイル名";参照記事
終わりに
自分のいた学校は極力コマンドを使用しない学校でしたが
コマンドはコマンドで別の仕様があることを今回知りました。
アウトプットもそうですがインプットも必要。
人との情報交換は本当に大事だなと改めて感じました。初学者な為、記事に不備やアドバイ等ございましたらご連絡頂けますと幸いです。
最後まで読んできただきありがとうございます。
- 投稿日:2020-03-20T13:07:33+09:00
RSpecで時間周りの備忘録
時間の操作
Timecopを使って止めた時間の中でテストするのが良い.
Railsには ActiveSupport::Testing::TimeHelpersでも似たことができるが、ネストしてると期待通り動かないとか細かい点でTimecopの方がおすすめです.ミリ秒を丸める
DB等でミリ秒が保持できない場合もあるのでテストが失敗することがある.
事前にミリ秒を丸めて扱っていた方が良い.# Good let(:now) { Time.current } # Best let(:now) { Time.current.change(usec: 0) }Timecopの場合
# Bad Timecop.freeze(Time.current) # Best Timecop.freeze Timecop.freeze(Time.current.change(usec: 0))⏳Timeout
RSpecを書いていると思いのほか時間がかかる処理が紛れ込んでしまう.
中には何かしらのタイミングで異常に時間がかかる処理もあったりする.タイムアウトする様にすることで不安定なテストを特定することはできます.
ただし不安定なテストを改善するわけでじゃないので、無闇にタイムアウトの時間を伸ばすのはよくない.
後、実行環境が非力だとタイムアウトが多発するのにも注意が必要です.require 'timeout' it do # 5秒経過すると `Timeout::Error` がraiseされる Timeout::timeout(5) do expect{ subject.execute }.to_not raise_error end endメタデータで制御
テストが少ないうちは大して気にならないが、ある程度の数になるとCIの実行時間が問題になってくる.
そこでRSpecにタイムアウトを仕掛けられる様にした.
spec/support/timeout.rb
に以下のコードを配置する
timeout.rb のコード
require 'timeout' class RSpecTimeoutError < Timeout::Error def message 'execution expired by RSpec' end end RSpec.configure do |config| rspec_timeout = ENV.fetch('RSPEC_TIMEOUT', 0).to_i config.around(:example) do |example| sec = example.metadata[:timeout] || rspec_timeout next example.run if sec <= 0 Timeout::timeout(sec, RSpecTimeoutError) do example.run end end config.backtrace_exclusion_patterns << /#{Regexp.escape(Pathname.new(File.expand_path(__FILE__)).relative_path_from(Rails.root).to_s)}/ end
使い方
タイムアウトの指定は2種類あり、それぞれ値にはタイムアウトする秒を指定します.
ただし0
を指定した時はタイムアウトは無効になります.環境変数を指定
RSPEC_TIMEOUT
を指定する事でタイムアウトが有効になります.
ローカル等の通常の環境ではタイムアウトはせず実行してもらいたいためです.CI等の全てのテストを実行するような場合のみ環境変数を指定すると良いでしょう.
RSPEC_TIMEOUT=20 rspecメタデータを指定
特定の箇所だけタイムアウトを制御したいときはメタデータを使います.
it('foo', timeout: 30) { ... }
- 投稿日:2020-03-20T12:23:07+09:00
【Rails】form_withについて簡単にまとめた
はじめに
form_with
について使いかたを簡単にまとめてみました。
今回はRubyonRailsAPIを色々と参照しました。form_withの機能
フォームを構成するヘルパーメソッド。
form_tag
やform_for
と同じような挙動をするが、
現在はform_with
を使用することが推奨されている。基本的な構文は下記のとおり。
= form_with model: @user do |f| = f.text_field :name = f.submit '登録'テキストフィールドに名前を入力して「登録」をおすとデータが送信される。
このとき、データはハッシュの階層構造であるparams[:user][:name]
という形で送信される。
よってコントローラーでストロングパラメーターを用いる際は下記のようになる。def user_params params.require(:user).permit(:name) endデータの送信先については、
:url
オプションを追加することによっても指定できるが、
渡されたモデルの状態(①新規②既存)によって自動推定をしてくれる。①新規作成されたモデルが渡された場合= form_with model: User.new do |f| = f.text_field :name①で生成されるHTML<form action="/users" method="post" data-remote="true"> <input type="text" name="user[name]"> </form>②既存のモデルが渡された場合= form_with model: User.first do |f| = f.text_field :name②で生成されるHTML<form action="/users/1" method="post" data-remote="true"> <input type="hidden" name="_method" value="patch"> <input type="text" name="user[name]" value="<the name of the user>"> </form>①のパターンでは、
action="/users"
であるのに対して、
②のパターンではaction="/users/1"
となっている上に隠しinputフィールドを利用してメソッドをPatch
に指定している。
これにより、上記①②のパターンでそれぞれ対応するアクションがcreate
になったりupdate
になったりする。ビュー上に表示しないものを送信したいとき
hidden_field
を使用することに対応可能。= form_with model: @user do |f| = f.hidden_field :age, :value => @user.age #ビューに表示されない = f.text_field :name = f.submit上記のように記載をすることで、ビュー上には表示されないが
params[:user][:age]
の中に
@user.age
の値を格納した状態でデータを送ることができる。ビュー上に表示させたいが編集はさせたくないとき
readonly: true
を指定すればOK。= form_with model: @user do |f| = f.text_field :name = f.text_field :age, value: @user.age, readonly: true #ビューに表示されるが編集不可 = f.submitまとめ
form_with
の仕組みが腹落ちしました。
特にモデルの状態の違いから生成されるhtmlが変わり、結果として対応するアクションが自動的に決まる仕組みのあたりは今まで曖昧な理解だったので今回がいい機会になりました。
- 投稿日:2020-03-20T10:27:40+09:00
SequelでPG::ConnectionBad: PQconsumeInput()エラーが出るようになって困った話
急に出始めた PG::ConnectionBad: PQconsumeInput() エラー
RailsでActiveRecordを使わずSequelをORMとして使っているのだが、いつからかRails起動直後のRDS(Postgresql)接続時に「PG::ConnectionBad: PQconsumeInput()」エラーが出るようになってしまった。
PG::ConnectionBad: PQconsumeInput() SSL error: decryption failed or bad record mac PG::ConnectionBad: PQconsumeInput() server closed the connection unexpectedly PG::ConnectionBad: PQconsumeInput() SSL error: sslv3 alert bad record macSequel側は変えていないのでRDSで何か仕様変更があったのか・・・。RDSのCA証明書の変更の影響かと思ったが、変更する前から当エラーは出てた。
状況
- Rails起動直後にいくつかのリクエストでこのエラーになる。全てではない。
- ある程度エラーが出るとその後は出ない。
- ステージング環境などで8時間くらいアクセスがないとまたでるようになる。
- なので、本番環境ではRails再起動直後だけこのエラーがでる。
- 開発環境のRDSではないPostgresqlだと出ない。
このエラーが出たらretryする仕組みも入れたけど、やはりそもそも出ないようにしたい。
connection_validatorを試す
真偽はわからないがpumaのようなマルチスレッドのアプリケーションサーバを使っているとこのエラーが出るらしい。Sequelのコネクションプーリングがスレッドセーフではない?
で、色々ググったところ日本語の情報はさっぱりの中、海外のサイトで以下の設定を入れれば解決するらしい情報を入手。
DB.extension(:connection_validator) DB.pool.connection_validation_timeout = -1https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/connection_validator_rb.html
コネクションプーリング内のコネクションが有効かどうかチェックして、有効でなければ再接続する設定。
DB.pool.connection_validation_timeout
はチェックする間隔(秒)で、デフォルト3600秒、−1のときは常にチェック。connection_validation_timeoutの値を変えて検証
- -1: エラーが出なくなった!
- 1以上: 起動直後はエラーでる
検証結果
DB.pool.connection_validation_timeout = -1
なら効果あり。
3600で効果出て欲しかったなあ。パフォーマンス試験
公式にもあるように、常にチェックだと気になるのがパフォーマンスの劣化。
connection_validatorありなしでApacheBenchでパフォーマンステストしてみた。connection_validatorなし
$ ab -c 100 -n 10000 "http://xxxxxxxxxx" Concurrency Level: 100 Time taken for tests: 22.620 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 15650000 bytes HTML transferred: 11390000 bytes Requests per second: 442.09 [#/sec] (mean) Time per request: 226.196 [ms] (mean) Time per request: 2.262 [ms] (mean, across all concurrent requests) Transfer rate: 675.66 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.4 0 5 Processing: 5 226 162.3 178 1102 Waiting: 5 226 162.3 178 1102 Total: 5 226 162.3 178 1102connection_validatorあり (connection_validation_timeout = -1)
$ ab -c 100 -n 10000 "http://xxxxxxxxxx" Concurrency Level: 100 Time taken for tests: 26.522 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 15650000 bytes HTML transferred: 11390000 bytes Requests per second: 377.05 [#/sec] (mean) Time per request: 265.216 [ms] (mean) Time per request: 2.652 [ms] (mean, across all concurrent requests) Transfer rate: 576.26 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 2 Processing: 10 264 157.8 231 1126 Waiting: 10 264 157.8 231 1126 Total: 10 264 157.9 231 1126(結果は3回試して一番良かった結果)
テスト結果
1リクエストあたり約40ms遅くなった。
結論
超絶負荷のかかる様なサーバではないので、40msのパフォーマンス劣化は許容範囲としてconnection_validatorを採用することにした。
auto_reconnectオプションとかあれば良いのに。
- 投稿日:2020-03-20T10:21:38+09:00
Rails deviseの導入〜新規登録、ログイン時のカラムの追加まで
初めに
某プログラミングスクールの卒業生です。
スクールに通う中で学んだことや、つまづいたことを備忘録としてまとめてます。
今回は、deviseを扱う際に必ず調べるであろう、独自カラムの追加方法をdeviseの導入からまとめておきたいと思います。環境
・Ruby 2.5.7
・Rails 5.2.4.1deviseの導入
deviseとは、ログインや新規登録機能等を簡単に実装できるgemです。
まず初めに、Gemfileに以下の1行追加して保存します。Gemfilegem 'devise'次にターミナルで下記を実行し、deviseをアプリケーションに読み込ませます。
$ bundle install最後にターミナルで下記を実行し、deviseの初期設定を行います。
$ rails g devise:installRunning via Spring preloader in process 29980 create config/initializers/devise.rb create config/locales/devise.en.yml =============================================================================== Some setup you must do manually if you haven't yet: 1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb: config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } In production, :host should be set to the actual host of your application. 2. Ensure you have defined root_url to *something* in your config/routes.rb. For example: root to: "home#index" 3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example: <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> 4. You can copy Devise views (for customization) to your app by running: rails g devise:views ===============================================================================ターミナルに上記のような表示がされれば成功です。
以上でdeviseを使う準備が整いました。deviseでモデルを作成する
準備が整ったので、いよいよdeviseを使ってみましょう。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきます。
まず初めに、モデルを作成します。
ターミナルで下記のを実行し、モデルを作成します。$ rails g devise UserRunning via Spring preloader in process 30045 invoke active_record create db/migrate/20200319222813_devise_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml insert app/models/user.rb route devise_for :usersこれで、「User」という名のモデルと、usersテーブル用のマイグレーションファイルが作成されました。
独自のカラムを追加しよう
モデルとマイグレーションファイルの作成ができたので、usersテーブルにカラムを追加しいきましょう。
今回はnameカラムを追加します。
先ほど、作成したマイグレーションファイルに以下の1行を追加します。20200319222813_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" t.string :name, null: false ←追加 ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at 以下省略追加したら、ターミナルでマイグレーションを実行しましょう。
$ rails db:migrate実際にカラムが追加できたかschemaファイルで確認します。
schema.rbActiveRecord::Schema.define(version: 2020_03_19_222813) do create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "name", null: false ←追加されている t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end追加されていることが確認できれば成功です。
Viewを追加しよう
カラムの追加がおわったら、次にViewを作成しましょう。
ターミナルで下記を実行しましょう。$ rails g devise:viewsinvoke Devise::Generators::SharedViewsGenerator create app/views/devise/shared create app/views/devise/shared/_error_messages.html.erb create app/views/devise/shared/_links.html.erb invoke form_for create app/views/devise/confirmations create app/views/devise/confirmations/new.html.erb create app/views/devise/passwords create app/views/devise/passwords/edit.html.erb create app/views/devise/passwords/new.html.erb create app/views/devise/registrations create app/views/devise/registrations/edit.html.erb create app/views/devise/registrations/new.html.erb create app/views/devise/sessions create app/views/devise/sessions/new.html.erb create app/views/devise/unlocks create app/views/devise/unlocks/new.html.erb invoke erb create app/views/devise/mailer create app/views/devise/mailer/confirmation_instructions.html.erb create app/views/devise/mailer/email_changed.html.erb create app/views/devise/mailer/password_change.html.erb create app/views/devise/mailer/reset_password_instructions.html.erb create app/views/devise/mailer/unlock_instructions.html.erbこれでdevise用のViewを作成する事ができました。
deviseをカスタマイズしよう
Viewが作成できたので、いよいよdeviseをカスタマイズしていきます。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきたいと思います。新規登録画面に名前を追加
deviseの新規登録画面はデフォルトで、以下のようになっています。
今回はここに名前(Name)を追加します。
新規登録のViewにNameを追加します。
以下を参考に追加してみてください。
※registrationsが新規登録のviewなので覚えておきましょうviews/devise/registrations/new.html.erb<h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <%# ここから %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <%# ここまで %> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autocomplete: "email" %> </div> <div class="field"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <% end %><br /> <%= f.password_field :password, autocomplete: "new-password" %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "new-password" %> </div> <div class="actions"> <%= f.submit "Sign up" %> </div> <% end %> <%= render "devise/shared/links" %>上記の追記で名前が追加できた事が確認できます。
これではまだ、名前の登録がデータベースに反映されません。
なので、最後にストロングパラメーターを設定しましょう。controllers/application_controller.rbclass ApplicationController < ActionController::Base # ここから before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end # ここまで endこれでデータベースに名前が登録できるようになりました。
今後、カラムを増やす場合はストロングパラメーターにも忘れずに追加しましょう。名前とパスワードでログインできるように変更
deviseのログイン画面はデフォルトで、以下のようになっています。
今回はEmailを名前(Name)に変更します。
ログインのViewをEmailからNameを変更します。
以下を参考に変更してみてください。
※sessionsがログインのviewなので覚えておきましょうviews/devise/sessions/new.html.erb<h2>Log in</h2> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <%# ここから %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <%# ここまで追加 %> <%# ここから %> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <%# ここまで削除 %> <div class="field"> <%= f.label :password %><br /> <%= f.password_field :password, autocomplete: "current-password" %> </div> <% if devise_mapping.rememberable? %> <div class="field"> <%= f.check_box :remember_me %> <%= f.label :remember_me %> </div> <% end %> <div class="actions"> <%= f.submit "Log in" %> </div> <% end %> <%= render "devise/shared/links" %>上記の追記でEmailが名前(Name)が変更できた事が確認できます。
これではまだ、名前でのログインができません。
devise.rbを書き換えて名前でのログインができるように変更します。config/initializers/devise.rb# ==> Configuration for any authentication mechanism # Configure which keys are used when authenticating a user. The default is # just :email. You can configure it to use [:username, :subdomain], so for # authenticating a user, both parameters are required. Remember that those # parameters are used only when authenticating and not when retrieving from # session. If you need permissions, you should implement that in a before filter. # You can also supply a hash where the value is a boolean determining whether # or not authentication should be aborted when the value is not present. # config.authentication_keys = [:email]devise.rbの中に上記のような記述があるかと思います。
その一番下の行をを変更してください。
※#を外すのを忘れないように注意してください。config/initializers/devise.rb# config.authentication_keys = [:email] ↓ 変更 config.authentication_keys = [:name]これで名前をとパスワードでログインできるようになりました。
以上で新規登録とログインのカスタマイズが終わります。最後に
備忘録程度のまとめになっているので、もしかしたら分かりにくいかもしれませんがご了承ください。
この投稿が少しでも役に立つ事があれば幸いです。
- 投稿日:2020-03-20T10:21:38+09:00
Rails deviseの導入〜新規登録、ログイン時のカラム
初めに
某プログラミングスクールの卒業生です。
スクールに通う中で学んだことや、つまづいたことを備忘録としてまとめてます。
今回は、deviseを扱う際に必ず調べるであろう、独自カラムの追加方法をdeviseの導入からまとめておきたいと思います。環境
・Ruby 2.5.7
・Rails 5.2.4.1deviseの導入
deviseとは、ログインや新規登録機能等を簡単に実装できるgemです。
まず初めに、Gemfileに以下の1行追加して保存します。Gemfilegem 'devise'次にターミナルで下記を実行し、deviseをアプリケーションに読み込ませます。
$ bundle install最後にターミナルで下記を実行し、deviseの初期設定を行います。
$ rails g devise:installRunning via Spring preloader in process 29980 create config/initializers/devise.rb create config/locales/devise.en.yml =============================================================================== Some setup you must do manually if you haven't yet: 1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb: config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } In production, :host should be set to the actual host of your application. 2. Ensure you have defined root_url to *something* in your config/routes.rb. For example: root to: "home#index" 3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example: <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> 4. You can copy Devise views (for customization) to your app by running: rails g devise:views ===============================================================================ターミナルに上記のような表示がされれば成功です。
以上でdeviseを使う準備が整いました。deviseでモデルを作成する
準備が整ったので、いよいよdeviseを使ってみましょう。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきます。
まず初めに、モデルを作成します。
ターミナルで下記のを実行し、モデルを作成します。$ rails g devise UserRunning via Spring preloader in process 30045 invoke active_record create db/migrate/20200319222813_devise_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml insert app/models/user.rb route devise_for :usersこれで、「User」という名のモデルと、usersテーブル用のマイグレーションファイルが作成されました。
独自のカラムを追加しよう
モデルとマイグレーションファイルの作成ができたので、usersテーブルにカラムを追加していきましょう。
今回はnameカラムを追加します。
先ほど、作成したマイグレーションファイルに以下の1行を追加します。20200319222813_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" t.string :name, null: false ←追加 ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at 以下省略追加したら、ターミナルでマイグレーションを実行しましょう。
$ rails db:migrate実際にカラムが追加できたかschemaファイルで確認します。
schema.rbActiveRecord::Schema.define(version: 2020_03_19_222813) do create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "name", null: false ←追加されている t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end追加されていることが確認できれば成功です。
Viewを追加しよう
カラムの追加がおわったら、次にViewを作成しましょう。
ターミナルで下記を実行しましょう。$ rails g devise:viewsinvoke Devise::Generators::SharedViewsGenerator create app/views/devise/shared create app/views/devise/shared/_error_messages.html.erb create app/views/devise/shared/_links.html.erb invoke form_for create app/views/devise/confirmations create app/views/devise/confirmations/new.html.erb create app/views/devise/passwords create app/views/devise/passwords/edit.html.erb create app/views/devise/passwords/new.html.erb create app/views/devise/registrations create app/views/devise/registrations/edit.html.erb create app/views/devise/registrations/new.html.erb create app/views/devise/sessions create app/views/devise/sessions/new.html.erb create app/views/devise/unlocks create app/views/devise/unlocks/new.html.erb invoke erb create app/views/devise/mailer create app/views/devise/mailer/confirmation_instructions.html.erb create app/views/devise/mailer/email_changed.html.erb create app/views/devise/mailer/password_change.html.erb create app/views/devise/mailer/reset_password_instructions.html.erb create app/views/devise/mailer/unlock_instructions.html.erbこれでdevise用のViewを作成する事ができました。
deviseをカスタマイズしよう
Viewが作成できたので、いよいよdeviseをカスタマイズしていきます。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきたいと思います。新規登録画面に名前を追加
deviseの新規登録画面はデフォルトで、以下のようになっています。
今回はここに名前(Name)を追加します。
新規登録のViewにNameを追加します。
以下を参考に追加してみてください。
※registrationsが新規登録のviewなので覚えておきましょうviews/devise/registrations/new.html.erb<h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <%# ここから %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <%# ここまで %> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autocomplete: "email" %> </div> <div class="field"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <% end %><br /> <%= f.password_field :password, autocomplete: "new-password" %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "new-password" %> </div> <div class="actions"> <%= f.submit "Sign up" %> </div> <% end %> <%= render "devise/shared/links" %>上記の追記で名前が追加できた事が確認できます。
これではまだ、名前の登録がデータベースに反映されません。
なので、最後にストロングパラメーターを設定しましょう。controllers/application_controller.rbclass ApplicationController < ActionController::Base # ここから before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end # ここまで endこれでデータベースに名前が登録できるようになりました。
今後、カラムを増やす場合はストロングパラメーターにも忘れずに追加しましょう。名前とパスワードでログインできるように変更
deviseのログイン画面はデフォルトで、以下のようになっています。
今回はEmailを名前(Name)に変更します。
ログインのViewをEmailからNameを変更します。
以下を参考に変更してみてください。
※sessionsがログインのviewなので覚えておきましょうviews/devise/sessions/new.html.erb<h2>Log in</h2> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <%# ここから %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <%# ここまで追加 %> <%# ここから %> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <%# ここまで削除 %> <div class="field"> <%= f.label :password %><br /> <%= f.password_field :password, autocomplete: "current-password" %> </div> <% if devise_mapping.rememberable? %> <div class="field"> <%= f.check_box :remember_me %> <%= f.label :remember_me %> </div> <% end %> <div class="actions"> <%= f.submit "Log in" %> </div> <% end %> <%= render "devise/shared/links" %>上記の追記でEmailが名前(Name)が変更できた事が確認できます。
これではまだ、名前でのログインができません。
devise.rbを書き換えて名前でのログインができるように変更します。config/initializers/devise.rb# ==> Configuration for any authentication mechanism # Configure which keys are used when authenticating a user. The default is # just :email. You can configure it to use [:username, :subdomain], so for # authenticating a user, both parameters are required. Remember that those # parameters are used only when authenticating and not when retrieving from # session. If you need permissions, you should implement that in a before filter. # You can also supply a hash where the value is a boolean determining whether # or not authentication should be aborted when the value is not present. # config.authentication_keys = [:email]devise.rbの中に上記のような記述があるかと思います。
その一番下の行をを変更してください。
※#を外すのを忘れないように注意してください。config/initializers/devise.rb# config.authentication_keys = [:email] ↓ 変更 config.authentication_keys = [:name]これで名前をとパスワードでログインできるようになりました。
以上で新規登録とログインのカスタマイズが終わります。最後に
備忘録程度のまとめになっているので、もしかしたら分かりにくいかもしれませんがご了承ください。
この投稿が少しでも役に立つ事があれば幸いです。
- 投稿日:2020-03-20T07:13:52+09:00
【Rails】小数点と3桁ずつ区切るカンマ
1234.50
"#{(product.price).to_f}" => 1234.5 "#{(product.price).to_s(:delimited)}" => 1,234.5
- 投稿日:2020-03-20T06:24:19+09:00
【Rails】PayjpのRSpecテスト、モックを使用してコントローラテストを実装する
はじめに
payjpなどの外部APIと通信するRailsアプリの場合、
テストする時には実際に通信が走ってしまっては困ります。
今回は、ダミー(テストダブル)を作成して、ダミーのモックを返すようにすることで、コントローラのテストを実装します。実装のサンプルコードだけ知りたい場合は、【サンプルコード】の項目までスクロールしてください。
普通にテストを実装すると
コントローラーのテストでは一般に以下を実装します。
- コントローラーのアクションに対応するビューが正しく表示されるかどうか
- リダイレクトが正しく行われるかどうか
- インスタンス変数が正しく定義されているかどうか
もし、以下のようなコントローラーがあった場合について考えます。
card_controller.rbdef index customer = Payjp::Customer.retrieve("cus_xxxxxxxxxxxxx") end今回はindexのアクションはindexのビューが表示されることをテストします。
コントローラのスペックでは以下のようになります。card_controller_spec.rbit "cardの一覧画面(indexアクション)にアクセスすると、indexのビューが表示される" do get :index expect(response).to render_template("index") endしかしこのままでは実際のコントローラでPayjp::Customer.retrieve()のコードが走り、
実際のpayjpと通信してしまいます。
(テストなのに、payjpの顧客や売り上げを作成してしまうのはまずいですよね!)payjpと通信してしまうのを回避する
通信を回避する方法として、allowというメソッドがあります。
使い方は以下のような感じです。allow(Payjp::Customer).to receive(:create).and_return(true)これはPayjp::Customer.create()という呼び出しがあった際に、trueを返すというものです。
これを使用すると、実際に通信が走ってしまうのを回避できると思います。モックとは
モックとは、本物のように振る舞う偽物、結論以下のようなものです。
payjp_customer = double("Payjp::Customer")先ほど、allowは最終的にtrueを返しました。
Payjp::Customer.create()ではpayjpの顧客を返すのが理想なため、顧客を返すにはどうしたら良いかというのを考えます。
payjpの実際の顧客データは以下の通りです。
顧客データ
{ "cards": { "count": 0, "data": [], "has_more": false, "object": "list", "url": "/v1/customers/cus_121673955bd7aa144de5a8f6c262/cards" }, "created": 1433127983, "default_card": null, "description": "test", "email": null, "id": "cus_121673955bd7aa144de5a8f6c262", "livemode": false, "metadata": null, "object": "customer", "subscriptions": { "count": 0, "data": [], "has_more": false, "object": "list", "url": "/v1/customers/cus_121673955bd7aa144de5a8f6c262/subscriptions" } }上記を丸々変数やメソッドにして返すようにしても良いのですが、rspecには
便利なメソッドが用意されています。
テストダブル - double()
です。
テストダブルはdouble("Payjp::Customer")
のように使用します。こうすると、Payjp::Customerオブジェクトのダミーを用意することができます。(つまりモックを作ることができる)これを使用すると以下のようにできます。
payjp_customer = double("Payjp::Customer") allow(Payjp::Customer).to receive(:create).and_return(payjp_customer)一歩応用すると
別のパターンを考えます。
もしコントローラに以下のようなコードがあったとします。controllercustomer = Payjp::Customer.create(....) customer.cards.retrieve(....)上記コードだとpayjpとの通信は、3回起きます。
- Payjp::Customer.create()
- customer.cards
- cards.retrieve()なので、テストダブル、allow文は以下のようになります。
specpayjp_customer = double("Payjp::Customer") payjp_list = double("Payjp::ListObject") payjp_card = double("Payjp::Card") allow(Payjp::Customer).to receive(:create).and_return(payjp_customer) allow(payjp_customer).to receive(:cards).and_return(payjp_list) allow(payjp_list).to receive(:retrieve).and_return(payjp_card)ここで注目して欲しいのはallow文です。
allow文は通信の回避という側面以外に、メソッドの登録をしているとも取れます。
double("Payjp::Customer")
で作成したオブジェクトはPayjp::Customerオブジェクトですが、実際のオブジェクトとは異なり、メソッドなどが全く定義されていないです。
(double("Payjp::Customer").cardsとしてもcardsというメソッドはありませんとエラーが出るでしょう。)
allow(payjp_customer).to receive(:cards).and_return(payjp_list)
とすることでpayjp_customerというテストダブルに、cardsというメソッドを教えていると捉えてもオッケーです。
【サンプルコード】 モックを使用したテストの実装
コントローラーのサンプル
より実践的な一例です。(*実際はもう少し、createアクションが完結になるよう努力しています。)
サンプルコード
def create Payjp.api_key = get_payjp_key customer = Payjp::Customer.create @credit = create_payjp_card(current_user, customer) if @credit.save redirect_to credits_path else render "new" end end private def create_payjp_card(user, customer) customer.cards.create(card: token_parmas[:token]) credit = Credit.new( user_id: user.id, customer_id: customer.id ) endspecのサンプル
サンプルコード
context "createアクションにアクセスした時" do before do payjp_customer = double("Payjp::Customer") payjp_list = double("Payjp::ListObject") payjp_card = double("Payjp::Card") allow(Payjp::Customer).to receive(:create).and_return(payjp_customer) allow(payjp_customer).to receive(:cards).and_return(payjp_list) allow(payjp_list).to receive(:create).and_return(payjp_card) allow(payjp_customer).to receive(:id).and_return("cus_xxxxxxxxxxxxx") end it "@creditが定義されていること" do post :create, params: {token: "tok_xxxxxxxx"} credit = create(:credit, user_id: user.id, customer_id: "cus_xxxxxxxxxxxxx") expect(assigns(:credit).customer_id).to eq(credit.customer_id) end endまとめ
モックの作成に、supportファイルを作成している方もいますが、私はdoubleを用いたテストの仕方をお勧めします。
ただし、allow文を使いすぎると、モックがきちんと作れているかのテストになってしまう気がします。railsではcontrollerテストがあまり推奨されていないように、他のテストでバリューを出すほうがいい気がします。
supportファイルでの実装
support/payjp_mock.rbmodule PayjpMock def self.payjp_mock_data { "cards": { "count": 0, "data": [], "has_more": false, "object": "list", "url": "/v1/customers/cus_121673955bd7aa144de5a8f6c262/cards" }, "created": 1433127983, "default_card": null, "description": "test", "email": null, "id": "cus_121673955bd7aa144de5a8f6c262", "livemode": false, "metadata": null, "object": "customer", "subscriptions": { "count": 0, "data": [], "has_more": false, "object": "list", "url": "/v1/customers/cus_121673955bd7aa144de5a8f6c262/subscriptions" } } end end参考資料
「【Rails】PAY.JPによる決済機能のモックを活用したコントローラーテスト」
https://qiita.com/tiphp452/items/87042d1800af9a312be9
「Rails で Payjp を使って決済システムを導入する」
https://qiita.com/hirotakasasaki/items/794c920016ac7c33da74
「使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」」
https://qiita.com/jnchito/items/640f17e124ab263a54dd
「決済処理を統合テストしたい」https://muut.com/i/payjp/general:n8jubya6r7gxcp4398dp9d697ed
- 投稿日:2020-03-20T02:33:26+09:00
RailsエンジニアがDDDやクリーンアーキテクチャに触れるためにとりあえずHanamiを始めてみる
Introduction
これまで Rails をそれなりにやってきて、ちょっとしたWebアプリならそれなりサクッと作れるようにはなりましたが、 Fat Controller だの Fat Model と言われるようにどこかで臨界点が来て、従来のMVCアーキテクチャとは違う別の設計を模索してみたい欲が出てきました
そのとっかりとして、巷で話題の クリーンアーキテクチャ に触れるために、 Rails の対抗馬として名乗りを挙げた Ruby 製フレームワーク Hanami を始めてみようかと思いました
Install
まずは Hanami を立ち上げてみます。
$ mkdir hanami-tutorial $ cd hanami-tutorial $ bundle initGemfile を書き換えます。
Gemfile# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "hanami"ライブラリをインストールして Rails と同様にサーバーを立ち上げます。
$ bundle $ bundle exec hanami new . $ bundle exec hanami server導入完了!と思いきや、2020年3月20日現在、エラーになりました
Bundler could not find compatible versions for gem "dry-types": In snapshot (Gemfile.lock): dry-types (= 0.12.3) In Gemfile: hanami (~> 1.3) was resolved to 1.3.3, which depends on hanami-validations (>= 1.3, < 3) was resolved to 1.3.6, which depends on dry-validation (~> 0.11, < 0.12) was resolved to 0.11.2, which depends on dry-types (~> 0.12.0) hanami-model (~> 1.3) was resolved to 1.3.2, which depends on dry-types (~> 0.11.0) Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict.指示に従って、もう一度サーバーを立ち上げます。
$ bundle update $ bundle exec hanami serverhttp://localhost:2300/ を開くと...
Static Page
Rails チュートリアルと同様に、まずは静的なページを作ってみます。
ルーティングは Rails 感があります。
apps/web/config/routes.rbroot to: 'home#index'アクションは Rails と違い独立したクラスを作ります。
apps/web/controllers/home/index.rbmodule Web module Controllers module Home class Index include Web::Action def call(params) end end end end endビューも Rails と違います。 Rails における View は Hanami においては View と Template に分かれていて、 Rails の Helper などの UI に関わるロジックは View で、 HTML テンプレートは Template が担当します。
apps/web/views/home/index.rbmodule Web module Views module Home class Index include Web::View end end end endapps/web/templates/home/index.html.erb<h1>Bookshelf</h1>http://localhost:2300/ を開くとページが差し替わったはずです。
これで自由にページを作れるようになりましたTemplate Engine
Hanami のデフォルトのテンプレートエンジンは Rails と同様に Erb ですが、 Slim や Haml で書きたいですよね。
Slim をインストールして、先ほど作ったテンプレートを書き換えます。Gemfilegem 'slim'$ bundle $ mv apps/web/templates/home/index.html.erb apps/web/templates/home/index.html.slimapps/web/templates/home/index.html.slimh1 Bookshelfこれで Slim は導入できました
Pry
Ruby 2.7 以下だとデバッグのために Pry を導入したいですね。これも簡単。
Gemfilegem 'pry'$ bundle $ bundle exec hanami console[1] pry(main)>Ridgepole
意見は分かれますが、自分はプロトタイピング重視で、 Rails のマイグレーションではなく Ridgepole を使いたいです。
Gemfilegem 'ridgepole'$ bundleRails の
database.yml
に相当するものが無いので、便宜上新たに作ります。config/database.yml# ridgepole を使うために用意 development: adapter: sqlite3 database: db/hanami_tutorial_development.sqliteRidgepole が使う Schemafile を生成します。まだテーブルを定義していないので空ファイルが出力されます。
$ bundle exec ridgepole -c config/database.yml -e > Schemafileテーブルを作ってみます。
Schemafilecreate_table :users, force: :cascade do |t| t.string :email, null: false t.timestamps null: false t.index :email, unique: true end$ bundle exec ridgepole -c config/database.yml -a Apply `Schemafile` -- create_table("users", {}) -> 0.0052s -- add_index("users", ["email"], {:unique=>true}) -> 0.0035sテーブルが作られたのでモデルを作ります。 Rails におけるモデルは Hanami においては Repository と Entity に分かれます。クリーンアーキテクチャが見えてきましたね
lib/hanami_tutorial/repositories/user_repository.rbclass UserRepository < Hanami::Repository endlib/hanami_tutorial/entities/user.rbclass User < Hanami::Entity endコンソールで動作確認してみます。
[1] pry(main)> UserRepository.new.create(email: 'test@example.com') [2] pry(main)> UserRepository.new.find(1) => #<User:0x0000000000000000 ... [3] pry(main)> UserRepository.new.users.where(email: 'test@example.com') => #<ROM::Relation::Composite name=users dataset= ...Hanami は ActiveRecord ではなく Rom という ORM を使っていますが、 Rails エンジニアなら雰囲気で使えそうな気がしますよね?
Job Queue
Rails の ActiveJob に相当する非同期処理モジュールが Hanami にはありませんが、自分の経験上 Job Queue ミドルウェアを移行することはそんなに無いので、ここでは Sidekiq を入れてみましょう。
Gemfilegem 'sidekiq'config/sidekiq.rbSidekiq.configure_server do |config| config.redis = { url: ENV['REDIS_URL'] } end Sidekiq.configure_client do |config| config.redis = { url: ENV['REDIS_URL'] } endワーカーを作ります。引数で与えられた秒数だけ待機して foo! と叫ぶだけのやつです。
lib/hanami_tutorial/workers/sleepy_echo_worker.rbclass SleepyEchoWorker include Sidekiq::Worker def perform(time) sleep time puts 'foo!' end endSidekiq を立ち上げます。
$ REDIS_URL=redis://localhost:6379 bundle exec sidekiq -r ./config/boot.rbコンソールでワーカーを呼び出します。
[1] pry(main)> SleepyEchoWorker.perform_async(3)Sidekiq を立ち上げたターミナルで、3秒間待機した後に foo! とログに出ることが確認できましたね
Conclusion
Rails エンジニアが Hanami でWebアプリを開発するための初期導入についてまとめてみました。今後 Hanami で開発する際にハマったことがあればここに追記していこうかと思います。
ありがとうございました
- 投稿日:2020-03-20T00:34:36+09:00
capistrano でreleasesがいっぱいになるときの対処法
以下の記事を参考にした。
Rails deployment to Amazon EC2 - No space left on device
以下のコードを追記すれば良いみたい。
config/deploy.rbset :keep_releases, 5 + after "deploy:restart", "deploy:cleanup"以上
- 投稿日:2020-03-20T00:31:36+09:00
capistranoで自動デプロイを実行すると、No space left on deviceと言われた
状況
bundle exec cap production deployというコマンドを叩き、自動デプロイを実行しようとすると、No space left on device
というエラーが出て、途中で止まってしまった。対処法
まず、以下の記事を参考に対処法を探った。
No space left on device とエラーが出るときの対処法
Linuxteminal$ df -i ファイルシス Iノード I使用 I残り I使用% マウント位置 devtmpfs 121370 281 121089 1% /dev tmpfs 125870 2 125868 1% /dev/shm tmpfs 125870 363 125507 1% /run tmpfs 125870 16 125854 1% /sys/fs/cgroup /dev/xvda1 424720 424576 144 100% / tmpfs 125870 1 125869 1% /run/user/1001どうやら、/dev/xvda1 ディレクトリナイがいっぱいになっている模様。
このコマンドを叩いて、ファイル数とたくさん消費しているディレクトリを探せば良いそうだ。terminal$ find /var/log/ -type f -name \* -exec cp -f /dev/null {} \;これでログを消すと,
terminal[takuya@ip-10-0-1-173 ~]$ df -i ファイルシス Iノード I使用 I残り I使用% マウント位置 devtmpfs 121370 281 121089 1% /dev tmpfs 125870 2 125868 1% /dev/shm tmpfs 125870 343 125527 1% /run tmpfs 125870 16 125854 1% /sys/fs/cgroup /dev/xvda1 596360 424567 171793 72% / tmpfs 125870 1 125869 1% /run/user/1001容量が空いて、無事解決。