Quantcast
Channel: 配列タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 758

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

$
0
0

変換できるのか

誤解を生む可能性がある為はじめに書いておきます。
「配列を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:0x00007fd2621de8e0id: 2,name: "山田">,#<AdminUser:0x00007fd2621de4f8id: 3,name: "佐藤">]
#Arrayを作ります。AdminUser.where(id: [2,3]).select(&:name)=>[#<AdminUser:0x00007fd26231ad80id: 2,name: "山田">,#<AdminUser:0x00007fd26231aa88id: 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:0x00007fd260347a30id: 3,name: "佐藤",#以下省略

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

AdminUser.where(id: [2,3]).select(&:name).order(id: :desc)=>NoMethodError:undefinedmethod`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.コントローラーやモデルに再取得メソッドを作る

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

defself.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
classArraydefto_activerecord_relationreturnApplicationRecord.noneifself.empty?clazzes=self.map(&:class).uniqraise'Array cannot be converted to ActiveRecord::Relation since it does not have same elements'ifclazzes.size>1clazz=clazzes.firstraise'Element class is not ApplicationRecord and as such cannot be converted'unlessclazz.ancestors.include?ApplicationRecordclazz.where(id: self.map(&:id))endend

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


Viewing all articles
Browse latest Browse all 758

Trending Articles