Mengonversi larik objek menjadi ActiveRecord :: Relation

104

Saya memiliki array objek, sebut saja itu Indicator. Saya ingin menjalankan metode kelas Indikator (yang dari def self.subjectsvarietas, cakupan, dll) pada larik ini. Satu-satunya cara yang saya tahu untuk menjalankan metode kelas pada sekelompok objek adalah membuatnya menjadi ActiveRecord :: Relation. Jadi saya akhirnya beralih ke to_indicatorsmetode Array.

def to_indicators
  # TODO: Make this less terrible.
  Indicator.where id: self.pluck(:id)
end

Kadang-kadang saya merangkai beberapa cakupan ini untuk menyaring hasil, dalam metode kelas. Jadi, meskipun saya memanggil metode pada ActiveRecord :: Relation, saya tidak tahu cara mengakses objek itu. Saya hanya bisa mendapatkan isinya melalui all. Tapi alladalah Array. Jadi saya harus mengubah array itu menjadi ActiveRecord :: Relation. Misalnya, ini adalah bagian dari salah satu metode:

all.to_indicators.applicable_for_bank(id).each do |indicator|
  total += indicator.residual_risk_for(id)
  indicator_count += 1 if indicator.completed_by?(id)
end

Saya kira ini diringkas menjadi dua pertanyaan.

  1. Bagaimana cara mengubah Array objek menjadi ActiveRecord :: Relation? Lebih disukai tanpa melakukan wheresetiap waktu.
  2. Saat menjalankan def self.subjectsmetode tipe pada ActiveRecord :: Relation, bagaimana cara mengakses objek ActiveRecord :: Relation itu sendiri?

Terima kasih. Jika saya perlu mengklarifikasi sesuatu, beri tahu saya.

Nathan
sumber
3
Jika satu-satunya alasan Anda mencoba mengonversi array itu kembali ke relasi adalah karena Anda mendapatkannya lewat .all, gunakan saja .scopedseperti yang ditunjukkan oleh jawaban Andrew Marshall (Meskipun di rel 4 itu akan berhasil .all). Jika Anda merasa perlu mengubah array menjadi relasi yang salah di suatu tempat ...
nzifnab

Jawaban:

46

Bagaimana cara mengubah Array objek menjadi ActiveRecord :: Relation? Lebih disukai tanpa melakukan suatu tempat setiap saat.

Anda tidak dapat mengonversi Array menjadi ActiveRecord :: Relation karena Relation hanyalah pembuat kueri SQL dan metodenya tidak beroperasi pada data aktual.

Namun, jika yang Anda inginkan adalah relasi, maka:

  • untuk ActiveRecord 3.x, jangan panggil alldan sebaliknya panggil scoped, yang akan mengembalikan Relasi yang mewakili rekaman yang sama yang allakan diberikan kepada Anda dalam Array.

  • untuk ActiveRecord 4.x, cukup panggil all, yang mengembalikan Relasi.

Saat menjalankan def self.subjectsmetode tipe pada ActiveRecord :: Relation, bagaimana cara mengakses objek ActiveRecord :: Relation itu sendiri?

Ketika metode dipanggil pada objek Relasi, selfadalah relasinya (sebagai lawan dari kelas model yang didefinisikannya).

Andrew Marshall
sumber
1
Lihat @Marco Prins di bawah tentang solusinya.
Justin
bagaimana dengan metode .push deprecated in rail 5
Jaswinder
@GstjiSaini Saya tidak yakin dengan metode persis yang Anda maksud, berikan tautan dokumen atau sumber. Padahal, jika sudah usang, bukan solusi yang sangat layak karena kemungkinan akan segera hilang.
Andrew Marshall
Dan itulah mengapa Anda dapat melakukan class User; def self.somewhere; where(location: "somewhere"); end; end, makaUser.limit(5).somewhere
Dorian
Ini adalah satu-satunya jawaban yang benar-benar mencerahkan OP. Pertanyaan ini mengungkapkan pengetahuan yang cacat tentang cara kerja ActiveRecord. @Justin hanya memperkuat kurangnya pemahaman tentang mengapa buruk untuk terus melewati array objek di sekitar hanya untuk memetakannya dan membuat kueri lain yang tidak perlu.
james2m
162

Anda dapat mengonversi larik objek arrmenjadi ActiveRecord :: Relation seperti ini (dengan asumsi Anda mengetahui kelas objek tersebut, yang mungkin Anda lakukan)

MyModel.where(id: arr.map(&:id))

Anda harus menggunakannya where, itu adalah alat yang berguna yang tidak boleh enggan Anda gunakan. Dan sekarang Anda memiliki satu baris yang mengonversi array menjadi relasi.

map(&:id)akan mengubah array objek Anda menjadi array yang hanya berisi id mereka. Dan meneruskan array ke klausa where akan menghasilkan pernyataan SQL INyang terlihat seperti:

SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....

Perlu diingat bahwa pengurutan array akan hilang - Tetapi karena tujuan Anda hanya menjalankan metode kelas pada koleksi objek-objek ini, saya berasumsi itu tidak akan menjadi masalah.

Marco Prins
sumber
3
Bagus, apa yang saya butuhkan. Anda dapat menggunakan ini untuk atribut apa pun pada model. bekerja dengan sempurna untuk saya.
nfriend21
7
Mengapa membangun SQL literal sendiri jika Anda bisa melakukannya where(id: arr.map(&:id))? Dan secara tegas, ini tidak mengubah array menjadi relasi, tetapi mendapatkan instance baru dari objek (setelah relasinya terwujud) dengan ID yang mungkin memiliki nilai atribut berbeda dari instance yang sudah ada di memori dalam array.
Andrew Marshall
7
Ini akan menghilangkan urutannya.
Velizar Hristov
1
@VelizarHristov Itu karena sekarang relasi, yang hanya bisa diurutkan berdasarkan kolom, dan bukan dengan cara yang Anda inginkan. Relasi lebih cepat menangani kumpulan data besar, dan akan ada beberapa trade-off.
Marco Prins
8
Sangat tidak efisien! Anda telah mengubah kumpulan objek yang sudah Anda miliki di memori menjadi objek yang akan Anda lakukan kueri database untuk diakses. Saya akan melihat refactoring metode kelas yang Anda ingin iterasi melalui array.
james2m
5

Nah, dalam kasus saya, saya perlu mengubah array objek ke ActiveRecord :: Relation serta mengurutkannya dengan kolom tertentu (id misalnya). Karena saya menggunakan MySQL, fungsi bidang dapat membantu.

MyModel.where('id in (?)',ids).order("field(id,#{ids.join(",")})") 

SQL terlihat seperti:

SELECT ... FROM ... WHERE (id in (11,5,6,7,8,9,10))  
ORDER BY field(id,11,5,6,7,8,9,10)

Fungsi bidang MySQL

Xingcheng Xia
sumber
Untuk versi ini yang bekerja dengan PostgreSQL, tidak terlihat lagi dari utas ini: stackoverflow.com/questions/1309624/…
armchairdj
0

ActiveRecord::Relation mengikat query database yang mengambil data dari database.

Anggaplah masuk akal, Kami memiliki array dengan objek dari kelas yang sama, lalu dengan kueri mana kami seharusnya mengikatnya?

Saat aku lari,

users = User.where(id: [1,3,4,5])
  User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc

Di sini, di atas, userskembalikan Relationobjek tetapi mengikat kueri database di belakangnya dan Anda dapat melihatnya,

users.to_sql
 => "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc"

Jadi tidak mungkin untuk kembali ActiveRecord::Relationdari larik objek yang tidak bergantung pada kueri sql.

sinar
sumber
0

Pertama-tama, ini BUKAN peluru perak. Dari pengalaman saya, saya menemukan bahwa mengubah menjadi relasi terkadang lebih mudah daripada alternatif. Saya mencoba menggunakan pendekatan ini dengan sangat hemat dan hanya dalam kasus di mana alternatifnya akan lebih kompleks.

Yang dikatakan di sini adalah solusi saya, saya telah memperpanjang Arraykelas

# lib/core_ext/array.rb

class Array

  def to_activerecord_relation
    return ApplicationRecord.none if self.empty?

    clazzes = self.collect(&: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.collect(&:id))
  end
end

Contoh penggunaannya adalah array.to_activerecord_relation.update_all(status: 'finished'). Sekarang di mana saya menggunakannya?

Terkadang Anda perlu memfilter ActiveRecord::Relationmisalnya mengambil elemen yang belum selesai. Dalam kasus tersebut yang terbaik adalah menggunakan ruang lingkup elements.not_finisheddan Anda akan tetap menyimpannya ActiveRecord::Relation.

Namun terkadang kondisi tersebut lebih kompleks. Keluarkan semua elemen yang belum selesai, dan yang telah diproduksi dalam 4 minggu terakhir dan telah diperiksa. Untuk menghindari membuat cakupan baru, Anda bisa memfilter ke array lalu mengonversinya kembali. Ingatlah bahwa Anda masih melakukan kueri ke DB, cepat karena DB mencari berdasarkan idtetapi masih kueri.

Haris Krajina
sumber