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

GitHub - carrierwaveuploader/carrierwave: Classier solution for file uploads for Rails, Sinatra and other Ruby web frameworks

まず初めに紹介するのは「CarrierWave」です。 かなり古株のファイルアップローダーで、同系統のgemの中でも評価は上位にきています。

使用実績の通り、バリデーションやキャッシュなど一通りの事はこなせます。

Shrine

GitHub - shrinerb/shrine: File Attachment toolkit for Ruby applications

次に紹介するのは、Shrineです。

こちらは、まだ実績の浅いgemですが、必要な機能のpluginだけ選択して読み込ませる事で、動作を軽量化させたり、影響範囲を狭めてデバックを容易にしたりなど、CarrierWaveには無い特徴的な機能を備えています。

ActiveStorage

Active Storage の概要 - Railsガイド

最後は、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インジェクション」の被害を受ける恐れがあります。

それを防ぐ為に、名前付きルートは役立てられています。

参考情報

Rails のルーティング - Railsガイド

ポリモーフィック関連付けについて

【結論】

ポリモーフィック関連付けとは
 ある特定のモデルが他の複数のモデルに属している事を
 一つの記述だけで表す手法

・画像などを一つのモデル(テーブル)で
 集約して管理したい場合に有用

ポリモーフィック関連付けの元となるテーブルの
 外部キーに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の関連付けの記述は一つだけですが
これでuserpost
それぞれに従属している関係を表せます。

また、上記では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_typeimageable_idというカラムが生成されます。

これらによって、どのテーブルのどのレコードと
関連付いてのかを判別する事が可能となります。

参考情報

Active Record の関連付け - Railsガイド

bin/setupの処理内容について

【結論】

・bin/setupでは、下記の処理を行っている。

・gemの依存関係のチェック、インストール 
・データベースの再構築
・古いログと一時ファイルの削除
・サーバーを再起動

【目次】

【本題】

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コマンドを利用出来る様になる

参考情報

Rails で Pry を使う - Qiita

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万円です

参考情報

http://ref.xaio.jp/ruby/classes/object/send

【Ruby on Rails】sendメソッドのいろんな書き方 - Qiita

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)

https://ref.xaio.jp/ruby/classes/enumerable/each_with_index

class Enumerator (Ruby 3.2 リファレンスマニュアル)

CKEditorについて

【結論】

・CKEditorとは、
 WYSIWYGエディタの一種

WYSIWYGエディタは
 ユーザーにリッチな編集画面を
 提供する為のインターフェイス

・CKEditorは、WYSIWYGエディタの中でも古株で
 シンプルな構造から、多く利用されている。

【目次】

【本題】

CKEditorについて

CKEditorとは、WYSIWYGエディタの一種です。

WYSIWYGエディタは
ユーザーにリッチな編集画面を
提供する為のインターフェイスです。

ryoutaku-jo.hatenablog.com

WYSIWYGエディタは、いくつか出回っていますが、
その中でもCKEditorは、軽量で導入が比較的簡単なことから
古くから利用され続けています。

導入方法

CKEditorはgemも出回っているそうですが、
そちらはCarrierWaveしか対応していないのと、
CKEditorバージョン4系しか使えませんでした。

github.com

今回、画像アップロードは、shrineを利用したかったので、
gemではなく、npmを使って、CKEditorを導入しました。

www.npmjs.com

ちなみに、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は、特定の条件に
 一致するレコードをまとめて削除する

【目次】

【本題】

削除系メソッドについて

RailsActiveRecordには、レコードの削除に対応した
メソッドが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