- 投稿日:2019-11-28T23:04:14+09:00
Railsの非同期通信でDELETEを行う
はじめに
自作アプリ「CheckMe!」はユーザビリティ向上を目指してDBとのやりとりに非同期通信を多く使っています。
CRUDを非同期でやっていますが、Deleteについて結構つまづくポイントがあったので備忘録を兼ねてまとめておきます。
この記事が同じことをやりたい人の手助けになれば幸いです。条件
削除対象のidが含まれたリンク(クラスに’delete_link’を持つ)をクリックした時にイベントを中止して
idのアイテムを削除する非同期通信をする。実装
$('.delete_link').on('click', function (e) { // stopPropagationがないとHTMLとJSONの // リクエストが同時に送られてしまう e.preventDefault(); e.stopPropagation(); //リンクに含まれるURLを取得する var url = $(this).attr('href'); $.ajax({ url: url, type: "DELETE", dataType: 'json', // CSRF tokenを発行する beforeSend: function (xhr) { xhr.setRequestHeader("X-CSRF-Token", $('meta[name="csrf-token"]').attr('content')) } }) .done(function (data) { 成功した時 }) .fail(function () { 失敗した時 }) })ポイント
stopPropagationを使うこと
preventDefaultだけだと、なぜかリクエストを送った際にHTMLとJSONのリクエストが
同時に送られてしまう。結果、2回異なるリクエストで非同期通信したことになってしまうので
stopPropagationを使う。CSRF tokenを発行する
CSRF tokenが無いと認証が通らず、そこで処理が止まってしまうのでtokenを渡すよう
にしてあげます。
- 投稿日:2019-11-28T19:45:14+09:00
gmaps4railsのマップタイプを設定する
はじめに
Google Map APIを利用したアプリ開発をしています。
その時に、マップの表示のデフォルトを航空写真にしたかったので実装してみました。
マップの表示の種類のことをマップタイプと呼ぶそうです。コード
このようになりました。
<script> handler = Gmaps.build('Google'); handler.buildMap({ provider: {}, internal: {id: 'map'}}, function(){ markers = handler.addMarkers(<%= raw @hash.to_json %>); handler.bounds.extendWith(markers); handler.fitMapToBounds(); // マップタイプの追記 handler.getMap().setMapTypeId("satellite"); }); </script>参考
参考にしたのはこちら
gmap4railsの設定の変更に役立った
・gmaps4railsの地図の拡大率を設定するmapTypeIdというものがあることを知った
・Google Maps API の使い方・利用方法
- 投稿日:2019-11-28T18:58:43+09:00
【Rails】CSVファイルからデータをインポート
簡単なTODOアプリに、CSVファイルからタスクを追加する機能を実装します。
手順は以下の記事で説明されているものとほとんど同じです。めちゃくちゃ参考になりました。
【Ruby on Rails】CSVインポート実装
タスク追加、編集、削除機能、ログイン機能などを持つTODOアプリにCSVアップロード機能を実装して行きます。
Ruby on Railsで簡単なアプリを作成
【Rails】ログイン機能を実装するrubyの標準ライブラリ
csv
を追加/config/application.rbrequire 'csv'
roo
というgemを追加csvファイルを読み込むためのgem
roo
を追加します。Gemfilegem 'roo'terminal$ bundle installタスク一覧画面にcsvアップロード用のフィールドを追加
/app/views/tasks/_logged_in.html.erb<%= form_tag import_tasks_path, multipart: true do %> <%= file_field_tag :file %> <%= submit_tag "インポート" %> <% end %>コントローラーにアクションを追加
Task.import(params[:file])
で使われているimport
メソッドは後ほど定義します。/app/controllers/tasks.controller.rbdef import Task.import(params[:file]) redirect_to root_url endルーティングを設定
collection {post :import}
と書き込むことで、resources :tasks
で作成されるルーティング以外の、tasksコントローラーのアクションへのルーティングを追加することができます。/config/route.rbresources :tasks do collection {post :import} endterminal$ rails routes . . import_tasks POST /tasks/import(.:format) tasks#import . .
import_tasks
という名前付きルートが追加されました。モデルにCSV読み込み、登録処理を実装
/app/models/task.rb#importメソッド def self.import(file) CSV.foreach(file.path, headers: true) do |row| # IDが見つかれば、レコードを呼び出し、見つかれなければ、新しく作成 task = find_by(id: row["id"]) || new # CSVからデータを取得し、設定する task.attributes = row.to_hash.slice(*updatable_attributes) task.save end end # 更新を許可するカラムを定義 def self.updatable_attributes ["title", "user_id"] end動作確認
以下のようなファイルを用意します。
taskForTodoApp.csvtitle,user_id パンを買う,2 筋トレ,2 メルカリの発送,8 ティッシュを交換する,2ハマったポイント
・CSVファイル内にuser_idを記載しておらず、データベースへの登録時にエラーが発生していたところで少しハマりました。
TODO
・TSVファイルも取り込めるようにする
参考
- 投稿日:2019-11-28T18:56:18+09:00
さくらVPSでCentOS7 10.Ruby On Railsインストール
はじめに
自由にテスト出来るLinuxのサーバーがほしくて、さくらVPSで構築してみました。
順次手順をアップしていく予定です。今回は、Ruby On Railsをインストールします。
どういった構成が正解かは判断がつきかねているのですが、今のところこんな感じです・・(^^;目次
- 申し込み
- CentOS7インストール
- SSH接続
- Apache・PHPインストール
- MariaDBインストール
- FTP接続
- sftp接続
- phpMyAdminインストール
- 環境のバックアップ
- Ruby On Railsインストール
10.Ruby On Railsインストール
必要パッケージインストール
Node.jsインストール
Node.jsのバージョンを管理する「n」パッケージをインストールします。
鶏と卵のような話ですが、「n」パッケージインストールにはnpmが必要で、npmにはNode.jsが必要ということで、Node.jsをインストールする為にまずnpmとNode.jsをインストールします。npmインストール
$ sudo yum install epel-releasenode.jsインストール
$ sudo yum install nodejs npm「n」パッケージインストール
$ sudo npm install -g n「n」パッケージを使ってNode.jsをインストール
最新版は「latest」、安定版は「stable」でインストールできます。
$ sudo n latest $ node -v v11.13.0$ sudo n stable $ node -v v10.15.3「n」パッケージの使い方
コマンド 内容 n インストールしているバージョンの表示やバージョンを切り替える n ls すべてのバージョンを表示する n --latest 最新バージョンを表示する n --lts 安定バージョンを表示する n latest 最新バージョンをインストールする n stable 安定バージョンをインストールする n [version] 指定したバージョンをインストールする n rm [version] 指定したバージョンを削除する n prune すべてのバージョンを削除する その他もろもろインストール
$ sudo yum -y install gcc make openssl openssl-devel gcc-c++ mysql-devel readline-devel libxml2-devel libxslt-devel git bzip2 zlib-devel sqlite-devel$ sudo npm install yarn -gRubyインストール
rbenvインストール
rbenvは、複数のRubyのバージョンを管理し、プロジェクトごとにRubyのバージョンを指定して使うことを可能としてくれるツールです。
まずこれをインストールします。$ cd /usr/local $ sudo git clone https://github.com/rbenv/rbenv.git $ sudo mkdir rbenv/plugins && cd rbenv/plugins $ sudo git clone git://github.com/sstephenson/ruby-build.gitログイン時にrbenvを使えるようにする初期化スクリプトを記述します。
$ sudo vi /etc/profile.d/rbenv.sh以下を追加
export RBENV_ROOT="/usr/local/rbenv" export PATH="${RBENV_ROOT}/bin:${PATH}" eval "$(rbenv init -)"パーミッションの変更
$ sudo chmod -R 777 /usr/local/rbenv設定を反映させるために、ここで一度ログアウトして再ログインします。
再ログインしたら、確認。$ rbenv install -l 2.6.5Rubyインストール
rbenvを使ってRubyをインストールします。
インストール可能なRubyバージョン一覧を表示します。$ rbenv install -lバージョン2.6.5のRubyをインストールします。
$ rbenv install 2.6.5全体で使用するRubyのバージョンを指定
$ rbenv global 2.6.5確認
$ ruby --version ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]Rails環境構築
GemとBundler
GemはRubyGems(Ruby用のパッケージ管理ツール)で管理されるrubyのアプリケーションやライブラリです。
Bundlerは、gem動詞の互換性を保ちながらパッケージの種類やバージョンを管理するツールです。gem
確認
$ which gem /usr/local/rbenv/shims/gem最新版にアップデート
$ gem update --systemバージョン確認
$ gem -v 3.0.6bundlerインストール
システムのgemにはbundlreのみインストールし、bundler以外のものはプロジェクトのvendor/bundleに格納します。
ので、ここではbundlerのみインストール。$ gem install bundlerプロジェクトのフォルダ
以前、4. Apache・PHPインストールでApacheをインストールした時に作成した、/var/www/の中にappというフォルダを作成し、その中にプロジェクトを作っていきます。
フォルダ作成
$ mkdir /var/www/appWebコンテンツに複数のユーザが操作出来るように作成したグループに権限を与えます。
$ sudo chown root:webadmin /var/www/app/ $ sudo chmod 2775 /var/www/app/ -R「bundle exec」の省略設定
railsコマンドをプロジェクト内のvendor/bundleに格納すると、railsコマンド呼出時に、「bundle exec rails server」のように「bundle exec」を付けて呼び出す必要があります。
このbundle execを省略できるように設定します。
この設定は、各ユーザーごとに必要です。スクリプトをダウンロード
$ cd $ curl -L https://github.com/gma/bundler-exec/raw/master/bundler-exec.sh > ~/.bundler-exec.shbundler-exec.shを編集
$ vi ~/.bundler-exec.shリストにrailsを追記(下の方にあります)
・ ・ (省略) ・ ・ unicorn unicorn_rails wagon rails }" define-bundler-aliases unset -f define-bundler-aliases反映させます。
$ source ~/.bashrc利用可能なRailsのバージョンの確認
$ gem query -ra -n "^rails$"現時点で最新は6.0.1でしたが、以下のプロジェクトでは5.2.4を使います。
プロジェクト作成
プロジェクト用フォルダ
bundlerを利用する為、「rails new 」する前に、フォルダを作成します。
$ mkdir /var/www/app/HelloWorld $ cd /var/www/app/HelloWorldローカルのrubyのバージョンを指定。
$ rbenv local 2.6.5確認
$ rbenv version 2.6.5 (set by /var/www/app/HelloWorld/.ruby-version)Gemfile
Gemfileの雛形作成
$ bundle init riting new Gemfile to /var/www/app/HelloWorld/GemfileGemfileの編集
$ vi Gemfilerailsのバージョン5.2.4を使用できるように下記のように修正します。
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # コメントを外し、バージョンを記述 gem "rails", "5.2.4"gemのインストール
$ bundle install --path vendor/bundleインストールのメッセージが流れ、無事に終わるかと思ったのですが、こんなメッセージが・・・
HEADS UP! i18n 1.1 changed fallbacks to exclude default locale. But that may break your application. Please check your Rails app for 'config.i18n.fallbacks = true'. If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be 'config.i18n.fallbacks = [I18n.default_locale]'. If not, fallbacks will be broken in your app by I18n 1.1.x. For more info see: https://github.com/svenfuchs/i18n/releases/tag/v1.1.0「i18nの設定方法が変わったので気を付けなさい」ということみたいです。
だだ、今回はRailsのバージョン5.2.4にしていますので、I18n (>= 1.1.0) and Rails (< 5.2.2)の条件には引っ掛からないので大丈夫だと思います。新規プロジェクト作成
これでやっとrailsコマンドが使えるようになりましたので、プロジェクトを作成します。
フォルダは作成済みですので、プロジェクト名の指定は不要です。$ rails new ..ruby-versionを上書きしますか?と聞いてきますのでYを入力。
Overwrite /var/www/app/HelloWorld/.ruby-version? (enter "h" for help) [Ynaqdhm]YGemfileを上書しますか?と聞いてきますのでYを入力。
Overwrite /var/www/app/HelloWorld/Gemfile? (enter "h" for help) [Ynaqdhm]Y順調に行くと思いきや、以下のメッセージが出ました。
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies... Bundler could not find compatible versions for gem "sprockets": In snapshot (Gemfile.lock): sprockets (= 4.0.0) In Gemfile: sass-rails (~> 5.0) was resolved to 5.1.0, which depends on sprockets (>= 2.8, < 4.0) rails (~> 5.2.4) was resolved to 5.2.4, which depends on sprockets-rails (>= 2.0.0) was resolved to 3.2.1, which depends on sprockets (>= 3.0.0) Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict. run bundle exec spring binstub --all bundler: command not found: spring Install missing gem executables with `bundle install`依存関係に問題があるから、「bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java」を実行しろということのようです。
あと、「bundle update」を実行すると、スナップショットを最初から再構築します、と。
なので、やってみます。$ bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies...... Bundler could not find compatible versions for gem "sprockets": In snapshot (Gemfile.lock): sprockets (= 4.0.0) In Gemfile: sass-rails (~> 5.0) was resolved to 5.1.0, which depends on sprockets (>= 2.8, < 4.0) rails (~> 5.2.4) was resolved to 5.2.4, which depends on sprockets-rails (>= 2.0.0) was resolved to 3.2.1, which depends on sprockets (>= 3.0.0) Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict.$ bundle update The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. ・ ・ (以下略) ・ ・と、同じようなメッセージが・・・
とりあえず、再度「bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java」して、「bundle update」すると、「Bundle updated!」と出たので、とりあえずOKかなと。サーバー起動
3000のポートを開ける
ファイアウォールを使っている場合
ポートを開ける
$ sudo firewall-cmd --permanent --add-port=3000/tcp $ sudo firewall-cmd --reload![2019-11-28.png](https://qiita-image-store.s3.ap-northeast-さくらVPSのパケットフィルタを使っている場合
- さくらVPSコントロールパネルにログインし、サーバーを選択
- [パケットフィルタ]-[パケットフィルタ設定へ>]を選択
- [+任意の解放ポート設定を追加する]ボタンをクリック
- [TCP][3000]を設定
- [設定]ボタンをクリック
サーバー起動
$ rails server -b 0.0.0.0 => Booting Puma => Rails 5.2.4 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.6.5-p114), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stop無事起動しました。
ブラウザで確認
次回
次回は、Pythonのインストールの予定です。
- 投稿日:2019-11-28T18:32:34+09:00
RailsのDM一覧に最後のメッセージを表示させる方法
はじめに
記事①と記事②を参考にDM機能を作らせていただきました。投稿してくださった方々に深く感謝いたします。
以下は記事①の通りにDM機能を作成した前提で進めます。やりたいこと
TwitterやLINEのようにメッセージの一覧に最後のメッセージを表記させる。
実装
記事①通りに進めるとメッセージ一覧はrooms#indexに表示させることになると思うので、以下のようにすればユーザ間の最後のメッセージを引っ張ってくることができます。
まず、記事②を参考にroomsコントローラ側に
@anotherEntries
を定義します。roomsコントローラーdef index @currentEntries = current_user.entries myRoomIds = [] @currentEntries.each do | entry | myRoomIds << entry.room.id end @anotherEntries = Entry.where(room_id: myRoomIds).where('user_id != ?', @user.id) endそして、
@anotherEntries
にはuser_idが自分のidではない相手の情報が配列として入っているので、以下のようにすればやりとりした最後のメッセージを引っ張ってこられます。rooms#index<% @anotherEntries.each do |e| %> <%= Message.find_by(id: e.room.message_ids.last).content %> <% end %>上はただ表記させただけなので、あとは自分好みにリンクにしたり字数に制限をかけたりしてみてください。
また、ここでは最後のメッセージを表示させましたが、上記のcontent
の部分をuser.name
にすれば最後にメッセージを送ったユーザ(自分か相手)の名前を引っ張ってくることもできます。
- 投稿日:2019-11-28T18:30:19+09:00
has_many_attachedなActiveStorageに属性を付与する
とあるModelに複数のファイルをAttachさせるときに、
ActiveStorage
のhas_many_attached
を使うことはよくあると思いますが、
このとき、
- ファイルに属性を付与したい
- 同じ属性のファイルが既にあるなら削除したい
的な感じの気持ちが溢れ出ると思います。
ので、やります。
class Foo < ApplicationRecord has_many_attached :bars endこのクラスで何かをattachします
class Foo < ApplicationRecord has_many_attached :bars def attach_bars bar = 'something' #何らか適当なファイル的なやつ bars.attach( io: StringIO.new(bar), filename: 'なんらかてきとうなふぁいるめい.txt', content_type: 'text/plain', metadata: { bar_type: 'something' } ) end end
metadata: { bar_type: 'something' }
の部分が何かいい感じのやつで、これでactive_storage_blobs.metadata
に"bar_type": "something"
な感じでデータが保持されるようになります。やったね。これで属性付与できたので、
bars
の中でbar_type == 'something'
なattachmentを削除したい場合は、foo = Foo.find(xxx) foo.bars.select {|bar| bar.metadata[:bar_type] == 'something' } .each {|bar| bar.purge }みたいな感じでpurgeすることはできるんですが、仮に
bars
が100件とか200件とかなってきたらウザウザなので、事前にクエリで絞り込む的なことがしたくなりました。というわけで、しました。
foo.bars.joins(:blob).where( "`active_storage_blobs`.`metadata`->>'$.bar_type' = ?", 'something' ).each { |bar| bar.purge }やったね!
- 投稿日:2019-11-28T18:16:43+09:00
コメントの削除も非同期で対応する
コメント投稿の非同期は成功しました。
そこで、コメント削除も非同期にて対応しようと思い。
クロームの検証画面でエレメントコピーして修正して下記コードを追加しました。
削除コメントして即削除でActiveRecord::RecordNotFound in TweetsController#destroyが出ます。
一度リロードをすれば問題なく削除できているので
非同期部分で引っかかっているのは確認済みです。どうすればいいのでしょうか?
コメント投稿と削除で別々の
コードがいるのでしょうか?
記載をするべきでしょうか?
何かファイルを増やしてそこに記載がいるのでしょうか?$(function(){ function buildHTML(comment){ var html = `<p> <strong> <a href=/users/${comment.user_id}>${comment.user_name}</a> : </strong> ${comment.text} <a class="comment-delete" rel="nofollow" data-method="delete" href="/tweets/${comment.tweet_id}">削除</a> </p>` return html; } $('#new_comment').on('submit',function(e){ e.preventDefault(); console.log(this) var formData = new FormData(this); var url = $(this).attr('action') $.ajax({ url: url, type: "POST", data: formData, dataType: "json", processData: false, contentType: false }) .done(function(data){ var html = buildHTML(data); $('.comments').append(html); $('.form_message').val(''); $('.form__submit').prop('disabled', false); }) .fail(function(){ alert('error'); }) }) })
- 投稿日:2019-11-28T17:28:28+09:00
新卒が研修を修了したので、Rails本番コーディングする前にインプットしたこと3つ ~ 性能・セキュリティ編 ~
この記事はモチベーションクラウドアドベントカレンダー5日目の記事です。
こんにちは。リンクアンドモチベーション、モチベーションクラウド開発チームの江上です。つい先日、約3ヶ月のエンジニア研修を卒業した19年新卒のメンバーを開発チームに迎え入れました。弊社では全社研修後、数ヶ月のエンジニア研修の後エンジニアやプロダクトオーナー、カスタマーサポートなどのプロダクトチームに配属されるフローになっていますので、例年この時期に配属になります。
ありがたいことに、現在モチベーションクラウドは大手のお客様にも使っていただけるプロダクトに成長しました。それに伴い、性能やセキュリティにおいて求められる基準も高くなってきています。そこで、既存メンバーの復習も兼ねて、新人であったとしても最低限押さえておくべきことを、具体例を交えて開発開始前にインプットすることにしました。
同じく本番コードを初めて書く新人を迎え入れる方、今から本番コードを書き始めようという方、クリスマス前に初心にかえって、清らかな心を取り戻したい方の参考になれば幸いです。
0. 前提条件
弊社では、以下の事項を習得していることを研修卒業の条件とするプログラムを実施しています。
- SQLの基本習得
- DockerかVMをつかった開発環境の構築習得
- Rails Tutorial最低1周
- rspecの書き方習得
- Gitの使い方習得
- エディタの使い方習得1. 事前知識
1-1. 読本
あくまで本番コードを書く上で、以下の資料を事前に読むことを決めています。
読むだけではなく、自分なりの感想文(まとめ)を提出してもらっています。
たまに、先輩からのいくつかクイズを出されます。
- リーダブルコード
- 安全なwebアプリケーションの作り方
- モチベーションクラウドのマニュアル1-2. 開発中の注意事項
1-2-1. 開発に必要な画面を常に開いておくこと
なにか問題があっても、logを見ていなかったり、consoleで試していないと勘違いで進んでしまう可能性があります。
- ググるためのchrome
- 試すためのrails console
- 確認するためのlog
は常に用意しておきましょう。1-2-2. 作業ブランチは最新にすること
朝来たら、
git pull
やgit merge 親ブランチ
をしましょう。
特に、pushするときになってmergeすると、コンフリクト地獄で死にたくなります。2. パフォーマンスについて
全てを網羅できているわけではないですが、抑えておいてほしいところを列挙しました。
2-1. N+1を起こさない
N+1問題とは
ループ処理の中で都度SQLを発行してしまい、大量のSQLが発行されてパフォーマンスが低下してしまう問題のことです。
例えば、1回1000件取るのに300msで終わるのに、1000回1件ずつ取ると、30ms * 1000 = 30000msかかってしまったりします。前提
class ModelA < ActiveRecord::Base has_many model_bs end class ModelB < ActiveRecord::Base belongs_to model_a end解消方法
2-1-1. includesする(実行計画によっては、n+1になる場合もあるので注意。references使ってる場合はjoinされる)
before_fixModelA.all.each do |m| m.model_bs # ここで毎回クエリが発行される endafter_fixModelA.includes(:model_bs).all.each do |a| a.model_bs end2-1-2. ループする必要がないならループしない
※ update_allはコールバックが実行されない点は注意
before_fixmodel_as = ModelA.where(id: [1,2,3,4,5]) model_as.each do |a| a.model_bs.update(some_flg: true) endafter_fixmodel_as = ModelA.where(id: [1,2,3,4,5]) model_bs = ModelB.where(id: model_as.map(&:model_b_id)) model_bs.update_all(some_flg: true)2-1-3. joinしたかったら、先に1000件とか一括で持ってきて、Hashにしとく
before_fixmodel_as = ModelA.where(id: [1,2,3,4,5]) model_as.each do |a| b = a.model_bs endafter_fixmodel_as = ModelA.where(id: (1...1000).to_a) model_bs = ModelB.where(id: model_as.map(&:model_b_id)) model_bs_hash = model_bs.inject({}) do |hash, b| hash[b.id] = b hash end model_as.each do |a| b = model_bs_hash[a.model_b_id] end2-2. レコード数が多いテーブルはジョインしない
1:nの関係にあったとき、1レコード平均10件の関連レコードがあると、100万レコードのテーブルと関連レコードをjoinしたときに生成される一時テーブルは1000万レコードになります。
1:1の関連の場合など、問題がないケースもありますが、レコード数が多いテーブルを認識し注意を払うことは大切です。解決方法
だいたいN+1と同じ方法で解決できます
2-3. json, text型を含むテーブルはselect句で必要なカラムに絞って取得する
レコード数が少なくとも、json、text型を含むレコードは1レコードのデータ量が大きくなりがちです。特に1000文字以上の文字列が入ってる場合は注意が必要です。
データ量が多いと、APサーバーとDB間のネットワークやメモリを余分に消費してしまい遅延やエラーの原因になります。解決方法
2-3-1. select句で取得カラムを絞る
before_fixModelA.allafter_fixModelA.all.select(:column_1, :column_2)2-3-2. includesしない
N+1と同様です
2-4 逐次で計算させない
毎回DBのmax関数などで計算させながら表示してるなどしてると、毎回負荷がかかる上に、その計算値を使ったsortをしたい場合は毎回全件取得する必要が出てきます。
2-4-1. どこかのカラムやキャッシュに計算結果を保持する
before_fixModelA.select("max(model_bs.id) as max_b_id"). joins(:model_bs). sort {|a| a.max_b_id}. first(10)after_fixclass ModelA < ActiveRecord::Base has_one model_a_stat end class ModelAStat < ActiveRecord::Base belongs_to model_a end ModelA.select("max_b_id"). joins(:model_a_stat). order(max_b_id: :asc). limit(10)3. セキュリティについて
セキュリティも奥が深いので、全てを網羅できているわけでは全然ないですが、コードを書く上で最低限抑えておいてほしいところを列挙しました。
3-1. paramsの取り扱い
paramsはブラウザをちょっといじったり、curlなどを使えば簡単に操作できてしまうので、自分が期待してる値でないものがくる可能性を常に考えておく必要があります。
3-1-1 params[:user_id]は使わない
current_userなどのsessionから引っ張ってきたインスタンスがapplication_controllerのbefore_filterあたりにだいたいあるので、理由がない限りそちらを使いましょう
before_fixModelA.where(user_id: params[:user_id])after_fixdef current_user User.where(id: session[:id]).first end ModelA.where(user_id: current_user.id)3-1-2 sessionから引っ張ってこれない、params[:xxx_id]
current_userやcurrent_companyなどのsessionから引っ張ってきたインスタンスでの検証も行いましょう
before_fixModelA.where(xxx_id: params[:xxx_id])after_fixModelA.where(xxx_id: params[:xxx_id], user_id: current_user.id)3-1-3 プレースホルダーを使う
where句などにparamsを入れる場合はプレースホルダーを使いましょう。
paramsじゃなくても、常にプレースホルダー使えば考えることは少なくなります。before_fixModelA.where("xxx_id = #{params[:xxx_id]}")after_fixModelA.where("xxx_id = ?", params[:xxx_id])3-1-4 xxx_typeなどのenumてきなパラメータや、orderのasc, descなどの定型文字が入ることを期待するパラメータ
case文などを使って、規定文字以外の場合は、何かに倒すか、エラーにしましょう。
before_fixModelA.order(xxx_id: params[:direction])after_fixdirection = (params[:direction] == "desc") ? "desc" : "asc" ModelA.order(xxx_id: direction)3-2. DB以外の共有リソースの取り扱い
DB以外の共有リソース
- ファイル
- キャッシュ
- S3
- ディレクトリ
- グローバル変数
- クラス変数
- インスタンス変数
問題
DBと違って、transactionなど、エラー時に巻き戻してくれる仕組みなどは基本的にないです。
よって
- 同時アクセスで書き換えられる
- エラーが起きると残ってしまって、次来た処理で不具合が起きる
などが起きえます。解決方法
3-2-1. 一時的にしか使わないならTmpfile使う
before_fixfile_path = "some_file_name" File.open(file_path, "w")after_fixTmpfile.open3-2-2. ensureでdeleteやcloseする(Tmpfile使えばcloseで削除されるので不要)
before_fixfile_path = "some_file_name" File.open(file_path, "w") do |f| f.puts "xxx" endafter_fixfile_path = "some_file_name" begin file = File.open(file_path, "w") do |f| f.puts "xxx" end ensure File.delete file_path end3-2-3. ファイルは追記モード(a)では使わない
before_fixfile_path = "some_file_name" File.open(file_path, "a") do |f| f.puts "xxx" endbefore_fixfile_path = "some_file_name" File.open(file_path, "w") do |f| f.puts "xxx" end最後に
改めてこの資料をつくったことで、性能やパフォーマンスの意識が少し上がった気がしています。
今後も常に心がけないとすぐに劣化していってしまうため、定期的に啓蒙活動を続けていく所存です。
利用しているお客様のためにも、抑えるべき部分はしっかり抑えてよりよいプロダクト開発を行っていきます。また、今後こちらをベースにオンボーディング用資料として充実させていこうと思います。
「もっとこうしたほうがいい」「うちではこうしてる」などあればぜひコメントいただければ幸いです。
- 投稿日:2019-11-28T17:17:40+09:00
Railsでeach�(ループ)の中にモーダルを入れても変数が適応されない問題
問題
variables.each do |variable|
という繰り返しのブロックの中で
それぞれの異なるvariableをmodalに渡す場合、
下記コードだと最初のvariableの値しか表示されない。<% books.each |book| do %> <a href="#book_modal">クリックするとモーダルを表示するリンク</a> <div class="modalList" id="book_modal"> # ここはモーダル </div> <% end %>例えば、
variables = [var1, var2, var3, var4]
とした場合、本来期待する動きは
1つめのモーダルには、var1
2つめのモーダルには、var2
3つめのモーダルには、var3
4つめのモーダルには、var4
だが、上記だと4つのモーダルすべてに
variable = var1
が反映され、var2, var3, var4
が反映されない。解決策
モーダルのidが一意となるような実装にしてやればよい。
<% books.each |book| do %> <a href="#book_modal-<%= book.id %>">クリックするとモーダルを表示するリンク</a> <div class="modalList" id="book_modal-<%= book.id %>"> # ここはモーダル </div> <% end %>最初にあげたコードだと、4つのモーダルのidがすべて
book_modal
のため、
最初のvariableしか反映されなかった。なので、Bookオブジェクトに一意に定められている
id
を使用して、
id = "book_modal-<%= book.id %>"
とした。すると、
1つめのモーダルのid: book_modal-var1
2つめのモーダルのid: book_modal-var2
3つめのモーダルのid: book_modal-var3
4つめのモーダルのid: book_modal-var4
のように各モーダルのidが変わってくれるので、
eachで回している変数のそれぞれの値を反映させることができた。
- 投稿日:2019-11-28T16:43:13+09:00
RailsとLaravelの比較
はじめに
この記事はRailsとLaravelを比較分析してみた記事です.
Railsは以前から勉強していて,新たにLaravelを使ってみました.
Railsの勉強にはRuby on Rails 5 超入門と改訂4版 基礎 Ruby on Rails (IMPRESS KISO SERIES)を使いました.
Ruby on Rails 5 超入門についてはチュートリアル形式で一見わかりやすいのですが,誤植などが多いのとレベルが優しすぎるのであまりオススメしません.Laravelの勉強はPHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応を使いました.買う前に中身を確認しなかった僕が悪いのですが,本の厚さの割に内容が薄いです.また辞書的に使う分には良いのですが,チュートリアル形式で作っていく感じではないので,フレームワークを使ったことのない人などにはオススメ出来ません.
RailsとLaravelの基本情報
*以下,2019/11/28現在の情報
Rails Laravel 言語 Ruby PHP 初リリース 2004年 2011年 github https://github.com/rails/rails https://github.com/laravel/framework github start数 44.6K 19.5K githubのstart数ではrailsの方がLarvelの倍くらいの数になっています.Railsはいま流行りと個人的に思っていたりするのですが,リリースからはもう10年以上経過していました.
Googleトレンドで調べてみた
Googleトレンドでは検索のトレンドを調べて,検索キーワードで比較したりできるサービスです.Googleは面白いサービスを作っていますね.
これを使ってRailsとLaravelについて調べてみました.すべての国
https://trends.google.co.jp/trends/explore?date=all&q=Rails,Laravel
日本
https://trends.google.co.jp/trends/explore?date=all&geo=JP&q=Rails,Laravel
Laravelはリリースが2011年なので,2012年頃まではほぼ0です.
全ての国
の場合ではLaravelがRailsをやや追い越したくらいになっています.日本
の場合ではまだRailsを追いかけていますが,このままの状態が続くと追い越しそうです.個人的分析
ここからはRailsとLaravelを使ってみた感想になります.
本,参考資料などの多さは? 勝者:Rails
Railsの方がネットでググったときなどの参考になる記事などが多いです.それはRailsで使われている言語のRubyの開発者が日本人(まつもとゆきひろ氏)なのでRubyの日本語資料がそもそも多いという点と,Laravelより7年早くリリースしているからかなと思っています.
*IT分野の中で日本発祥の技術はRubyと深層学習のChainerくらいしか知らない(他にあったらコメントください)のでRubyは本当すごいです.
勉強のしやすさは? 勝者:Rails
難易度的にもRailsとLaravelは遜色ないですが,やはり本や参考資料などが多い方が勉強しやすいです.以前Go言語のフレームワークのGinを使う機会があったのですが,公式以外のドキュメントが少なくて開発しにくかったことがあります.
使いやすさは? 同じ
- railsには
scaffold
という色々必要なものを一発で作ってくれるコマンドがあるのですが,Laravelは標準でサポートしていません(あるけど,githubの更新も止まっているのでバージョンに寄っては今後使えなくなるかも).- Laravelは比較的命名規則などが緩いので,ネットで調べても色々な書き方をしているのでわかりにくいです.
- 逆にRailsは規則が厳しいのですが,厳しいが故に,変数名などを修正するのが億劫になります.
- Laravelはデフォルトでログイン認証などが入っているので,インストールなどの手間が省けます.
RailsとLaravelのどっちがいいの? 若干Laravelかな
ここまで読んだ人にはRailsの方が良い感じに見えますが,他のエンジニアの人の意見を聞くと
「Railsは比較的開発が速くできるからスタートアップに多いが,保守・運用や難しい機能にはRailsは向いておらず,JavaやPHPのフレームワークの方が開発が容易」
という意見が多かったです(社会人になって1ヶ月くらいなのでこの辺は自分の中で不確定要素).確かに求人とか見てるとPHPとかJavaの募集の方が多いなという印象を持ったのでLaravelとしました.
まとめ
Railsが最強(僕の大学の教授)という人もいれば,Railsはオワコンと言っている人もいますのでどうなんですかね...
僕の周囲ではRailsの方がよく耳にします.
Rails,LarvelはRubyとPHPという言語の違いは多少ありますが,考え方や使い方はほとんど同じです.個人的には勉強のしやすいRailsを最初にやってMVCの基礎を勉強して,Laravelとかに移ればいいかなと思います.
- 投稿日:2019-11-28T15:58:39+09:00
【Rails】もっと早く知りたかったデバッグ用gem 'better_errors','binding_of_caller'
はじめに
先日伊藤さんのこちらの動画(↓)を見ました。
プログラミング初心者歓迎!「エラーが出ました。どうすればいいですか?」から卒業するための基本と極意 - YouTube「いや、なんでもっと早く見なかった!」と思うくらい具体的なデバッグ手法が諸々解説されています。
そこで出てきたgem
better_errors
&binding_of_caller
の導入方法と見方について簡単にまとめます。今まで
puts デバッグ
binding.pry
rails serverのログ
を使ってエラーと闘っていましたが、この2つのgemはもっと早く知りたかったです
この記事が役に立つ方
- エラーに苦しんでいるRails初心者
この記事のメリット
- デバッグの効率が上がる
環境
- macOS Catalina 10.15.1
- zsh: 5.7.1
- Ruby: 2.6.5
- Rails: 5.2.3
- Docker: 19.03.5
better_errors
とは?デフォルトのエラー画面をわかりやすく整形してくれるgem。
binding_of_caller
とは?上記
better_errors
と一緒に使うことで、ブラウザ上でirb
を使えるようになるgem。
※本記事では使用方法について触れていません。導入方法
Gemfilegroup :development do gem 'better_errors' gem 'binding_of_caller' endGemfileに上記追記し、
bundle install
で完了。簡単!
※Dockerを使用している場合はもうひと手間必要
app/config/environments/development.rbBetterErrors::Middleware.allow_ip! "0.0.0.0/0"仮想環境を使っている方は、うまく動作しないようです。
私はDockerを使っていますが、上記コードを追記してサーバー再起動で動作しました。
Gem 『Better errors』が動かないとき | HippoBlog使用例
ArgumentErrorが出ていた場合
form_with
を使おうとしたらエラーが出た場合を例にします。エラーメッセージが最上部にあるのは変わりませんが、
その下に2つタブがあります。これが超便利。
- Application Frames
- All Frames
1. Application Frames
最初はApplication Framesが表示されています。
ここでは、自分の書いたコードを対象にエラーに関係する箇所を明示してくれています。
2. All Frames
次に、All Framesをクリックすると、自分が書いた箇所以外(gemやActiveSupportなど)まで踏み込んでエラーに関係する箇所が表示されます。
それぞれクリックしていくとコードが表示されます。
例えば今回は、
form_with
のソースコードまで表示してくれます。
↓
form_with
の最初の1行を抜き出すとこんな感じです。def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)これで指定すべき引数がわかり、今回は
「いけね!そういえば
model:
って書き忘れた!」と気づくことが出来ます。便利!
その他、
NoMethodError
で~ for nil:NilClass
とか出ていたら、ブラウザでそのまま変数の中身を確認したり出来て非常に便利です。
※画像最下部がコンソールになっています。おわりに
最後まで読んで頂きありがとうございました
デバッグ手法は学び始めの段階でなるべく多くリストアップしておいたほうが効率がいいと思いますが、
better_errors
はもっと早く導入していたかったです伊藤さんのYoutube、非常に勉強になるのでまた他の動画も見させて頂きます。
参考にさせて頂いたサイト(いつもありがとうございます)
プログラミング初心者歓迎!「エラーが出ました。どうすればいいですか?」から卒業するための基本と極意 - YouTube
プログラミング初心者歓迎!「エラーが出ました。どうすればいいですか?」から卒業するための基本と極意(解説動画付き) - Qiita
Gem 『Better errors』が動かないとき | HippoBlog
【Rails】better_errorsとbinding_of_callerで自分でエラーを解決できるようになろう【初心者向け】 - Qiita
- 投稿日:2019-11-28T15:02:25+09:00
openssl1.0系がbrewから消えたことでrailsでエラーが発生する時の対処法
openssl1.0系がbrewから消えたことでrailsでエラーが発生する時の対処法
次のようなエラーが発生する場合の対処法です。
Library not loaded: /opt/local/lib/libssl.1.0.0.dylib (LoadError)
openssl1.1系にアップデートされた状態で、postgresqlのバージョンが古いとエラーが発生します。
対処法としては、
brew upgrade postgresql
でpostgresqlをアップデートしたら直りました。参考までに、openssl1.0系がbrewから消えた原因のHomebrewのコミットはこれです。
https://github.com/Homebrew/homebrew-core/commit/0349a7ca76f483483c6d5c1d4cfe6b458dee2665
- 投稿日:2019-11-28T14:10:22+09:00
Ruby on Rails のselectメソッドで生成されるoptionタグにdisabled属性を追加する
- 投稿日:2019-11-28T13:29:43+09:00
cloud9って何なの?
cloud9の使い方
1.environmentはパソコンのローカル環境のこと
以下の画像のPythonとかpracticeがenvironmentと呼ばれていて、environmentの一つ一つが仮想的な環境になっている。パソコン一台を仮想空間に持っているみたいなイメージ。environmentを4つ以上作ると有料なので注意してください!
environmentを起動(OPEN IDEを押す)する度にEC2と呼ばれているAWSのサーバーのインスタンス(仮想サーバーの一部)が生成されて仮想サーバーを使うことが出来ます。逆にログアウトするとサーバーは返却される。environmentを使うときだけサーバーを借りて、使わないときは返却するというイメージ。大規模開発とかでは使う分だけお金を払うのでお得にサーバを利用できます。
environmentは個別の環境なので、言語のバージョンの設定やMysqlの準備などはenvironmentごとに行う必要があります。アプリごとに環境を設定するのはめんどくさいと思うのでenvironmentごとの容量(1GB)超えないまでは複数のアプリ開発も同じenvironmentでやっちゃった方がいいと思います。
2.ターミナルを使おう!
緑のプラスボタンを押して"New Terminal"を押すとターミナルを起動でき、仮想的なパソコン(environment)に命令を出すことが出来ます。コマンドはLinuxコマンドになるので、Windowsユーザの方は一通りLinuxコマンド勉強してから使った方がいいかもしれないです。
3.作成したアプリケーションをプラウザで確認しよう
2番で起動したターミナルで、作成したアプリケーションをAWSのプラウザで確認できます。以下の画像の左画面で、表示したいアプリケーションのディレクトリに移動してサーバを起動します(railsの場合は"rails s")。その後、上の一覧のPreviewを押して、”Preview running application”を押すと作成したアプリケーションを以下の画像の右側の表示が出て来ます。
その右上の矢印ボタンを押すとcloud9のプラウザで以下のようにアプリケーションの確認が出来ます。
※注意
cloud9のプラウザはたまにCSSの適用がされなかったり、明らかに通っているパスが通らなかったりという不具合があるので、そういう時はサーバの再起動をすると大抵直ります。
- 投稿日:2019-11-28T11:34:01+09:00
Railsでデータ抽出時、シンボルとクエリのSQL発行文の違い
環境
- Rails: 6.0.0
- Mysql: 5.7
- Ruby: 2.6.3
やりたいこと
where句を使ってのデータ抽出
シンボル
hogehoge_controller.rbids = [1, 3] message = Message.where(id: ids) #=> SELECT `messages`.* FROM `messages` WHERE `messages`.`id` IN (1, 3) ORDER BY `messages`.`created_at` DESCSQL文ベタ書き
hogehoge_controller.rbids = [1, 3] message = Message.where(id: ids) #=> SELECT `messages`.* FROM `messages` WHERE (id = 1,3) ORDER BY `messages`.`created_at` DESC # "Mysql2::Error: Operand should contain 1 column(s)"というエラー発生結論
シンボルだとIN句を使ってクエリを発行してくれる。
- 投稿日:2019-11-28T08:49:03+09:00
Railsで開発環境のURLをlocalhost:3000ではなくa.example.comとかにする方法
/etc/hosts
ファイルをsudoで編集します。Linuxでも同様に動きます。127.0.1.1 a.example.com 127.0.1.1 b.example.comこれで、アプリを起動させて、ブラウザーをそれらのいずれかに向けると、動作するはずです。次に、Railsアプリのサーバーを起動します。
rails s rails s -p 3001最後に、ブラウザで次を指定します。
foo.example.com:3000 bar.example.com:30013000番とかを消したい場合
80番ポートで起動させてください
sudo rails server --port=80参考
- 投稿日:2019-11-28T08:40:00+09:00
�deviseとvalidationのエラーメッセージを日本語化
【その1】
config/locales下に2つのファイルを新規作成。
ja.yml
devise.ja.yml
【その2】
ja.yml
に以下を貼り付けja.yml--- ja: activerecord: errors: messages: record_invalid: 'バリデーションに失敗しました: %{errors}' restrict_dependent_destroy: has_one: "%{record}が存在しているので削除できません" has_many: "%{record}が存在しているので削除できません" date: abbr_day_names: - 日 - 月 - 火 - 水 - 木 - 金 - 土 abbr_month_names: - - 1月 - 2月 - 3月 - 4月 - 5月 - 6月 - 7月 - 8月 - 9月 - 10月 - 11月 - 12月 day_names: - 日曜日 - 月曜日 - 火曜日 - 水曜日 - 木曜日 - 金曜日 - 土曜日 formats: default: "%Y/%m/%d" long: "%Y年%m月%d日(%a)" short: "%m/%d" month_names: - - 1月 - 2月 - 3月 - 4月 - 5月 - 6月 - 7月 - 8月 - 9月 - 10月 - 11月 - 12月 order: - :year - :month - :day datetime: distance_in_words: about_x_hours: one: 約1時間 other: 約%{count}時間 about_x_months: one: 約1ヶ月 other: 約%{count}ヶ月 about_x_years: one: 約1年 other: 約%{count}年 almost_x_years: one: 1年弱 other: "%{count}年弱" half_a_minute: 30秒前後 less_than_x_seconds: one: 1秒以内 other: "%{count}秒未満" less_than_x_minutes: one: 1分以内 other: "%{count}分未満" over_x_years: one: 1年以上 other: "%{count}年以上" x_seconds: one: 1秒 other: "%{count}秒" x_minutes: one: 1分 other: "%{count}分" x_days: one: 1日 other: "%{count}日" x_months: one: 1ヶ月 other: "%{count}ヶ月" x_years: one: 1年 other: "%{count}年" prompts: second: 秒 minute: 分 hour: 時 day: 日 month: 月 year: 年 errors: format: "%{attribute}%{message}" messages: accepted: を受諾してください blank: を入力してください confirmation: と%{attribute}の入力が一致しません empty: を入力してください equal_to: は%{count}にしてください even: は偶数にしてください exclusion: は予約されています greater_than: は%{count}より大きい値にしてください greater_than_or_equal_to: は%{count}以上の値にしてください inclusion: は一覧にありません invalid: は不正な値です less_than: は%{count}より小さい値にしてください less_than_or_equal_to: は%{count}以下の値にしてください model_invalid: 'バリデーションに失敗しました: %{errors}' not_a_number: は数値で入力してください not_an_integer: は整数で入力してください odd: は奇数にしてください other_than: は%{count}以外の値にしてください present: は入力しないでください required: を入力してください taken: はすでに存在します too_long: は%{count}文字以内で入力してください too_short: は%{count}文字以上で入力してください wrong_length: は%{count}文字で入力してください template: body: 次の項目を確認してください header: one: "%{model}にエラーが発生しました" other: "%{model}に%{count}個のエラーが発生しました" helpers: select: prompt: 選択してください submit: create: 登録する submit: 保存する update: 更新する number: currency: format: delimiter: "," format: "%n%u" precision: 0 separator: "." significant: false strip_insignificant_zeros: false unit: 円 format: delimiter: "," precision: 3 separator: "." significant: false strip_insignificant_zeros: false human: decimal_units: format: "%n %u" units: billion: 十億 million: 百万 quadrillion: 千兆 thousand: 千 trillion: 兆 unit: '' format: delimiter: '' precision: 3 significant: true strip_insignificant_zeros: true storage_units: format: "%n%u" units: byte: バイト eb: EB gb: GB kb: KB mb: MB pb: PB tb: TB percentage: format: delimiter: '' format: "%n%" precision: format: delimiter: '' support: array: last_word_connector: "、" two_words_connector: "、" words_connector: "、" time: am: 午前 formats: default: "%Y年%m月%d日(%a) %H時%M分%S秒 %z" long: "%Y/%m/%d %H:%M" short: "%m/%d %H:%M" pm: 午後https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml
【その3】
devise.ja.yml
に以下を貼り付けdevise.ja.ymlja: activerecord: attributes: user: confirmation_sent_at: パスワード確認送信時刻 confirmation_token: パスワード確認用トークン confirmed_at: パスワード確認時刻 created_at: 作成日 current_password: 現在のパスワード current_sign_in_at: 現在のログイン時刻 current_sign_in_ip: 現在のログインIPアドレス email: Eメール encrypted_password: 暗号化パスワード failed_attempts: 失敗したログイン試行回数 last_sign_in_at: 最終ログイン時刻 last_sign_in_ip: 最終ログインIPアドレス locked_at: ロック時刻 password: パスワード password_confirmation: パスワード(確認用) remember_created_at: ログイン記憶時刻 remember_me: ログインを記憶する reset_password_sent_at: パスワードリセット送信時刻 reset_password_token: パスワードリセット用トークン sign_in_count: ログイン回数 unconfirmed_email: 未確認Eメール unlock_token: ロック解除用トークン updated_at: 更新日 models: user: ユーザ devise: confirmations: confirmed: アカウントを登録しました。 new: resend_confirmation_instructions: アカウント確認メール再送 send_instructions: アカウントの有効化について数分以内にメールでご連絡します。 send_paranoid_instructions: メールアドレスが登録済みの場合、本人確認用のメールが数分以内に送信されます。 failure: already_authenticated: すでにログインしています。 inactive: アカウントが有効化されていません。メールに記載された手順にしたがって、アカウントを有効化してください。 invalid: "%{authentication_keys}またはパスワードが違います。" last_attempt: もう一回誤るとアカウントがロックされます。 locked: アカウントは凍結されています。 not_found_in_database: "%{authentication_keys}またはパスワードが違います。" timeout: セッションがタイムアウトしました。もう一度ログインしてください。 unauthenticated: アカウント登録もしくはログインしてください。 unconfirmed: メールアドレスの本人確認が必要です。 mailer: confirmation_instructions: action: メールアドレスの確認 greeting: "%{recipient}様" instruction: 以下のリンクをクリックし、メールアドレスの確認手続を完了させてください。 subject: メールアドレス確認メール email_changed: greeting: こんにちは、%{recipient}様。 message: あなたのメール変更(%{email})のお知らせいたします。 subject: メール変更完了。 password_change: greeting: "%{recipient}様" message: パスワードが再設定されたことを通知します。 subject: パスワードの変更について reset_password_instructions: action: パスワード変更 greeting: "%{recipient}様" instruction: パスワード再設定の依頼を受けたため、メールを送信しています。下のリンクからパスワードの再設定ができます。 instruction_2: パスワード再設定の依頼をしていない場合、このメールを無視してください。 instruction_3: パスワードの再設定は、上のリンクから新しいパスワードを登録するまで完了しません。 subject: パスワードの再設定について unlock_instructions: action: アカウントのロック解除 greeting: "%{recipient}様" instruction: アカウントのロックを解除するには下のリンクをクリックしてください。 message: ログイン失敗が繰り返されたため、アカウントはロックされています。 subject: アカウントの凍結解除について omniauth_callbacks: failure: "%{kind} アカウントによる認証に失敗しました。理由:(%{reason})" success: "%{kind} アカウントによる認証に成功しました。" passwords: edit: change_my_password: パスワードを変更する change_your_password: パスワードを変更 confirm_new_password: 確認用新しいパスワード new_password: 新しいパスワード new: forgot_your_password: パスワードを忘れましたか? send_me_reset_password_instructions: パスワードの再設定方法を送信する no_token: このページにはアクセスできません。パスワード再設定メールのリンクからアクセスされた場合には、URL をご確認ください。 send_instructions: パスワードの再設定について数分以内にメールでご連絡いたします。 send_paranoid_instructions: メールアドレスが登録済みの場合、パスワード再設定用のメールが数分以内に送信されます。 updated: パスワードが正しく変更されました。 updated_not_active: パスワードが正しく変更されました。 registrations: destroyed: アカウントを削除しました。またのご利用をお待ちしております。 edit: are_you_sure: 本当によろしいですか? cancel_my_account: アカウント削除 currently_waiting_confirmation_for_email: "%{email} の確認待ち" leave_blank_if_you_don_t_want_to_change_it: 空欄のままなら変更しません title: "%{resource}編集" unhappy: 気に入りません update: 更新 we_need_your_current_password_to_confirm_your_changes: 変更を反映するには現在のパスワードを入力してください new: sign_up: アカウント登録 signed_up: アカウント登録が完了しました。 signed_up_but_inactive: ログインするためには、アカウントを有効化してください。 signed_up_but_locked: アカウントが凍結されているためログインできません。 signed_up_but_unconfirmed: 本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。 update_needs_confirmation: アカウント情報を変更しました。変更されたメールアドレスの本人確認のため、本人確認用メールより確認処理をおこなってください。 updated: アカウント情報を変更しました。 updated_but_not_signed_in: あなたのアカウントは正常に更新されましたが、パスワードが変更されたため、再度ログインしてください。 sessions: already_signed_out: 既にログアウト済みです。 new: sign_in: ログイン signed_in: ログインしました。 signed_out: ログアウトしました。 shared: links: back: 戻る didn_t_receive_confirmation_instructions: アカウント確認のメールを受け取っていませんか? didn_t_receive_unlock_instructions: アカウントの凍結解除方法のメールを受け取っていませんか? forgot_your_password: パスワードを忘れましたか? sign_in: ログイン sign_in_with_provider: "%{provider}でログイン" sign_up: アカウント登録 minimum_password_length: "(%{count}字以上)" unlocks: new: resend_unlock_instructions: アカウントの凍結解除方法を再送する send_instructions: アカウントの凍結解除方法を数分以内にメールでご連絡します。 send_paranoid_instructions: アカウントが見つかった場合、アカウントの凍結解除方法を数分以内にメールでご連絡します。 unlocked: アカウントを凍結解除しました。 errors: messages: already_confirmed: は既に登録済みです。ログインしてください。 confirmation_period_expired: の期限が切れました。%{period} までに確認する必要があります。 新しくリクエストしてください。 expired: の有効期限が切れました。新しくリクエストしてください。 not_found: は見つかりませんでした。 not_locked: は凍結されていません。 not_saved: one: エラーが発生したため %{resource} は保存されませんでした。 other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした。"https://github.com/tigrish/devise-i18n/blob/master/rails/locales/ja.yml
【その4】
config/application.rb
に以下の一文を追記application.rbmodule Dansyalist class Application < Rails::Application config.i18n.default_locale = :ja 略 end end
以上4点にお気をつけて、快適な日本語ライフをお楽しみください。
ではまた!
- 投稿日:2019-11-28T08:37:47+09:00
バリデーションを設定しよう。
バリデーションとは
フォームから送られてきた値が正常な値であるか検証する機能です。
よく使う2つを見ていきましょう。①
presence: true
②unique: true
①値が空ではないか
フォームが空の場合は保存できなくします。
モデルに
validates :カラム名, presence: true
を追記例)tweetsテーブルのtextカラムに入る値を入力必須にしたい↓
tweet.rbclass Tweet < ApplicationRecord validates :text, presence: true endtextを入力しなかった場合にエラーとなり、保存できません。
②値が他と被ってないか
他のレコードに既に同じ値がある場合は保存できなくします。
唯一無二の値のことを、一意な値と言います。モデルに、
validates :カラム名, unique: true
を追記例)tweetsテーブルのtextカラムに入る値を一意にしたい↓
tweet.rbclass Tweet < ApplicationRecord validates :text, unique: true endまったく同じテキストは保存できなくなりました。
おはよう
とツイートできるのは1人だけ。早いもの勝ちとなります。
エラーメッセージを表示させる
保存できないことを知らせてあげましょう。
new.html.haml- if @tweet.errors.any? %div %h2= "#{@tweet.errors.full_messages.count}件のエラーが発生しました。" %ul - @tweet.errors.full_messages.each do |message| %li= message
もしエラーメッセージが表示されない時は以下を疑いましょう。
・form_with
のlocal: true
が抜けてる。new.html.haml= form_with model: tweet do |form| #誤り = form_with model: tweet, local: true do |form| #正しい僕はエラーメッセージが表示されなくて、2日間悩みました。
ではまた!
- 投稿日:2019-11-28T08:36:43+09:00
Railsチュートリアル 第10章 ユーザーの更新・表示・削除 - すべてのユーザーを表示する
何をするか
Usersコントローラーに
index
アクションを実装していきます。これにより、「すべてのユーザーの一覧表示」という操作が可能になります。追加機能として、以下の機能も実装していきます。
- RDBへのサンプルユーザーの追加
- ページネーション機能
- 英語ではpagination=ページ分割
- ユーザー一覧の出力を複数ページに分割する機能のこと
Railsチュートリアル本文においては、ユーザー一覧ページのモックアップとして、図 10.8が示されています。
ユーザー情報のセキュリティモデル
Railsチュートリアル本文においては、現在開発中のサンプルアプリケーションにおいて、ユーザー情報のセキュリティモデルは以下のように設計することとしています。
- ユーザーの
show
ページは、非ログインユーザーも含め、すべての利用者に表示を許可する- ユーザーの
index
ページは、ログイン済のユーザーのみに表示を許可するユーザーの一覧ページ
「ユーザーの
index
ページは、ログイン済のユーザーのみに表示を許可する」という動作に対するテスト表題内容の対偶をとると、「非ログインユーザーがユーザーの
index
ページを表示しようとした場合、表示を許可しない」という内容になります。「表示を許可しない」という場合の動作は、「ログインページにリダイレクトする」というのが最も自然ですね。ということで、「非ログインユーザーがユーザーの
index
ページを表示しようとした場合」に対応するテストの実装は、以下のようになります。名前は「should redirect index when not logged in」とします。test "should redirect index when not logged in" do get users_path assert_redirected_to login_url end実装箇所は
test/controllers/users_controller_test.rb
内です。test/controllers/users_controller_test.rbrequire 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) @other_user = users(:mkirisame) end + + test "should redirect index when not logged in" do + get users_path + assert_redirected_to login_url + end ...略 end
現時点で、このテストは失敗します。
test/controllers/users_controller_test.rb(10行目)test "should redirect index when not logged in" do# rails test test/controllers/users_controller_test.rb:10 Running via Spring preloader in process 1376 Started with run options --seed 60339 ERROR["test_should_redirect_index_when_not_logged_in", UsersControllerTest, 0.728221700002905] test_should_redirect_index_when_not_logged_in#UsersControllerTest (0.73s) AbstractController::ActionNotFound: AbstractController::ActionNotFound: The action 'index' could not be found for UsersController test/controllers/users_controller_test.rb:11:in `block in <class:UsersControllerTest>' 6/6: [===================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.73096s 1 tests, 0 assertions, 0 failures, 1 errors, 0 skips現時点でUsersコントローラーに
index
アクションは実装されていないため、「Usersコントローラーにindex
アクションが実装されていない」旨のエラーメッセージが出力されます。Usersコントローラーに
index
アクションを実装し、beforeフィルターの対象とする続いて
index
アクションを実装していきます。index
アクションの動作は、現状ではRailsデフォルトの動作とするため、実装は何も記述せずにメソッド定義だけ記述することとします。「ユーザーの
index
ページは、ログイン済のユーザーのみに表示を許可する」という動作は、「index
アクションを、beforeフィルターのlogged_in_user
に追加し、同メソッドによる保護対象とする」ことにより実現できます。早速app/controllers/users_controller.rb
の内容を修正しましょう。app/controllers/users_controller.rbclass UsersController < ApplicationController - before_action :logged_in_user, only: [:edit, :update] + before_action :logged_in_user, only: [:index, :edit, :update] before_action :correct_user, only: [:edit, :update] + + def index + end ...略 endここまでの実装が完了すると、テスト「should redirect index when not logged in」が成功するようになります。
# rails test test/controllers/users_controller_test.rb:10 Running via Spring preloader in process 1402 Started with run options --seed 22324 6/6: [===================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.54567s 1 tests, 1 assertions, 0 failures, 0 errors, 0 skipsindexビューを実装する
Usersコントローラーで
index
アクションの実装が済んだら、今度はindexビューを実装します。中身は「全ユーザーが格納された変数を作成し、順々に表示する」というものになります。indexビューを実装するために、Usersコントローラーの
index
アクションに必要となる新たな実装以下の動作をUsersコントローラーの
index
アクションにに実装する必要があります。
User.all
を用い、RDBに保存された全ユーザーの情報を取得する- 取得したユーザー情報を、ビューで使えるユーザー変数
@users
に代入するコードとしては以下のようになります。
@users = User.all早速Usersコントローラーの
index
アクションに反映しましょう。app/controllers/users_controller.rbclass UsersController < ApplicationController ...略 def index + @users = User.all end ...略 end
indexビューを実際に実装する
indexビューのファイル名は
app/views/users/index.html.erb
となります。現時点で当該ファイルは存在しないため、まず当該ファイルを新規に作成するところから始まります。>>> touch app/views/users/index.html.erb空の
app/views/users/index.html.erb
が作成できたら、埋め込みRubyを記述していきます。app/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul>上述埋め込みRubyコードで、特筆すべき点は以下です。
- ユーザーを列挙する領域全体は
ul
要素としているeach
メソッドでユーザーを列挙している- 各ユーザーのプロフィール画像と名前を
li
タグで囲っているUsersHelper#gravatar_for
の:size
オプションに、デフォルト以外の値を与えている
- デフォルトは80、今回与えた値は50
(参考)
UsersHelper#gravatar_for
の現状の実装なお、私の環境では、現状の
UsersHelper#gravatar_for
の実装は以下のようになっています。UsersHelper#gravatar_fordef gravatar_for(user, options = { size:80 }) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) size = options[:size] gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") endユーザーのindexページの表示を整えるために、CSSに手を加える
app/assets/stylesheets/custom.scss...略 + + .users { + list-style: none; + margin: 0; + li { + overflow: auto; + padding: 10px 0; + border-bottom: 1px solid $gray-lighter; + } + } ...略
ビューのヘッダー部に、ユーザー一覧ページへのリンクを追加する
ビューのヘッダー部には、すでにユーザー一覧ページへのリンクが存在します。リンク先は、現時点まで仮に
#
としてきましたが、ここまでの実装でユーザー一覧ページが表示できるようになったので、実際のユーザー一覧ページにリンクするようにします。app/views/layouts/_header.html.erb<header class="navbar navbar-fixed-top navbar-inverse"> <div class="container"> ...略 <nav> <ul class="nav navbar-nav navbar-right"> ...略 <% if logged_in? %> - <li><%= link_to "Users", '#' %></li> + <li><%= link_to "Users", users_path %></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> Account<b class="caret"></b> </a> <ul class="dropdown-menu"> <li><%= link_to "Profile", current_user %></li> <li><%= link_to "Settings", edit_user_path(current_user) %></li> <li class="divider"></li> <li> <%= link_to "Log out", logout_path, method: :delete %> </li> </ul> </li> <% else %> <li><%= link_to "Log in", login_path %></li> <% end %> </ul> </nav> </div> </header>実際に動くユーザーのindex
ここまでの実装が完了すれば、ユーザーのindexは実際に動くようになります。
# rails test Running via Spring preloader in process 1441 Started with run options --seed 33187 36/36: [=================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.18539s 36 tests, 96 assertions, 0 failures, 0 errors, 0 skips新たに実装したテストも含め、すべてのテストが無事通ることが確認できました。
上記はログインユーザーでusersページを表示したスクリーンショットです。
演習 - ユーザーの一覧ページ
1. レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。
ヒント: log_in_asヘルパーを使ってリスト 5.32にテストを追加してみましょう。
テストを実装するソースファイルは
test/integration/site_layout_test.rb
となります。ログイン状態の再現に必要な処理
ログイン状態を再現するために、まずは
setup
メソッドにより、fixtureから@user
に有効なユーザー情報を与える必要があります。対応するコードは以下になります。def setup @user = users(:rhakurei) endログイン済みユーザー・非ログインユーザー共通に必要となるテスト
リクエストの内容は、「当該レイアウトを使うリソースへの
GET
リクエスト」であれば何でも構いません。今回は「/ へのGET
リクエスト」とします。get root_pathHome・Help・About・Contactへのリンクは、ログイン済みユーザー・非ログインユーザー関係なく共通です。対応するコードは以下になります。
ログイン済みユーザー・非ログインユーザー共通に必要となるテストassert_select "a[href=?]", root_path assert_select "a[href=?]", help_path assert_select "a[href=?]", about_path assert_select "a[href=?]", contact_path非ログインユーザーに必要となるテスト
ログイン済みユーザーでない場合、レイアウトに存在するリンクは以下の要件を満たす必要があります。
- ログインページへのリンクが存在すること
- ユーザー一覧ページへのリンクが存在しないこと
- ユーザー情報ページへのリンクが存在しないこと
- ユーザー情報編集ページへのリンクが存在しないこと
- ログアウトページへのリンクが存在しないこと
対応するコードは以下になります。
非ログインユーザーに必要となるテストassert_select "a[href=?]", login_path assert_select "a[href=?]", users_path, count: 0 assert_select "a[href=?]", user_path(@user), count: 0 assert_select "a[href=?]", edit_user_path(@user), count: 0 assert_select "a[href=?]", logout_path, count: 0ログイン済みユーザーに必要となるテスト
ログイン済みユーザーの場合、レイアウトに存在するリンクは以下の要件を満たす必要があります。
- ログインページへのリンクが存在しないこと
- ユーザー一覧ページへのリンクが存在すること
- 自身のユーザー情報ページへのリンクが存在すること
- 自身のユーザー情報編集ページへのリンクが存在しないこと
- ログアウトページへのリンクが存在すること
対応するコードは以下になります。
ログイン済みユーザーに必要となるテストassert_select "a[href=?]", login_path, count: 0 assert_select "a[href=?]", users_path assert_select "a[href=?]", user_path(@user) assert_select "a[href=?]", edit_user_path(@user) assert_select "a[href=?]", logout_path
test/integration/site_layout_test.rb
の変更内容
test/integration/site_layout_test.rb
の変更内容の全体像は以下のようになります。test/integration/site_layout_test.rbrequire 'test_helper' class SiteLayoutTest < ActionDispatch::IntegrationTest + + def setup + @user = users(:rhakurei) + end ...略 + + test "layout links without login" do + get root_path + assert_select "a[href=?]", root_path + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + assert_select "a[href=?]", login_path + assert_select "a[href=?]", users_path, count: 0 + assert_select "a[href=?]", user_path(@user), count: 0 + assert_select "a[href=?]", edit_user_path(@user), count: 0 + assert_select "a[href=?]", logout_path, count: 0 + end + + test "layout links with login" do + log_in_as @user + get root_path + assert_select "a[href=?]", root_path + assert_select "a[href=?]", help_path + assert_select "a[href=?]", about_path + assert_select "a[href=?]", contact_path + assert_select "a[href=?]", login_path, count: 0 + assert_select "a[href=?]", users_path + assert_select "a[href=?]", user_path(@user) + assert_select "a[href=?]", edit_user_path(@user) + assert_select "a[href=?]", logout_path + end end今実装したテストが成功することを確認する
ここまで実装が完了すれば、
test/integration/site_layout_test.rb
全体を使ったテストを実行できるようになります。ここまでに記述してきた振る舞いは、本当に正しいのでしょうか。実際にやってみましょう。# rails test test/integration/site_layout_test.rb Running via Spring preloader in process 1584 Started with run options --seed 33479 3/3: [===================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.33232s 3 tests, 25 assertions, 0 failures, 0 errors, 0 skipsテストは無事成功しました。ここまでに記述してきた振る舞いは確かに正しいようです。
サンプルのユーザー
Fakerというgem
Fakerはダミーデータを生成するためのgemです。「開発環境などで、開発中のアプリケーションの振る舞いを検証するため、ある程度まとまった数のデータが欲しい」という場合に便利です。
Gemfile
にFakerを追加するGemfilesource 'https://rubygems.org' gem 'rails', '5.1.6' gem 'bcrypt', '3.1.12' + gem 'faker', '1.7.3' ...略 group :development do ...略 end ...略
Faker gemは、通常開発環境でしか使わないため、通常はGemfileの
:development
グループ以下に追加します。しかしながら、Railsチュートリアルのサンプルプログラムにおいては、「本番環境でFakerを使う」という例外的な運用が発生するため、Faker gemをGemfileのグループ外に追加しています。
bundle install
でFakerをインストールするFakerをGemfileに追加したら、例によって
bundle install
を実行します。# bundle install ...略 Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies... Bundler could not find compatible versions for gem "i18n": In snapshot (Gemfile.lock): i18n (= 1.6.0) In Gemfile: rails (= 5.1.6) was resolved to 5.1.6, which depends on activesupport (= 5.1.6) was resolved to 5.1.6, which depends on i18n (< 2, >= 0.7) faker (= 1.7.3) was resolved to 1.7.3, which depends on i18n (~> 0.5) Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict.うまくいきませんでした。RailsとFakerで、それぞれが別に依存する
i18n
というgemの要求バージョンが合わないことが原因のようです。指示通りに
bundle update
を実行してみます。# bundle update The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies..... ...略 Fetching faker 1.7.3 Installing faker 1.7.3 ...略 Bundle updated! Gems in the group production were not installed.今度こそ無事にFaker gemがインストールできたようです。
100人のサンプルユーザーをRDB上に生成する
db/seeds.rb
というファイルに、100人のサンプルユーザーを生成するためのコードを追加します。サンプルユーザーの内訳は以下のとおりです。
- 「Example User」という名前とメールアドレスを持つ1人のユーザー
- それらしい名前とメールアドレスを持つ99人のユーザー
db/seeds.rbUser.create(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar") 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!( name: name, email: email, password: password, password_confirmation: password) endこのコードのポイントは以下です。
create
ではなくcreate!
メソッドを用いている
- ユーザーが無効な場合に
nil
を返すのではなく、例外を投げる- エラーを検知できる可能性を高めることを狙っている
RDBのリセットと、100人のユーザーの生成
RDBをリセットした上で、上記
db/seeds.rb
の内容に基づき、実際に100人のユーザーをサンプルアプリケーション上に生成してみます。まずはRDBをリセットします。
# rails db:migrate:reset Dropped database 'db/development.sqlite3' Dropped database 'db/test.sqlite3' Created database 'db/development.sqlite3' Created database 'db/test.sqlite3' ...略 == [timestamp] AddRememberDigestToUsers: migrating ========================= -- add_column(:users, :remember_digest, :string) -> 0.0116s == [timestamp] AddRememberDigestToUsers: migrated (0.0119s) ================続いて、実際に
db/seeds.rb
の内容をRDBに反映します。コマンドはrails db:seed
です。# rails db:seedエラーがなければ、シェルには何も表示されずに実行が終わります。
100人のユーザーが生成された!
ログインしてindexページを表示してみましょう。
Michael Hartl氏の手により、最初のいくつかのメールアドレスには、デフォルトのGravatar画像以外の写真が関連付けられています。その写真もきちんと表示されていますね。
# rails console --sandbox >> User.count (1.1ms) SELECT COUNT(*) FROM "users" => 100
rails console
からUser.count
を実行した結果も、きちんと100
が返ってきました。演習 - サンプルのユーザー
1. 試しに他人の編集ページにアクセスしてみて、10.2.2で実装したようにリダイレクトされるかどうかを確かめてみましょう。
現在、id=1のユーザーでログインしていることを前提とします。
--- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess controller: users action: show id: '1' permitted: false/users/1/edit への
GET
リクエストに対しては、最終的に「200 OK」のステータスコードが返ってきます。Started GET "/users/1/edit" ...略 Completed 200 OK in 648ms (Views: 604.0ms | ActiveRecord: 9.6ms)--- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess controller: users action: edit id: '1' permitted: false続いて、/users/2/edit に
GET
リクエストを送出してみます。Started GET "/users/2/edit" ...略 Redirected to http://localhost:8080/ Filter chain halted as :correct_user rendered or redirected Completed 302 Found in 14ms (ActiveRecord: 8.1ms) Started GET "/" for ...略 Completed 200 OK in 378ms (Views: 351.2ms | ActiveRecord: 2.8ms)「ルートURLにリダイレクトされる」という動作は、確かに実装した通りですね。
ページネーション
ページネーションの必要性
「1つのページに大量のユーザーが表示される」というのは、検索ロボットや自動処理用のスクリプトによるアクセスであればともかく、人間が使う上では非常に使いづらいです。100人でも人間が扱うには多いですし、今後ユーザー数が数千人に増える可能性もあります。1つのページに表示されるユーザーの数は、人間が扱いやすい人数、例えば30人にしたいところです。
そうした要求を実現する機能がページネーション(pagination)です。Railsチュートリアル本文では、will_paginateメソッドによるページネーションを実装する、としています。
ページネーションの実装に必要なgemの追加
前提として、ページネーションの実装は以下の条件で行うものとします。
- ページネーションには
will_paginate
メソッドを用いる- Bootstrapのページネーションスタイルを適用する
上記前提条件の元で新たに必要となるgemは、
will_paginate
とbootstrap-will_paginate
の2つになります。早速Gemfileに追加しましょう。Gemfilesource 'https://rubygems.org' gem 'rails', '5.1.6' gem 'bcrypt', '3.1.12' gem 'faker', '1.7.3' + gem 'will_paginate', '3.1.6' + gem 'bootstrap-will_paginate', '1.0.0' ...略
続いて
bundle install
を実行します。# bundle install ...略 Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies.... ...略 Fetching will_paginate 3.1.6 Installing will_paginate 3.1.6 Fetching bootstrap-will_paginate 1.0.0 Installing bootstrap-will_paginate 1.0.0 ...略 Bundle complete! 26 Gemfile dependencies, 84 gems now installed. Gems in the group production were not installed. Bundled gems are installed into `/usr/local/bundle`問題なくgemの追加が完了したようですね。
ページネーションを実際に使う
必要となる実装
追加・変更が必要となる実装は以下です。
- indexビューでページネーションを使うようにする
- Usersコントローラーの
index
アクションの動作を、ページネーションに対応したものに変更する
- 具体的には、RDBからユーザーを取得する処理を書き換える
indexページでページネーションを使うようにする
ページネーションのリンクを表示するためのメソッドを
app/views/users/index.html.erb
に追加すればOKです。今回はwill_paginate
メソッドですね。app/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> + <%= will_paginate %> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> + <%= will_paginate %>Railsチュートリアル本文では、indexビュー内の
will_paginate
メソッドの動作について以下のように説明しています。
users
ビューのコード中から、@users
オブジェクトを自動的に見つけ出す- 他のページにアクセスするためのページネーションリンクを作成する
will_paginate
をページの上下に2つ追加しているのは、「表示されているユーザー一覧の先頭・末尾両方にページネーションのリンクを表示する」ようにするためです。
will_paginate
メソッドが必要とする@users
変数の内容
will_paginate
メソッドを使う場合、@users
変数はpaginate
メソッドの戻り値である必要があります。paginate
メソッドの実行例を以下に示します。# rails console --sandbox >> User.paginate(page: 1) User Load (14.7ms) SELECT "users".* FROM "users" LIMIT ? OFFSET ? [["LIMIT", 11], ["OFFSET", 0]] (0.8ms) SELECT COUNT(*) FROM "users" => #<ActiveRecord::Relation [#<User id: 1,...
paginate
メソッドは、キーが:page
で値がページ番号のハッシュを引数として取るUser.paginate
は、:page
パラメーターに基づき、RDBからUserモデルに紐付けされたひとかたまり(デフォルトでは30)のデータを取り出す
- 1ページ目は1番目から31番目のユーザー
- 2ページ目は31番目から60番目のユーザー
- 以下略
- なお、キー
:page
に対する値がnil
の場合は、単に最初のページが返ってくる# rails console --sandbox >> User.paginate(page: nil) User Load (0.2ms) SELECT "users".* FROM "users" LIMIT ? OFFSET ? [["LIMIT", 11], ["OFFSET", 0]] (0.3ms) SELECT COUNT(*) FROM "users" => #<ActiveRecord::Relation [#<User id: 1,...上記はキー
:page
に対する値をnil
としてUser.paginate
を実行した例です。Usersコントローラーの
index
アクションの動作を、ページネーションに対応したものに変更する具体的には、
index
アクション内のall
をpaginate
に置き換えます。なお、params[:page]
は、ビュー側のwill_paginate
によって自動的に与えられます。def index @users = User.paginate(page: params[:page]) end実際に
app/controllers/users_controller.rb
に対して行う変更は以下のとおりになります。app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update] before_action :correct_user, only: [:edit, :update] def index - @users = User.all + @users = User.paginate(page: params[:page]) end ...略 endページネーション機能が使えるようになる
ここまでの実装が完了すれば、サンプルアプリケーションにおけるページネーション機能が有効になります。
ページネーションのリンクが表示されているのがわかります。
2ページ目は上記スクリーンショットのようになります。
演習 - ページネーション
1. Railsコンソールを開き、
page
オプションにnil
をセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。本文中で例を示したとおりです。
2. 先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、
User.all
のクラスとどこが違うでしょうか? 比較してみてください。>> User.paginate(page: 1).class => User::ActiveRecord_Relation >> User.paginate(page: 1).class.superclass => ActiveRecord::Relation >> User.paginate(page: 1).class.superclass.superclass => Object >> User.all.class => User::ActiveRecord_Relationpaginationオブジェクトのクラスは
User::ActiveRecord_Relation
です。User.all
のクラスと違いはないようです。ユーザー一覧のテスト
30人超のユーザーをfixtureに追加する
ページネーションに対するテストを行うためには、30人を上回るユーザー情報が必要となります。これだけの人数のユーザー情報を人手で追加するのは面倒です。
しかしながら、fixtureは埋め込みRubyに対応しています。2人のユーザー情報は手動で追加し、さらに30人のそれらしいユーザー情報はイテレータを使って追加することにしましょう。
test/fixtures/users.ymlrhakurei: name: Reimu Hakurei email: rhakurei@example.com password_digest: <%= User.digest('password') %> mkirisame: name: Marisa Kirisame email: example.example@example.org password_digest: <%= User.digest('password') %> + + skomeiji: + name: Satori Komeiji + email: example_example@example.net + password_digest: <%= User.digest('password') %> + + rusami: + name: Renko Usami + email: example0@example.com + password_digest: <%= User.digest('password') %> + + <% 30.times do |n| %> + user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> + <% end %>
indexに対する統合テストの生成
続いて、indexに対する統合テストを生成します。
# rails generate integration_test users_index Running via Spring preloader in process 11865 invoke test_unit create test/integration/users_index_test.rbindexに対する統合テストを書く
「
pagination
クラスを持ったdiv
タグをチェックして、最初のページにユーザーがいることを確認する」というテストを書いていきます。test/integration/users_index_test.rbrequire 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @user = users(:rhakurei) end test "index including pagination" do log_in_as(@user) get users_path assert_template 'users/index' assert_select 'div.pagination' User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end end endテストは無事成功
test/integration/users_index_test.rb
を対象としてテストを実行してみます。# rails test test/integration/users_index_test.rb Running via Spring preloader in process 12058 Started with run options --seed 56737 1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.12612s 1 tests, 32 assertions, 0 failures, 0 errors, 0 skips無事テストが成功しました。
余談 - 「
div.pagination
が無い」と言われてテストが失敗する場合私はこのテストが通らずに数十分悩むこととなりました。Railsチュートリアル 第10章 - 「div.paginationが無い」と言われてテストが失敗する場合にて、顛末を記述しています。
演習 - ユーザー一覧のテスト
1. 試しにリスト 10.45にあるページネーションのリンク (
will_paginate
の部分) を2つともコメントアウトしてみて、リスト 10.48のテストがred
に変わるかどうか確かめてみましょう。app/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> - <%= will_paginate %> + <%# <%= will_paginate %> %> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> - <%= will_paginate %> + <%# <%= will_paginate %> %># rails test test/integration/users_index_test.rb Running via Spring preloader in process 12071 Started with run options --seed 63044 FAIL["test_index_including_pagination", UsersIndexTest, 2.9782001999847125] test_index_including_pagination#UsersIndexTest (2.98s) Expected at least 1 element matching "div.pagination", found 0.. Expected 0 to be >= 1. test/integration/users_index_test.rb:12:in `block in <class:UsersIndexTest>' 1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.98834s 1 tests, 2 assertions, 1 failures, 0 errors, 0 skips
Expected at least 1 element matching "div.pagination", found 0..
というメッセージが出てテストが失敗しています。pagination
というクラスを持つdiv
要素がない、という理由によるテストの失敗ですね。2.1. 先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが
green
のままであることを確認してみましょう。app/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> - <%= will_paginate %> + <%# <%= will_paginate %> %> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> - <%# <%= will_paginate %> %> + <%= will_paginate %># rails test test/integration/users_index_test.rb Running via Spring preloader in process 12084 Started with run options --seed 955 1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.81957s 1 tests, 32 assertions, 0 failures, 0 errors, 0 skips確かにテストが成功しますね。「
will_paginate
のリンクは2つ存在しなければテストが失敗する」という実装を実現するために…というのは次の演習です。2.2.
will_paginate
のリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか?ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。
assert_select 'div.pagination'
に、count: 2
というオプションを追加します。test/integration/users_index_test.rbrequire 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest ...略 test "index including pagination" do log_in_as(@user) get users_path assert_template 'users/index' - assert_select 'div.pagination' + assert_select 'div.pagination', count: 2 User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end end end# rails test test/integration/users_index_test.rb Running via Spring preloader in process 12097 Started with run options --seed 32036 FAIL["test_index_including_pagination", UsersIndexTest, 2.6599518999864813] test_index_including_pagination#UsersIndexTest (2.66s) Expected exactly 2 elements matching "div.pagination", found 1.. Expected: 2 Actual: 1 test/integration/users_index_test.rb:12:in `block in <class:UsersIndexTest>' 1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.66190s 1 tests, 2 assertions, 1 failures, 0 errors, 0 skips
Expected exactly 2 elements matching "div.pagination", found 1
というメッセージが出てテストが失敗しています。「pagination
というクラスを持つdiv
要素が2つ必要」と言ってきているので、テスト対象は確かに正しいようです。
app/views/users/index.html.erb
のすべてのコメントアウトを外し、正しい実装コードに戻します。app/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> - <%# <%= will_paginate %> %> + <%= will_paginate %> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> - <%# <%= will_paginate %> %> + <%= will_paginate %># rails test test/integration/users_index_test.rb Running via Spring preloader in process 12110 Started with run options --seed 37850 1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.50007s 1 tests, 32 assertions, 0 failures, 0 errors, 0 skips今度こそテストは成功しました。
パーシャルのリファクタリング
動機
indexページに関する機能的実装はここまでで完了し、テストも実装して成功する状態まで持っていくことができました。indexページに関するコードをさらに質の高いコードに書き換えていく準備はここまでで整っている…というのが現状の立ち位置です。
ここまで言及されていませんでしたが、Railsチュートリアル本文によれば、「Railsにはコンパクトなビューを作成するための素晴らしいツールがいくつもある」とのことです。そうしたコードを使ってindexページのコードをリファクタリングしたい、というのがこの節の動機です。
user
パーシャルを実装し、render
で個別ユーザー情報のHTMLコードを生成するようにするapp/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> <%= will_paginate %> <ul class="users"> <% @users.each do |user| %> - <li> - <%= gravatar_for user, size: 50 %> - <%= link_to user.name, user %> - </li> + <%= render user %> <% end %> </ul> <%= will_paginate %>上記コードのポイントは以下です。
- Userクラスの
user
変数を引数としてrender
を呼び出しているapp/views/users/_user.html.erb
というパーシャルが必要になるまずは
app/views/users/_user.html.erb
を作成しましょう。>>> touch app/views/users/_user.html.erb続いて、
app/views/users/_user.html.erb
の内容を記述していきます。app/views/users/_user.html.erb<li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li>ここまでで、「
user
パーシャルを実装し、render
で個別ユーザー情報のHTMLコードを生成するようにする」というリファクタリングはひとまず完了しました。実は、
render
は@users
を直接引数に取ることができる先ほど、「Userクラスの
user
変数を引数としてrender
を呼び出している」と言及しました。実は、@users
を直接引数とし、ブロックを使わない形でrender
を呼び出すというリファクタリングが可能です。app/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> <%= will_paginate %> <ul class="users"> - <% @users.each do |user| %> - <%= render user %> - <% end %> + <%= render @users %> </ul> <%= will_paginate %>上記コードのポイントは以下です。
- Railsは、
@users
をUserオブジェクトのコレクションであると推測する- Userオブジェクトのコレクションを引数として
render
を呼び出した際、render
の動作は以下となる
- Userオブジェクトのコレクションを列挙する
- Userオブジェクトの各インスタンスを、
_user.html.erb
パーシャルで出力する最後にテストを実行します。
# rails test Running via Spring preloader in process 12214 Started with run options --seed 3088 39/39: [=================================] 100% Time: 00:00:05, Time: 00:00:05 Finished in 5.56925s 39 tests, 146 assertions, 0 failures, 0 errors, 0 skipsテストは無事成功しました。
演習 - パーシャルのリファクタリング
1. リスト 10.52にあるrenderの行をコメントアウトし、テストの結果が
red
に変わることを確認してみましょう。app/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> <%= will_paginate %> <ul class="users"> - <%= render @users %> + <%# <%= render @users %> %> </ul> <%= will_paginate %># rails test test/integration/users_index_test.rb Running via Spring preloader in process 12201 Started with run options --seed 4733 FAIL["test_index_including_pagination", UsersIndexTest, 2.5006732000038028] test_index_including_pagination#UsersIndexTest (2.50s) Expected at least 1 element matching "a[href="/users/14035331"]", found 0.. Expected 0 to be >= 1. test/integration/users_index_test.rb:14:in `block (2 levels) in <class:UsersIndexTest>' test/integration/users_index_test.rb:13:in `block in <class:UsersIndexTest>' 1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.51114s 1 tests, 3 assertions, 1 failures, 0 errors, 0 skips
Expected at least 1 element matching "a[href="/users/14035331"]", found 0
というのは、「ユーザー情報へのリンクが描画されていない」という趣旨のメッセージですね。演習が終わったら、
app/views/users/index.html.erb
のコードを元に戻しておくことも忘れずに。app/views/users/index.html.erb<% provide(:title, 'All Users') %> <h1>All Users</h1> <%= will_paginate %> <ul class="users"> - <%# <%= render @users %> %> + <%= render @users %> </ul> <%= will_paginate %>
- 投稿日:2019-11-28T08:33:36+09:00
ルーティングのネスト
いつ使うの
ツイートにコメントできるようにする時を考えます。
コメントのparamsの中に、どのツイートに対してのコメントなのかわかるようにそのツイートのid情報を含めます。やり方→ルーティングをネストさせる
実際に例を見たほうが早いです。
routes.rbRails.application.routes.draw do resources :tweets do resources :comments, only: [:create, :destroy] end endこのように記述し、
rails routes
を叩いてルーティングを確認すると、、、
ターミナルtweet_comments POST /tweets/:tweet_id/comments(.:format) comments#create tweet_comment DELETE /tweets/:tweet_id/comments/:id(.:format) comments#destroy真ん中にtweet_idが含まれてるぅ!!!
これでどのツイートに対してのコメントかわかるようになったね!!!【注意】form_withでインスタンスを2つ渡す
haml- form_with model: [@tweet, @comment] do |form| = form.text_field :text = form.submit@tweetを渡すことでtweet_idを渡せるようになります。
お忘れなく。
ではまた!
- 投稿日:2019-11-28T07:07:32+09:00
【Rails】SSLの導入【Rails Tutorial 7章まとめ】
SSL
Secure Sockets Layer(SSL)は、ローカルのサーバーからネットワークに流れるデータを暗号化し、セキュリティを強化する技術である。
SSLを使うとURLがhttpからhttpsに変わる。SSLの導入
本番環境用の設定ファイルであるproduction.rbのconfig.force_sslをtrueに設定し、本番環境でSSLを使用させる。
config/environments/production.rbRails.application.configure do . . . # Force all access to the app over SSL, use Strict-Transport-Security, # and use secure cookies. config.force_ssl = true . . . end50行目あたりにある。
- 投稿日:2019-11-28T06:42:46+09:00
【Rails】ユーザー登録フォームの作成その3 ユーザー登録の成功【Rails Tutorial 7章まとめ】
ユーザー登録成功時の処理
ユーザー登録に成功すると、なぜかnewページに戻る。
これはcreateアクションでユーザーが作成されたあと、対応するcreateビューに行こうとするが、createビューが存在しないためである。
createビューは必要ないため、ここではユーザーのプロフィールページにリダイレクトすることにする。app/controllers/users_controller.rbdef create @user = User.new(user_params) if @user.save flash[:success] = "Welcome to the Sample App!" redirect_to user_url(@user) else render 'new' end enduser_url(@user)の行き先は/user/:idへのGETリクエストであり、showアクションに到達する。
また、リダイレクトの場合名前付きルートは_pathではなく_urlを使用する。
なお、user_url(@user)を単に@userと書いてもよいらしい。flash変数を使って、ユーザー登録成功時のメッセージを表示している。
ユーザー登録成功時のテスト
ユーザー登録成功時のテストを書く。
test/integration/users_signup_test.rbrequire 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest . . . test "valid signup information" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end follow_redirect! assert_template 'users/show' assert_not flash.empty? end endユーザー登録失敗時のテストと基本は同じ。
こちらはassert_differenceを使っている。
これはブロックの前後で第一引数が第二引数分だけ増減したことを確認する。
ユーザー登録の成功により、User.countは1増えているはずである。リダイレクト先に移動するにはfollow_redirect!を使う。
flashが表示されていることも確認しておく。
flashの表示
flashをレイアウトに追加して表示できるようにする。
app/views/layouts/application.html<body> <%= render 'layouts/header' %> <div class="container"> <% flash.each do |message_type, message| %> <div class="alert alert-<%= message_type %>"><%= message %></div> <% end %> <%= yield %> <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> </div> . . . </body>
- 投稿日:2019-11-28T05:34:39+09:00
【Rails】ユーザー登録フォームの作成その2 登録失敗時のエラー【Rails Tutorial 7章まとめ】
ユーザー登録に失敗した場合の処理
ユーザー登録に失敗した場合は、renderメソッドを使って新規登録ページに戻り、エラーメッセージを表示する。
renderを使うことで、newアクションの@user = User.newは実行されず、以前送信した内容がフォーム内に保持される。エラーメッセージはパーシャルを使って分けておくことにする。
フォーム内にはエラーメッセージ用パーシャルへのrenderを書く。app/views/users/new.html<%= form_for(@user) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %>各フォームについているform_controlクラスは、bootstrap用である。
エラーメッセージ用のパーシャルは次のようである。
app/views/shared/_error_messages.html.erbapp/views/shared/_error_messages.html<% if @user.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@user.errors.count, "error") %>. </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>コントローラにまたがって共通で使うパーシャルはsharedディレクトリに入れておく。
ここで、any?メソッドはempty?メソッドの逆であり、要素が一つでもある場合はtrue、ない場合はfalseを返す。
@user.errors.countは、エラーの個数を返す。pluralizeメソッドは、第一引数に整数を、第二引数に英単語をとり、整数値に応じて英単語を複数化する。
不規則活用にも使える。>> helper.pluralize(1, "error") => "1 error" >> helper.pluralize(5, "error") => "5 errors"エラーが起こり、newページに戻されると、エラーメッセージが表示される。
また、Railsによってfield_with_errorsクラスを持つdivタグによってフォームのラベルとフォームそのものを囲んでくれる。
これによってcssを使ってエラーを起こしたフォームの枠を赤くしたりできる。
cssは以下のよう。app/assets/stylesheets/custom.scss/* forms */ . . . #error_explanation { color: red; ul { color: red; margin: 0 0 30px 0; } } .field_with_errors { @extend .has-error; .form-control { color: $state-danger-text; } }has_errorはbootstrapのcssクラスである。
登録エラー時のテスト
新規ユーザー登録用の統合テストを生成する。
$ rails generate integration_test users_signup登録エラー時のテストを書く。
test/integration/users_signup_test.rbrequire 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_template 'users/new' assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end assert_template 'users/new' end end①まずsignup_pathにGETリクエストを送信する。行き先はnewアクションのnewビューである。
②newビューが表示されていることを確認する。
③assert_no_differenceを使って、ユーザー登録操作の前後でユーザー数が変わらないことを確認する。ユーザー登録が失敗すればユーザー数は変わらないはずである。
④users_pathにPOSTリクエストを送信する。postメソッドの第二引数にはparamsハッシュを指定する。
(paramsは変数でありハッシュでもあるらしい...よく分からない)
⑤ユーザー登録が失敗し、renderメソッドによってnewページに戻っていることを確認する。必要であれば
⑥エラーメッセージが表示されていることを確認する。assert_select 'div#<CSS id for error explanation>' assert_select 'div.<CSS class for field with error>'URLの修正
登録エラーでnewページに戻ると、URLが/signupから/usersに変わる。
これはusersリソースのRESTfulなルーティングによるもの。
フォームの送信は/usersのPOSTリクエストに送られて、createアクションに到達する。
その後登録エラーでnewページに戻るが、リダイレクトではなく再読み込みしていないので、URLはPOSTリクエストがされた時のままになる。
(という認識であってるかな?)これを修正するためにルーティングを変更する。
URLが/signupのPOSTリクエストを作る(行き先はcreateアクション)。config/routes.rbRails.application.routes.draw do . . . get '/signup', to: 'users#new' post '/signup', to: 'users#create' resources :users endフォームの送信先もこのルーティングに変更する。
app/views/users/new.html.erb<%= form_for(@user, url: signup_path) do |f| %> . . . <% end %>テストのPOSTリクエスト部分を修正する。また、form_forから生成されるformタグのaction属性(送信先のURLを値にとる)が正しいURLになっていることを確認する。
test/integration/users_signup_test.rbtest "invalid signup information" do get signup_path assert_template 'users/new' assert_no_difference 'User.count' do post signup_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end assert_template 'users/new' assert_select 'form[action="/signup"]' end
- 投稿日:2019-11-28T05:34:39+09:00
【Rails】ユーザー登録フォームの作成その2 登録失敗時のエラー【Rails Tutorial 7章まとめ】
ユーザー登録に失敗した場合の処理
ユーザー登録に失敗した場合は、renderメソッドを使って新規登録ページに戻り、エラーメッセージを表示する。
renderを使うことで、newアクションの@user = User.newは実行されず、以前送信した内容がフォーム内に保持される。エラーメッセージはパーシャルを使って分けておくことにする。
フォーム内にはエラーメッセージ用パーシャルへのrenderを書く。app/views/users/new.html<%= form_for(@user) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %>各フォームについているform_controlクラスは、bootstrap用である。
エラーメッセージ用のパーシャルは次のようである。
app/views/shared/_error_messages.html.erbapp/views/shared/_error_messages.html<% if @user.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@user.errors.count, "error") %>. </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>コントローラにまたがって共通で使うパーシャルはsharedディレクトリに入れておく。
ここで、any?メソッドはempty?メソッドの逆であり、要素が一つでもある場合はtrue、ない場合はfalseを返す。
@user.errors.countは、エラーの個数を返す。pluralizeメソッドは、第一引数に整数を、第二引数に英単語をとり、整数値に応じて英単語を複数化する。
不規則活用にも使える。>> helper.pluralize(1, "error") => "1 error" >> helper.pluralize(5, "error") => "5 errors"エラーが起こり、newページに戻されると、エラーメッセージが表示される。
また、Railsによってfield_with_errorsクラスを持つdivタグによってフォームのラベルとフォームそのものを囲んでくれる。
これによってcssを使ってエラーを起こしたフォームの枠を赤くしたりできる。
cssは以下のよう。app/assets/stylesheets/custom.scss/* forms */ . . . #error_explanation { color: red; ul { color: red; margin: 0 0 30px 0; } } .field_with_errors { @extend .has-error; .form-control { color: $state-danger-text; } }has_errorはbootstrapのcssクラスである。
登録エラー時のテスト
新規ユーザー登録用の統合テストを生成する。
$ rails generate integration_test users_signup登録エラー時のテストを書く。
test/integration/users_signup_test.rbrequire 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_template 'users/new' assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end assert_template 'users/new' end end①まずsignup_pathにGETリクエストを送信する。行き先はnewアクションのnewビューである。
②newビューが表示されていることを確認する。
③assert_no_differenceを使って、ユーザー登録操作の前後でユーザー数が変わらないことを確認する。ユーザー登録が失敗すればユーザー数は変わらないはずである。
④users_pathにPOSTリクエストを送信する。postメソッドの第二引数にはparamsハッシュを指定する。
(paramsは変数でありハッシュでもあるらしい...よく分からない)
⑤ユーザー登録が失敗し、renderメソッドによってnewページに戻っていることを確認する。必要であれば
⑥エラーメッセージが表示されていることを確認する。assert_select 'div#<CSS id for error explanation>' assert_select 'div.<CSS class for field with error>'URLの修正
登録エラーでnewページに戻ると、URLが/signupから/usersに変わる。
これはusersリソースのRESTfulなルーティングによるもの。
フォームの送信は/usersのPOSTリクエストに送られて、createアクションに到達する。
その後登録エラーでnewページに戻るが、リダイレクトではなく再読み込みしていないので、URLはPOSTリクエストがされた時のままになる。
(という認識であってるかな?)これを修正するためにルーティングを変更する。
URLが/signupのPOSTリクエストを作る(行き先はcreateアクション)。config/routes.rbRails.application.routes.draw do . . . get '/signup', to: 'users#new' post '/signup', to: 'users#create' resources :users endフォームの送信先もこのルーティングに変更する。
app/views/users/new.html.erb<%= form_for(@user, url: signup_path) do |f| %> . . . <% end %>テストのPOSTリクエスト部分を修正する。また、form_forから生成されるformタグのaction属性(送信先のURLを値にとる)が正しいURLになっていることを確認する。
test/integration/users_signup_test.rbtest "invalid signup information" do get signup_path assert_template 'users/new' assert_no_difference 'User.count' do post signup_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end assert_template 'users/new' assert_select 'form[action="/signup"]' end
- 投稿日:2019-11-28T05:33:39+09:00
【Rails】ユーザー登録フォームの作成【Rails Tutorial 7章まとめ】
newアクションとルーティング
ユーザーを新規登録するため、newアクションで新しいUserオブジェクトを作成し、@user変数に入れておく。
app/controllers/users_controller.rbclass UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @user = User.new end endルーティングはURLを/signupとして、usersコントローラのnewアクションに紐づけておく。
config/routes.rbRails.application.routes.draw do . . . get '/signup', to: 'users#new' resources :users endユーザー登録フォーム
form_forヘルパーメソッドを使って、ユーザーの新規登録フォームを作る。
form_forはUserオブジェクトを引数にとり、その属性でフォームを生成する。ユーザー登録フォームは次のようになる。
app/views/users/new.html.erbapp/views/users/new.html<% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>このコードにより生成されるhtmlは次のようになる。
app/views/users/new.html<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post"> <input name="utf8" type="hidden" value="✓" /> <input name="authenticity_token" type="hidden" value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" /> <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" /> <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" /> <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" /> <label for="user_password_confirmation">Confirmation</label> <input id="user_password_confirmation" name="user[password_confirmation]" type="password" /> <input class="btn btn-primary" name="commit" type="submit" value="Create my account" /> </form>form_forを使ったフォームの構造
form_forの役割
form_forは変数fを使ったブロックをとる構造になっており、次のようなhtmlを生成する。
<%= form_for(@user) do |f| %> . . . <% end %> ↓ <form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post"> . . . </form>action属性は"/users"、method属性は"post"となっている。
この2つの属性は、/usersに対してHTTPのPOSTリクエストを送信する、といった指示をしている。
すると、Usersリソースが提供するRESTfulなルート(https://qiita.com/kagamiya9/items/48f66b20aee03fe9da1f )に基づいて、createアクションに行き着く。このような流れが自動でできるのは、次のような理由らしい。
①form_forの引数は@userであり、Railsは@userがUserクラスであることを認識する。
②@userはnewアクションで新規作成されているため、Railsはpostメソッドを使ってフォームを構築すべきだと判断する。各入力フォーム
nameの入力フォームは次のようになる。
<%= f.label :name %> <%= f.text_field :name %> ↓ <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" />emailの入力フォームは次のようになる。
<%= f.label :email %> <%= f.email_field :email %> ↓ <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" />nameのtype属性は"text"だが、emailでは"email"である。
前者はf.text_field、後者はf.email_fieldとすることでtype属性がそれぞれ決まっている。
こうすると、スマホなどではメールアドレス入力用のキーボードが表示されるようになっている。passwordの入力フォームは次のようになる。
<%= f.label :password %> <%= f.password_field :password %> ↓ <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" />type属性がpasswordの場合、フォームに文字を入力すると黒丸•で表示されるようになる。
password_confiramtionの入力フォームは次のようになる。
<%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> ↓ <label for="user_password_confirmation">Confirmation</label> <input id="user_password_confirmation" name="user[password_confirmation]" type="password" />password_confirmationでは、f.labelの第二引数に文字列"Confirmation"をとっている。
これにより、フォーム上のラベルが指定した文字列になる。
他のフォームは属性ごとに自動で設定されている。
(nameはNameになる。なお、"Confirmation"を除くと、Password confirmationになる)
サブミットボタンも同様で、value属性が任意の文字列になる。Createアクションの作成
name属性によるハッシュの構成
<input id="user_name" name="user[name]" - - - /> <input id="user_password" name="user[password]" - - - />inputには特殊なname属性がついている。
Railsはnameの値を使って、初期化したハッシュを (params変数経由で) 構成する。どういうことかというと、送信された内容はまず次のようなハッシュになり、params変数に代入される。
params = { users: { name: "Foo Bar", email: "foo@invalid", password: "foo", password_confirmation: "bar" } }createアクションでは、このparams変数を使ってユーザーを新規登録する。
@user = User.new(params[:user]):userシンボルの値は、入力される属性(nameやemail)とその値からなるハッシュなので、上のコードは次のコードと同じである。
@user = User.new(name: "Foo Bar", email: "foo@invalid", password: "foo", password_confirmation: "bar")Strong Parameters
上のようなコードはセキュリティ上問題があるらしい。
paramsをそのまま送信すると、管理者用の属性であるadminなどの値を送信して、管理者権限を奪われるからだとか。
この辺は説明が非常に分かりにくいのだが、要は許可された属性以外は送信できないようにする、ということだ。結局のところ、次のようなuser_paramsメソッドをコントローラ内に作る。
これはStrong Parametersと呼ばれる。
先の問題があるコードは、マスアサイメントと呼ばれる。app/controllers/users_controller.rbclass UsersController < ApplicationController . . . def create @user = User.new(user_params) if @user.save # 保存の成功をここで扱う。 else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end endprivate内にあるメソッドはweb経由で外部に晒されることがない。
ユーザー登録に失敗した場合の処理
ユーザー登録に失敗した場合は、renderメソッドを使って新規登録ページに戻り、エラーメッセージを表示する。
エラー部分は長いので別記事にまとめることにする。ユーザー登録に成功した場合の処理
成功部分も別記事にまとめることにする。
- 投稿日:2019-11-28T05:33:39+09:00
【Rails】ユーザー登録フォームの作成その1【Rails Tutorial 7章まとめ】
newアクションとルーティング
ユーザーを新規登録するため、newアクションで新しいUserオブジェクトを作成し、@user変数に入れておく。
app/controllers/users_controller.rbclass UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @user = User.new end endルーティングはURLを/signupとして、usersコントローラのnewアクションに紐づけておく。
config/routes.rbRails.application.routes.draw do . . . get '/signup', to: 'users#new' resources :users endユーザー登録フォーム
form_forヘルパーメソッドを使って、ユーザーの新規登録フォームを作る。
form_forはUserオブジェクトを引数にとり、その属性でフォームを生成する。ユーザー登録フォームは次のようになる。
app/views/users/new.html.erbapp/views/users/new.html<% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>このコードにより生成されるhtmlは次のようになる。
app/views/users/new.html<form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post"> <input name="utf8" type="hidden" value="✓" /> <input name="authenticity_token" type="hidden" value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" /> <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" /> <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" /> <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" /> <label for="user_password_confirmation">Confirmation</label> <input id="user_password_confirmation" name="user[password_confirmation]" type="password" /> <input class="btn btn-primary" name="commit" type="submit" value="Create my account" /> </form>form_forを使ったフォームの構造
form_forの役割
form_forは変数fを使ったブロックをとる構造になっており、次のようなhtmlを生成する。
<%= form_for(@user) do |f| %> . . . <% end %> ↓ <form accept-charset="UTF-8" action="/users" class="new_user" id="new_user" method="post"> . . . </form>action属性は"/users"、method属性は"post"となっている。
この2つの属性は、/usersに対してHTTPのPOSTリクエストを送信する、といった指示をしている。
すると、Usersリソースが提供するRESTfulなルート(https://qiita.com/kagamiya9/items/48f66b20aee03fe9da1f )に基づいて、createアクションに行き着く。このような流れが自動でできるのは、次のような理由らしい。
①form_forの引数は@userであり、Railsは@userがUserクラスであることを認識する。
②@userはnewアクションで新規作成されているため、Railsはpostメソッドを使ってフォームを構築すべきだと判断する。各入力フォーム
nameの入力フォームは次のようになる。
<%= f.label :name %> <%= f.text_field :name %> ↓ <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" />emailの入力フォームは次のようになる。
<%= f.label :email %> <%= f.email_field :email %> ↓ <label for="user_email">Email</label> <input id="user_email" name="user[email]" type="email" />nameのtype属性は"text"だが、emailでは"email"である。
前者はf.text_field、後者はf.email_fieldとすることでtype属性がそれぞれ決まっている。
こうすると、スマホなどではメールアドレス入力用のキーボードが表示されるようになっている。passwordの入力フォームは次のようになる。
<%= f.label :password %> <%= f.password_field :password %> ↓ <label for="user_password">Password</label> <input id="user_password" name="user[password]" type="password" />type属性がpasswordの場合、フォームに文字を入力すると黒丸•で表示されるようになる。
password_confiramtionの入力フォームは次のようになる。
<%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> ↓ <label for="user_password_confirmation">Confirmation</label> <input id="user_password_confirmation" name="user[password_confirmation]" type="password" />password_confirmationでは、f.labelの第二引数に文字列"Confirmation"をとっている。
これにより、フォーム上のラベルが指定した文字列になる。
他のフォームは属性ごとに自動で設定されている。
(nameはNameになる。なお、"Confirmation"を除くと、Password confirmationになる)
サブミットボタンも同様で、value属性が任意の文字列になる。Createアクションの作成
name属性によるハッシュの構成
<input id="user_name" name="user[name]" - - - /> <input id="user_password" name="user[password]" - - - />inputには特殊なname属性がついている。
Railsはnameの値を使って、初期化したハッシュを (params変数経由で) 構成する。どういうことかというと、送信された内容はまず次のようなハッシュになり、params変数に代入される。
params = { users: { name: "Foo Bar", email: "foo@invalid", password: "foo", password_confirmation: "bar" } }createアクションでは、このparams変数を使ってユーザーを新規登録する。
@user = User.new(params[:user]):userシンボルの値は、入力される属性(nameやemail)とその値からなるハッシュなので、上のコードは次のコードと同じである。
@user = User.new(name: "Foo Bar", email: "foo@invalid", password: "foo", password_confirmation: "bar")Strong Parameters
上のようなコードはセキュリティ上問題があるらしい。
paramsをそのまま送信すると、管理者用の属性であるadminなどの値を送信して、管理者権限を奪われるからだとか。
この辺は説明が非常に分かりにくいのだが、要は許可された属性以外は送信できないようにする、ということだ。結局のところ、次のようなuser_paramsメソッドをコントローラ内に作る。
これはStrong Parametersと呼ばれる。
先の問題があるコードは、マスアサイメントと呼ばれる。app/controllers/users_controller.rbclass UsersController < ApplicationController . . . def create @user = User.new(user_params) if @user.save # 保存の成功をここで扱う。 else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end endprivate内にあるメソッドはweb経由で外部に晒されることがない。
ユーザー登録に失敗した場合の処理
ユーザー登録に失敗した場合は、renderメソッドを使って新規登録ページに戻り、エラーメッセージを表示する。
エラー部分は長いので別記事にまとめることにする。ユーザー登録に成功した場合の処理
成功部分も別記事にまとめることにする。
- 投稿日:2019-11-28T02:54:05+09:00
Rails 学習メモ
今日からチュートリアルを開始
プロジェクトの作成
$ rails new project_name
サーバーの起動
$ rails server
これで
localhost:3000
でアクセス可能Controllerの生成
$ rails generate controller home top => rails g controllerに変換可能Laravelの
php artisan make:controller
的な感じではなく、自動でviews/home/tophtml.erb
と、app/controllers/home_controller.rb
とルーティングのconfig/routes.rb
が生成され、ルーティングに自動で追加される。便利だなこれ。
ただ、既存のControllerが存在する場合、rails generate controller home test
はエラーになる。ルーティング
config/route.rb
に記述route.rbRails.application.routes.draw do get "top" => "home#top" get "about" => "home#about" end画像挿入
public配下に画像ファイルを配置し、呼び出し元で
/image_name.png
などを入力すれば反映される。
- 投稿日:2019-11-28T02:19:45+09:00
【Rails】Usersリソース【Rails Tutorial 7章まとめ】
Usersリソース
ユーザー情報をアプリケーション上で表示するために、RESTアーキテクチャに従って、データの作成、表示、更新、削除をリソースとして扱う。
これらに対応する4つの基本操作(POST、GET、PATCH、DELETE)を各アクションに割り当てていく。何がなんだかよく分からないが...
<id=1のユーザーを表示する場合>
URLは自動的に/users/1となる。
それに対してGETリクエストを送信すると、自動的にshowアクションが呼び出され、showビューが表示される。みたいなことをRESTとやらが勝手にしてくれる、ということらしい。
細かいことは後で調べることにして、ルーティングに次のように書く。
config/routes.rbRails.application.routes.draw do root 'static_pages#home' get '/help', to: 'static_pages#help' . . . resources :users endすると次のようなHTTPリクエスト、URL、アクション、名前付きルートの関係が自動で構築される。
ルーティングはこの通りに自動で設定されるので、各アクションやビューを手動で作ればOKである。
ユーザー情報を表示するshowアクションと仮のビューを作る。
app/controllers/users_controller.rbclass UsersController < ApplicationController def show @user = User.find(params[:id]) end def new end endapp/views/users/show.html.erb<%= @user.name %>, <%= @user.email %>これにより、/users/1にアクセスすると、params[:id]の:idに1が代入される。
findメソッドはid=1のユーザーをデータベースから探し、あれば@userに代入される。
@userのnameやemailは埋め込みRubyを使うことで、showビューで表示されるようになる。
- 投稿日:2019-11-28T02:04:09+09:00
【Rails】Gravatarによるプロフィール画像の表示【Rails Tutorial 7章まとめ】
Gravatar
Gravatar(http://ja.gravatar.com/ )は、メールアドレスとプロフィール画像を関連づけてくれるサービスである。
Gravatarヘルパーメソッド
UserオブジェクトからGravatarに登録された画像を表示できるようにするため、gravatar_forヘルパーメソッドを作成する。
app/helpers/users_helper.rbmodule UsersHelper # 引数で与えられたユーザーのGravatar画像を返す def gravatar_for(user) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end内容の詳細は無視することにして、これをユーザー表示ビューで使用してみる。
app/views/users/show.html.erb<% provide(:title, @user.name) %> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1>/users/1にアクセスして、ちゃんと表示されているか確認する。
プロフィール画像を設定したメールアドレスの場合
gravatarの画像は.gravatarというcssクラスが与えられている。
サイズのオプション引数
次のようにすると、gravatar_for user, size: 50のようにサイズを指定して呼び出せる。
app/helpers/users_helper.rbmodule UsersHelper # 引数で与えられたユーザーのGravatar画像を返す def gravatar_for(user, size: 80) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end3行目のURLも変更していることに注意する。
- 投稿日:2019-11-28T00:52:44+09:00
【Rails】デバッグ情報の表示【Rails Tutorial 7章まとめ】
debugメソッド
アプリケーションの動作を確認するために、debugメソッドを使ってサイトのレイアウトにデバッグ情報を表示する。
app/views/layouts/application.html.erb<body> <div> . . . <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> </div> </body>if Rails.env.development?とすると、開発環境でのみデバッグ情報を表示できる。
debug(params)から生成されたhtmlには、.debug_dumpというbootstrap用のcssクラスが与えられているようである。byebugジェムとdebugger
byebugジェムと、それによって使用できるようになるdebuggerメソッドを使うと、アプリケーションの任意の位置でその状態を確認できるようになる。
byebugジェムを導入する。
Gemfile.rbgroup :development, :test do gem 'sqlite3', '1.3.13' gem 'byebug', '9.0.6', platform: :mri enddebuggerをアプリケーションの任意の位置に挿入する。
app/controllers/users_controller.rbdef show @user = User.find(params[:id]) debugger end/users/1にアクセスすると、rails serverを立ち上げたコンソールにbyebugプロントが表示される。
(byebug)Railsコンソールのようにコマンドを呼び出せる。
(byebug) @user.name "Example User" (byebug) @user.email "example@railstutorial.org" (byebug) params[:id] "1"用が済んだらCtrl+Dで終了し、debuggerメソッドを削除しておく。