LoginSignup
34
21

Railsで配列をActive Record Relationに変換したい

Last updated at Posted at 2021-01-13

2023/12/6 追記

いいねが多くなってきたので1点追記します。
本記事ではサンプルコードとしてAdminUserというモデルに2回アクセスしています。
パフォーマンス観点では、なるべくクエリ発行は少なくするべきなので、
配列をActive Record Relationに戻す必要が本当にあるのか等、
他の方法がないかよく検討してください。

変換できるのか

誤解を生む可能性がある為はじめに書いておきます。
「配列をActive Record Relationに変換したい」という理由でこの記事を見ていただいている方がいると思いますが、
厳密には配列をActive Record Relationに変換することは出来ません。
ただし、取得した配列を使って、Active Record Relationを取得することは出来ます。

私自身「配列 Active Record Relation 変換」で検索したりしたので、あえてこのようなタイトルにしています。

配列とActive Record Relationについて

はじめに簡単に説明をしますが、分かる方はここまで飛ばしてください。
配列とActive Record Relation、それぞれの例を上げてみます。

#Active Record Relationを作ります。

AdminUser.where(id: [2, 3])
=> [#<AdminUser:0x00007fd2621de8e0
  id: 2,
  name: "山田">,
 #<AdminUser:0x00007fd2621de4f8
  id: 3,
  name: "佐藤">]
#Arrayを作ります。
AdminUser.where(id: [2, 3]).select(&:name)
=> [#<AdminUser:0x00007fd26231ad80
  id: 2,
  name: "山田">,
 #<AdminUser:0x00007fd26231aa88
  id: 3,
  name: "佐藤">]

結果は同じように見えますが、.classをつけてみると違いが分かります。

AdminUser.where(id: [2, 3]).class
=> AdminUser::ActiveRecord_Relation
AdminUser.where(id: [2, 3]).select(&:name).class
=> Array

モデルからデータを取り出す際に、wherefindselectmapなど色々なメソッドを使うことがあると思いますが、Railsでは、使うメソッドによって返り値の型が決まっています。
上記の例の使い方で言うと、whereだとActive Record Relationが返り、selectだとArrayクラスが返ることになります。
※厳密にはselectは使い方によってはActive Record Relationが返ります。

例えばこんな時に困る

Viewに何らかのデータを表示したい。
whereを使ってActive Record Relationにしたはいいが、
そのあとselectでデータの絞り込みなどを行った。
Viewに表示する前に並び替えをしたいので、orderメソッドを使ったら、エラーが発生した。

Railsで開発をする際によく使うorderと言うメソッド。並び替えが出来ます。

#idが大きい順に並び替える
AdminUser.where(id: [2, 3]).order(id: :desc)
=> [#<AdminUser:0x00007fd260347a30
  id: 3,
  name: "佐藤",
  #以下省略

このorderメソッドをArrayクラスに対して使うとこのようなエラーになります。

AdminUser.where(id: [2, 3]).select(&:name).order(id: :desc)
=> NoMethodError: undefined method `order' for #<Array:0x00007fd26027d578>

このような場合に、AdminUser.where(id: [2, 3]).select(&:name)をActive Record Relationに変換したい、という欲望が生まれます。

結論

ここからがタイトルのような欲望を叶える具体的な方法です。
admin_usersというArrayクラスから@admin_usersというActive Record Relationを作りたいなら、こうします。

admin_users = AdminUser.where(id: [2, 3]).select(&:name)
@admin_users = AdminUser.where(id: admin_users.map(&:id))

要はwhere(id: admin_users.map(&:id))をしているだけですが、
何をしているかというと、既にある配列のidだけを取り出して配列を作って、それをwhereの引数に使ってAdminUserから再度データを取得しています。

注意点として、下記があるでしょうか。

  • データの中身の順番が変わる可能性があること
  • データを再取得する処理を行っていること(大量のデータの場合は余計な時間がかかる)

実装方法

この処理を使って実装するにあたって、同じ処理をたくさんするのであれば、スッキリとまとめてしまいたいです。
2つの方法を紹介します。

1. コントローラーやモデルに再取得メソッドを作る
2. Arrayクラスを拡張して再取得メソッドを作る

1.コントローラーやモデルに再取得メソッドを作る

コントローラーやモデルに下記のように書きます。

  def self.get_activerecord_relation(arr)
    where(id: arr.map(&:id))
  end

使い方

admin_users = AdminUser.where(id: [2, 3]).select(&:name) => #Arrayクラス
@admin_users = AdminUser.get_activerecord_relation(admin_users) => #Active Record Relation

ちょっと気持ち悪いかなと思います。

2.Arrayクラスを拡張して再取得メソッドを作る

array.rbというファイルを作ります。

lib/core_ext/array.rb
class Array
  def to_activerecord_relation
    return ApplicationRecord.none if self.empty?

    clazzes = self.map(&:class).uniq
    raise 'Array cannot be converted to ActiveRecord::Relation since it does not have same elements' if clazzes.size > 1

    clazz = clazzes.first
    raise 'Element class is not ApplicationRecord and as such cannot be converted' unless clazz.ancestors.include? ApplicationRecord

    clazz.where(id: self.map(&:id))
  end
end

core_ext.rbというファイルを作ります。

config/initializers/core_ext.rb
require 'core_ext/array'

使い方

admin_users = AdminUser.where(id: [2, 3]).select(&:name) => #Arrayクラス
@admin_users = admin_users.to_activerecord_relation => #Active Record Relation

2の方法は、Rubyのオープンクラスという後からメソッドを追加したり出来る機能を使った方法で、
Arrayクラスに全体に対して、to_activerecord_relationというメソッドが使えるようにしてしまおう、みたいな感じです。

オープンクラスに関しては今回紹介したもの以外には例えば、
Stringクラスにto_boolというメソッドを追加して、"true""false"という文字列が代入されたオブジェクトをboolean型に変換出来るようにする、なども出来ます。

参考にさせていただいた記事

https://qiita.com/shibadai/items/ddbc76a8b980cd8354bc
https://stackoverflow.com/questions/17331862/converting-an-array-of-objects-to-activerecordrelation

34
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
21