zenet_logo

-株式会社ゼネット技術ブログ-

Ruby on Railsの開発で便利だったこと2点

こんにちは。システム事業部の坂本です。普段はRuby on Railsを利用したシステム開発を行っています。

f:id:zenet-tech:20210317101112p:plain

日頃からRuby on Railsシステム開発を行う中で、知っておいて便利だった2つのことをお伝えします。

■DBから重複無しのデータを取得する時に、「select.uniq」ではなく「distinct.pluck」を利用する

DBから重複無しのデータを取得する時は、「distinct.pluck」を利用するのがおすすめです。例えば、以下のような「User」のデータがDBにあると仮定します。

id name age
1 A山B子 26
2 A山B子 28
3 C山D夫 31
4 C山D夫 31
5 E竹F男 24
6 E竹F男 24

この時、モデル.distinct.pluck(:取得したいカラム)と記述すると、ユニークなデータを配列で取得することが出来ます。

例えば、Userのnameを一意に取得する場合、以下のように書くことが出来ます。

User.distinct.pluck(:name)
# => ['A山B子', 'C山D夫', 'E竹F男']

また、カラムを2つ以上指定すれば、2つのカラムを1ペアとした場合にユニークなペアのみを取得することが出来ます。

User.distinct.pluck(:name, :age)
# => [['A山B子', 26], ['A山B子', 28], ['C山D夫', 31], ['E竹F男', 24]]

また、ユニークなデータを配列で取得することが出来る方法としてselect.uniqもありますが、戻り値が異なります。

User.select(:name).uniq
=> #<ActiveRecord::Relation [
#   <User id: nil, name: "A山B子">,
#   <User id: nil, name: "C山D夫">,
#   <User id: nil, name: "E竹F男">
# ]>

上記の通り、2つの方法には以下のような差異があります。

  • distinct.pluckは、文字列・数値などの配列が返ってくる
  • select.uniqは、ActiveRecord::Relationの配列が返ってくる

前者はシンプルに配列として返すだけなので処理は軽くなりますが、後者はActiveRecord::Relationの配列を返すため処理が重くなります。

User.distinct.pluck(:name).first.class
# => String

User.select(:name).uniq.first.class
# => User(id: integer, name: String, age: integer, created_at: datetime, updated_at: datetime)

そのため、1レコードに対してカラムが非常に多いテーブルや、対象となるレコードが非常に多い場合は、パフォーマンスに差が出ます。

## テーブル、Userのレコード構成を確認
User
=> User(id: integer, name: String, age: integer, created_at: datetime, updated_at: datetime)

## テーブル、Userのレコード数は現在10012
User.all.count
   (3.1ms) SELECT COUNT(*) FROM "users"
=> 10012

## 実行速度検証のためrequire
require 'benchmark'

## distinct.pluckの実行速度を検証
result1 = Benchmark.realtime do
   User.distinct.pluck(:id)
end
   (12.0ms) SELECT DISTINCT "users"."id" FROM "users"
=> 0.020277999981772155

## select.uniqの実行速度を検証
result2 = Benchmark.realtime do
  User.select(:id).uniq
end
User Load (6.3ms) SELECT "users"."id" FROM "users"
=> 0.08473200001753867

上記の通り、レコード数が約10000件のみの非常にシンプルな構成のテーブルでも差が出ます。 そのため、もっとカラムが多いテーブル、もっとレコードが多く10万件を超えるようなテーブル等では、2つの処理で大幅に差が出ます。

実際に私も開発を行う中で、パフォーマンスに問題のあったページを改修する際にselect.uniqではなくdistinct.pluckを利用するようにすることで大幅にページ表示速度が向上したことがありました。

本当に1つのカラムのみ必要なケースでは、distinct.pluckを積極的に使っていきましょう。

■オプション「—sandbox」を利用してrails consoleをより便利に利用する

コードの挙動を確認する際などに利用するrails consoleですが、起動時に「--sandbox」(または「-s」)のオプションを指定すると非常に便利です。

参考:Railsガイド | Rails のコマンドラインツール

まず、実際に「rails console --sandbox」でコンソールを実行すると以下のような表示が出ます。

❯❯❯ bundle exec rails c -s
Admin.countLoading development environment in sandbox (Rails 5.2.4.3)
Any modifications you make will be rolled back on exit

Any modifications you make will be rolled back on exitと表示されている通り、 「--sandbox」オプションをつけてrails consoleを起動すると、 コンソールを抜けた後にDBに加えた全ての変更がロールバックされます。

そのため、モデルの挙動を一通り確認したり、大量にデータの更新・作成・削除が行われるバッチ処理の動作確認を行う際に便利です。 端的に言うと、コンソール上で実行したバッチ処理の実装が誤っていて、余分なデータが大量に作成されたり、テーブルのデータが全て削除されても一度コンソールを抜ければ全てデータが元通りになります。

## 「-s」オプションを付けてrails consoleを起動
❯❯❯ bundle exec rails c -s
Admin.countLoading development environment in sandbox (Rails 5.2.4.3)
Any modifications you make will be rolled back on exit
Frame number: 0/16
[1] pry(main)> User.count
   (9.3ms)  SELECT COUNT(*) FROM "users"
=> 100

## 間違えてUserのデータを全削除してしまった
[2] pry(main)> User.delete_all
  User Destroy (0.7ms)  DELETE FROM "users"
=> 100

## exitすると、ROLLBACKが発生する
[3] pry(main)> exit
   (0.7ms)  ROLLBACK

## もう一度、「-s」オプションを付けてrails consoleを起動
❯❯❯ bundle exec rails c -s
Loading development environment in sandbox (Rails 5.2.4.3)
Any modifications you make will be rolled back on exit
Frame number: 0/16

## 一度delete_allしたが、削除されていない。
[1] pry(main)> User.count
   (1.4ms)  SELECT COUNT(*) FROM "users"
=> 100

そのため重い処理の動作確認を行う際には、「--sandbox」オプションを利用して確認しておきたいですね。


短いですが、私がRailsで開発を行っている中で便利だと思ったことを2つご紹介しました。

Ruby on Railsで使えるメソッドやRubyで使えるメソッドは沢山知っておいて損は無いので、沢山覚えて自分のものにしていきましょう!