こんにちは。システム事業部の坂本です。普段はRuby on Railsを利用したシステム開発を行っています。
日頃から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で使えるメソッドは沢山知っておいて損は無いので、沢山覚えて自分のものにしていきましょう!