rescue_fromなどを用いたエラーハンドリングをリファクタリング
【結論】
・例外をキャッチする方法として、begin~rescueメソッドがあるが、メソッド全体を指定する場合、beginは省略可能
・rescue_fromを利用すれば、指定したコントローラー全体の例外をキャッチする事が可能
【目次】
【本題】
エラーハンドリングのリファクタリング
404や500エラーが発生した際に、任意のエラーページを表示させるには、コントローラーで発生する例外をキャッチする必要があります。
例外をキャッチする一般的な方法として、begin~rescue
を使用する方法があります。
class PostsController < ApplicationController def show begin @post = current_user.post.find(params[:id]) rescue ActiveRecord::RecordNotFound => e render 'errors/error_404', status: :not_found end def edit begin @post = current_user.post.find(params[:id]) rescue ActiveRecord::RecordNotFound => e render 'errors/error_404', status: :not_found end end
但し、これだと何点か冗長な部分があるので、リファクタリングを行っていきます。
beginの省略
まず、例外を検知する範囲をメソッド全体に指定するのであれば、begin
は省略可能です。
class PostsController < ApplicationController def show @post = current_user.post.find(params[:id]) rescue ActiveRecord::RecordNotFound => e render 'errors/error_404', status: :not_found end def edit @post = current_user.post.find(params[:id]) rescue ActiveRecord::RecordNotFound => e render 'errors/error_404', status: :not_found end end
rescue_fromの活用
rescue ActiveRecord::RecordNotFound => e
という記述がDRYでは無いので、共通化させます。
その際に利用するのが、rescue_from
です。
rescue_from
によって、コントローラー全体の例外をキャッチする事が可能になります。
class PostsController < ApplicationController rescue_from ActiveRecord::RecordNotFound do |exception| render 'errors/error_404', status: :not_found end def show @post = current_user.post.find(params[:id]) end def edit @post = current_user.post.find(params[:id]) end end
これにより、各メソッドの見通しが非常に良くなりました。
最後に、今回のテーマと関係無いですが、@post
を生成する部分もbefore_action
で共通化します。
class PostsController < ApplicationController rescue_from ActiveRecord::RecordNotFound do |exception| render 'errors/error_404', status: :not_found end before_action :set_post def show end def edit end private def set_post @post = current_user.posts.find(params[:id]) end end
これで完成です。
参考情報
https://ruby-rails.hatenadiary.com/entry/20141111/1415707101
[Ruby] 例外処理を実装する時のrescue書き方3パターン - Qiita
Rails tips: rescue_fromでコントローラのエラーをrescueする(翻訳)|TechRacho by BPS株式会社
ファイルアップローダー「CarrierWave・Shrine・ActiveStorage」の比較
【結論】
・CarrierWaveは、一通りの機能を備えており使用実績も多い。
・Shrineは、必要な機能のpluginだけ選択して読み込む事が出来る。
・ActiveStorageは、Ralis5.2から標準装備されたファイルアップローダー。
【目次】
【本題】
Railsのファイルアップローダーについて
Railsで画像などのファイルをアップロードする機能を実装する場合、ファイルアップローダーが必要になります。 今回はそのファイルアップローダーの中で、主流の三種類の比較を行いたいと思います。
CarrierWave
まず初めに紹介するのは「CarrierWave」です。 かなり古株のファイルアップローダーで、同系統のgemの中でも評価は上位にきています。
使用実績の通り、バリデーションやキャッシュなど一通りの事はこなせます。
Shrine
GitHub - shrinerb/shrine: File Attachment toolkit for Ruby applications
次に紹介するのは、Shrineです。
こちらは、まだ実績の浅いgemですが、必要な機能のpluginだけ選択して読み込ませる事で、動作を軽量化させたり、影響範囲を狭めてデバックを容易にしたりなど、CarrierWaveには無い特徴的な機能を備えています。
ActiveStorage
最後は、ActiveStorageです。
こちらは、Rails5.2から追加された機能で、本当に最近登場したばかりです。
標準で組み込まれているので、導入の容易さがメリットではありますが、まだ開発されて日浅い事もあり、機能面では他のgemに見劣りする部分があるようです(バリデーション・キャッシュ非対応など)
名前付きルートを使用する利点
【結論】
・名前付きルートとは、「パスpath」「パスurl」などの形式で、リンク先のURLを指定出来るヘルパー
・URL構造を気にせずにパスを指定出来るメリットがある
・また、SQLインジェクション対策としても用いられる
【目次】
【本題】
名前付きルートについて
名前付きルートとは、「パスpath」「パスurl」などの形式で、リンク先のURLを指定する為のヘルパーです。
下記のコードであれば、post_path
がそれにあたります。
= link_to '投稿する', post_path(@post), method: :post
なお、「パスヘルパー」や「URLヘルパー」とも呼ばれています。
URL構造を気にする必要が無くなる
これを用いる利点の一つに、URL構造を気にする必要が無くなるというものがあります。
もし、ヘルパーを使わないと、下記の様に階層が深いURLなどだと、可読性が落ちたり、
= link_to '投稿する', "accounts/#{account_id}/campaigns/#{campaign_id}/posts/#{params[:id]}/", method: :post
下記の様に、ヘルパーを使えば、随分と可読性は高まります。
= link_to '投稿する', accounts_campaigns_posts(@accounts, @campaign), method: :post
SQLインジェクションを防止出来る
もう一つの利点として、SQLインジェクションなどに対するセキュリティ対策になるという事です。
例えば、先ほどのコードですが、下記の様に書き換える事もできます。
= link_to '投稿する', "/posts/#{params[:id]}", method: :post
しかし、パラメーターは、ユーザーが任意に可変する余地がある箇所なので、これをURLに指定してしまうと、意図的に違う処理を引き起こしさせる「SQLインジェクション」の被害を受ける恐れがあります。
それを防ぐ為に、名前付きルートは役立てられています。
参考情報
ポリモーフィック関連付けについて
【結論】
・ポリモーフィック関連付けとは
ある特定のモデルが他の複数のモデルに属している事を
一つの記述だけで表す手法
・画像などを一つのモデル(テーブル)で
集約して管理したい場合に有用
・ポリモーフィック関連付けの元となるテーブルの
外部キーにimageable
を設定する事で
親モデルとレコードの判別が可能になる
【目次】
【本題】
ポリモーフィック関連付けについて
ポリモーフィック関連付けとは、
ある一つのモデルが他の複数のモデルに属している事を
一つの記述だけで表す手法です。
例えば、下記の様なアソシエーションです。
class Image < ApplicationRecord belongs_to :imageable, polymorphic: true end class User < ApplicationRecord has_many :images, as: :imageable end class Post < ApplicationRecord has_many :images, as: :imageable end
Image
の関連付けの記述は一つだけですが
これでuser
とpost
の
それぞれに従属している関係を表せます。
また、上記ではImageを例に出しましたが、
画像の様に、一つのテーブルで集約して管理した方が
拡張性/メンテナンス性の観点で優れている場合に、
この方法が採用されます。
外部キーの設定
なお、ポリモーフィック関連付けでは
外部キーの設定の仕方も独特です。
class CreateImages < ActiveRecord::Migration[5.0] def change create_table :images do |t| t.references :imageable, polymorphic: true, index: true t.text :image_data t.string :image_relation, limit: 120 t.timestamps end end end
上記の例の様にt.references :imageable, polymorphic: true, index: true
とすると、
imageable_type
とimageable_id
というカラムが生成されます。
これらによって、どのテーブルのどのレコードと
関連付いてのかを判別する事が可能となります。
参考情報
bin/setupの処理内容について
【結論】
・bin/setupでは、下記の処理を行っている。
・gemの依存関係のチェック、インストール
・データベースの再構築
・古いログと一時ファイルの削除
・サーバーを再起動
【目次】
- bin/setupの処理内容
- 実行結果
- Installing dependencies
- Preparing database
- Removing old logs and tempfiles
- Restarting application server
- カスタマイズも可能
【本題】
bin/setupの処理内容
コマンド一つで開発環境を整えるbin/setup
コマンドですが
実際にその処理内容がどういったものか気になったので、
確認して見ました。
実行結果
ターミナル上では、コマンドを実行すると下記の様に表示が出ます。
ここから読み解いて行きます。
$bin/setup == Installing dependencies == 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`. The Gemfile's dependencies are satisfied == Preparing database == Database 'good_erorrs_development' already exists Database 'good_erorrs_test' already exists -- create_table("post_comments", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0740s -- create_table("posts", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0201s -- create_table("users", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0197s -- add_foreign_key("post_comments", "posts") -> 0.0181s -- add_foreign_key("post_comments", "users") -> 0.0167s -- add_foreign_key("posts", "users") -> 0.0160s -- create_table("post_comments", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0462s -- create_table("posts", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0236s -- create_table("users", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0185s -- add_foreign_key("post_comments", "posts") -> 0.0171s -- add_foreign_key("post_comments", "users") -> 0.0165s -- add_foreign_key("posts", "users") -> 0.0162s == Removing old logs and tempfiles == == Restarting application server ==
Installing dependencies
== Installing dependencies == 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`. The Gemfile's dependencies are satisfied
必要なGemパッケージがインストールされているか調べています。
ここでは、The Gemfile's dependencies are satisfied
と表示されているので、
必要なGemは全てインストールされていることを表しています。
Preparing database
ここではデータベースの構築を行っています。
まず大元のデータベース(開発環境とテスト環境)の作成を行います。
ここでは既に作成済みの為、already exists
と表示されています。
次に各テーブルを作成しています。
なお、seedファイルがあれば、この段階で初期データの投入も行われます。
== Preparing database == Database 'good_erorrs_development' already exists Database 'good_erorrs_test' already exists -- create_table("post_comments", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0740s -- create_table("posts", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0201s -- create_table("users", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0197s -- add_foreign_key("post_comments", "posts") -> 0.0181s -- add_foreign_key("post_comments", "users") -> 0.0167s -- add_foreign_key("posts", "users") -> 0.0160s -- create_table("post_comments", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0462s -- create_table("posts", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0236s -- create_table("users", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8", :force=>:cascade}) -> 0.0185s -- add_foreign_key("post_comments", "posts") -> 0.0171s -- add_foreign_key("post_comments", "users") -> 0.0165s -- add_foreign_key("posts", "users") -> 0.0162s
Removing old logs and tempfiles
ここでは、古いログと一時ファイルの削除を行っています。
Restarting application server
ここでは、上記の処理を適用させる為に
サーバーを再起動させています。
カスタマイズも可能
なお、これらrails new
した際の標準の動作で、
もし処理を追加したい場合は、bin/setup
ファイルの
コードに手を加える事でカスタマイズが可能です。
「pry-rails、pry-byebug、pry-doc」それぞれの役割について
【結論】
・pry-railsは、
ソースコードに「binding.pry」を埋め込む事で
そこがブレークポイントにデバックが出来る
・pry-byebugは、
binding.pry で止めたところから
next コマンドで一行ずつステップ実行ができる
・pry-docは、
Cで書かれたRubyのソースコードを表示出来る
show-sourceコマンドを利用出来る様になる
【目次】
【本題】
デバックツールについて
Ruby on Railsでデバックを行う際、
主要なgemとして「pry-rails、pry-byebug、pry-doc」が存在する
今回は、それらの仕様について説明します。
pry-rails
ソースコードに「binding.pry」を埋め込む事で
そこがブレークポイントにデバックが出来ます。
上記の様に、変数の値を確認したり、新たな変数を 定義して結果を試したりといったデバックを行う事が可能です。
pry-byebug
binding.pry で止めたところから
next コマンドで一行ずつステップ実行ができる
pry-doc
Cで書かれたRubyのソースコードを表示出来る
show-sourceコマンドを利用出来る様になる
参考情報
sendメソッドについて
【結論】
・sendメソッドとは、レシーバーの持っている
メソッドを呼び出すメソッド
・第二引数に指定した値を
呼び出したメソッドの引数に渡す事も出来る
・呼び出したいメソッドを変数で指定して
条件分岐させる際などに有用である
【目次】
【本題】
sendメソッドについて
sendメソッドとは、
レシーバーの持っているメソッドを呼び出す為のメソッドです。
#Fruitクラスと、そのインスタンスメソッドのappleを定義する class Fruit def apple puts "2万円です" end end #Fruitクラスのインスタンスを生成する fruit = Fruit.new # レシーバであるfruitが持っているメソッドのappleを呼び出す fruit.send(:apple) => 2万円です
第二引数でメソッドの引数を指定可能
また、sendメソッドは、第二引数に値を指定すると、
呼び出したメソッドの引数に渡す事も出来ます。
#Fruitクラスと、そのインスタンスメソッドのappleを定義する class Fruit def apple(money) puts "#{money}万円です" end end #Fruitクラスのインスタンスを生成する fruit = Fruit.new # レシーバであるfruitが持っているメソッドのappleを呼び出す fruit.send(:apple, 100) => 100万円です
利用シーン
sendメソッドは、呼び出したいメソッドを
変数で指定したい場合に有用です。
class Fruit def apple puts "20円です" end def melon puts "100円です" end end fruit = Fruit.new favorite_color = "red" favorite_food = favorite_color == 'red' ? 'apple' : 'melon' # 変数でメソッドを呼び出す事が可能 fruit.send(favorite_food) => 20円です # 直接メソッドを実行する場合、変数は指定出来ない fruit.favorite_food => NoMethodError: undefined method `favorite_food'
これにより出力したい内容を条件分岐させたり、
コードを共通化させる際に役立ちます。
なお、呼び出すメソッドが決まっている場合は
記述が冗長になるので、利用は控えた方が良いと考えています。
#直接メソッドを実行する場合 fruit.apple => 2万円です # sendメソッドの場合 fruit.send(:apple) => 2万円です
参考情報
with_indexによる繰り返し処理のカウント
【結論】
・with_indexとは、
eachやmapなどで繰り返しの処理をした際、
現時点の繰り返し回数をカウントしてくれる。
・引数を指定する事で、
カウント開始時点の数を任意で決められる
・eachの場合、each_with_indexという
with_indexメソッドの機能が
あらかじめ備わったメソッドも存在する
【目次】
【本題】
繰り返し処理のカウント
eachなどの繰り返し文を実行する際、
処理結果に、何回目の処理だったのかを出力したいケースがあります。
例えば、配列に格納した数字を順に出力して、
それが何番目なのか?を表示させたい場合です。
一例として、下記のように実装することも出来ます。
count = 1 [6, 12, 18].each do |num| puts "#{num}は、#{count}番目です" count += 1 end => 6は、1番目です 12は、2番目です 18は、3番目です
しかし、これでは回数を数える為の
変数「count」を定義して、繰り返す度に
その変数を+1する必要があります。
こういった処理が不要になるのが、「with_index」です
with_indexについて
with_indexとは、eachなどの繰り返し文の
処理回数を記録するインデックスを生成します。
[6, 12, 18].each.with_index do |num, i| puts "#{num}は、#{i}番目です" end => 6は、0番目です 12は、1番目です 18は、2番目です
このようにする事で、わざわざ処理回数を記録する為の
変数を定義せずとも、処理回数を参照することが出来ます。
しかし、上記の場合だと、カウントが「0」から始まっています。
もし、任意の数からカウントを始めたい場合は、
with_indexの引数に指定する必要があります。
[6, 12, 18].each.with_index(1) do |num, i| puts "#{num}は、#{i}番目です" end => 6は、1番目です 12は、2番目です 18は、3番目です
これで任意の数からカウントが可能になります。
each以外でも利用可能
なお、with_indexはEnumeratorクラスのメソッドなので、
each以外にも、mapやselectでも利用可能です。
[6, 12, 18].map.with_index(1) { |num, i| "#{num}は、#{i}番目です" } => ["6は、1番目です", "12は、2番目です", "18は、3番目です"]
each_with_indexについて
なお、eachの場合だと、あらかじめwith_indexの機能が備わった
each_with_indexというメソッドも存在します。
[6, 12, 18].each_with_index do |num, i| puts "#{num}は、#{i}番目です" end => 6は、0番目です 12は、1番目です 18は、2番目です
しかし、こちらはカウント開始時点の数を
指定できないという欠点があります。
参考情報
instance method Enumerator#with_index (Ruby 2.1.0)
CKEditorについて
【結論】
・CKEditorとは、
WYSIWYGエディタの一種
・WYSIWYGエディタは
ユーザーにリッチな編集画面を
提供する為のインターフェイス
・CKEditorは、WYSIWYGエディタの中でも古株で
シンプルな構造から、多く利用されている。
【目次】
【本題】
CKEditorについて
CKEditorとは、WYSIWYGエディタの一種です。
WYSIWYGエディタは
ユーザーにリッチな編集画面を
提供する為のインターフェイスです。
WYSIWYGエディタは、いくつか出回っていますが、
その中でもCKEditorは、軽量で導入が比較的簡単なことから
古くから利用され続けています。
導入方法
CKEditorはgemも出回っているそうですが、
そちらはCarrierWaveしか対応していないのと、
CKEditorバージョン4系しか使えませんでした。
今回、画像アップロードは、shrineを利用したかったので、
gemではなく、npmを使って、CKEditorを導入しました。
ちなみに、CKEditorをCDNで読み込む際は、下記のように
「javascript_include_tag」を使う方法もあります。
<%= javascript_include_tag 'https://cdn.ckeditor.com/ckeditor5/11.2.0/classic/ckeditor.js' %>
http://railsdoc.com/references/javascript_include_tag
実装例
はてなブログなどをされている方であれば馴染み深いと思いますが、
この様に文字のサイズなどを編集できて、
その結果をそのまま見れるという特徴があります
なお、HTML形式でデータベースには保存されます。
<p>はい、いいえ</p> <h1>はい、いいえ</h1> <ul><li>はい</li> <li>いいえ</li></ul> <ol><li>はい</li> <li>いいえ</li></ol> <figure class="image"> <img src="/uploads/image/95/image/standard-61e2ee8deba069f05095e2ba4faf583f.png"> </figure>
削除系メソッド(delete, delete_all, destroy, destroy_all)について
【結論】
・deleteは、指定した条件のレコードに対して
SQLのDELTE文を直接実行する。
その為、関連付けられてるレコードは削除されない。
・destroyは、ActiveRecordを介して
指定した条件のレコードを削除する。
関連付けられてるレコードも削除される。
・_allは、特定の条件に
一致するレコードをまとめて削除する
【目次】
【本題】
削除系メソッドについて
RailsのActiveRecordには、レコードの削除に対応した
メソッドが4種類存在します。
今回は、各メソッドの仕様と
使い分けについて、解説します。
deleteについて
deleteは、指定した条件に合致する
一件のレコードを削除します。
処理の流れとしては、直接SQLのDELTE文を実行します。
なので、モデルは経由しない為、
関連付けたテーブルのレコードは削除されません。
User.find(id: 1).delete
destroyについて
destroyも、指定した条件に合致する
一件のレコードを削除します。
deleteとの違いを上げるとすると、
モデル(ActiveRecord)を介して削除を行う点です。
その為、関連付けさせたモデルに
dependent: :destroyを指定していれば、
そちらのレコードも一緒に削除してくれます。
User.find(1).destroy
delete_allとdestroy_allについて
これらは、deleteやdestroyと違い、
条件に合致する複数のレコードを同時に削除することが可能です。
Post.where(user_id: 1).delete_all Post.where(user_id: 1).destroy_all