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

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

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

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




## 配列とActive Record Relationについて
はじめに簡単に説明をしますが、分かる方は[ここまで](https://qiita.com/fgem28/items/25e25d400f2ce21f4235#%E7%B5%90%E8%AB%96)飛ばしてください。
配列とActive Record Relation、それぞれの例を上げてみます。

```ruby
#Active Record Relationを作ります。

AdminUser.where(id: [2, 3])
=> [#<AdminUser:0x00007fd2621de8e0
  id: 2,
  name: "山田">,
 #<AdminUser:0x00007fd2621de4f8
  id: 3,
  name: "佐藤">]
```

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

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

```ruby
AdminUser.where(id: [2, 3]).class
=> AdminUser::ActiveRecord_Relation
```

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

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

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

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

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

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

```ruby
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を作りたいなら、こうします。

```ruby
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.コントローラーやモデルに再取得メソッドを作る
コントローラーやモデルに下記のように書きます。

```ruby
  def self.get_activerecord_relation(arr)
    where(id: arr.map(&:id))
  end
```
使い方

```ruby
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'
```

使い方

```ruby
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