การแปลงอาร์เรย์ของวัตถุเป็น ActiveRecord :: Relation


105

ฉันมีอาร์เรย์ของวัตถุเรียกมันว่าไฟล์Indicator. ฉันต้องการเรียกใช้เมธอดคลาส Indicator (พวกdef self.subjectsความหลากหลายขอบเขต ฯลฯ ) บนอาร์เรย์นี้ วิธีเดียวที่ฉันรู้ในการเรียกใช้เมธอดคลาสกับกลุ่มของอ็อบเจ็กต์คือการกำหนดให้เป็น ActiveRecord :: Relation ดังนั้นผมจึงจบลงด้วยการหันไปเพิ่มวิธีการในการto_indicatorsArray

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

ในบางครั้งฉันเชื่อมโยงขอบเขตเหล่านี้ไว้เล็กน้อยเพื่อกรองผลลัพธ์ลงในวิธีการคลาส แม้ว่าฉันจะเรียกเมธอดบน ActiveRecord :: Relation แต่ฉันก็ไม่รู้วิธีเข้าถึงวัตถุนั้น ฉันสามารถเข้าถึงเนื้อหาของมันได้allเท่านั้น แต่allเป็น Array. ดังนั้นฉันต้องแปลงอาร์เรย์นั้นเป็น ActiveRecord :: Relation ตัวอย่างเช่นนี่เป็นส่วนหนึ่งของหนึ่งในวิธีการ:

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

ฉันเดาว่าสิ่งนี้ย่อมาจากคำถามสองข้อ

  1. ฉันจะแปลงอาร์เรย์ของวัตถุเป็น ActiveRecord :: Relation ได้อย่างไร โดยเฉพาะอย่างยิ่งโดยไม่ต้องทำwhereแต่ละครั้ง
  2. เมื่อเรียกใช้def self.subjectsเมธอด type บน ActiveRecord :: Relation ฉันจะเข้าถึงอ็อบเจ็กต์ ActiveRecord :: Relation นั้นได้อย่างไร

ขอบคุณ. หากต้องการชี้แจงอะไรโปรดแจ้งให้เราทราบ


3
หากเหตุผลเดียวของคุณในการพยายามแปลงอาร์เรย์นั้นกลับไปเป็นรีเลชันนั้นเป็นเพราะคุณได้รับมันผ่านทาง.allให้ใช้.scopedตามที่คำตอบของ Andrew Marshall ระบุไว้ (แม้ว่าในราง 4 จะใช้งานได้.all) หากคุณพบว่าตัวเองจำเป็นต้องเปลี่ยนอาร์เรย์ให้เป็นความสัมพันธ์คุณผิดพลาดที่ไหนสักแห่ง ...
nzifnab

คำตอบ:


46

ฉันจะแปลงอาร์เรย์ของวัตถุเป็น ActiveRecord :: Relation ได้อย่างไร โดยเฉพาะอย่างยิ่งโดยไม่ต้องทำในแต่ละครั้ง

คุณไม่สามารถแปลง Array เป็น ActiveRecord :: Relation ได้เนื่องจาก Relation เป็นเพียงตัวสร้างสำหรับแบบสอบถาม SQL และวิธีการของมันจะไม่ทำงานกับข้อมูลจริง

อย่างไรก็ตามหากสิ่งที่คุณต้องการคือความสัมพันธ์:

  • สำหรับ ActiveRecord 3.x อย่าโทรallและโทรแทนscopedซึ่งจะให้ Relation กลับมาซึ่งแสดงถึงระเบียนเดียวกันกับที่allจะให้คุณใน Array

  • สำหรับ ActiveRecord 4.x เพียงแค่เรียกallซึ่งส่งคืน Relation

เมื่อเรียกใช้def self.subjectsเมธอด type บน ActiveRecord :: Relation ฉันจะเข้าถึงอ็อบเจ็กต์ ActiveRecord :: Relation นั้นได้อย่างไร

เมื่อเมธอดถูกเรียกใช้บนวัตถุ Relation ความสัมพันธ์selfคือความสัมพันธ์ (ตรงข้ามกับคลาสโมเดลที่กำหนดไว้)


1
ดู @Marco Prins ด้านล่างเกี่ยวกับวิธีแก้ปัญหา
จัสติ

สิ่งที่เกี่ยวกับวิธีการผลักดันที่เลิกใช้ในราง 5
Jaswinder

@GstjiSaini ฉันไม่แน่ใจเกี่ยวกับวิธีการที่แน่นอนที่คุณอ้างถึงโปรดระบุเอกสารหรือลิงก์แหล่งที่มา แม้ว่าจะเลิกใช้งานแล้วก็ไม่ใช่วิธีแก้ปัญหาที่เป็นไปได้มากนักเนื่องจากมีแนวโน้มว่าจะหายไปในไม่ช้า
Andrew Marshall

และนั่นเป็นเหตุผลที่คุณสามารถทำได้class User; def self.somewhere; where(location: "somewhere"); end; endแล้วUser.limit(5).somewhere
Dorian

นี่เป็นคำตอบเดียวที่ทำให้ OP ได้กระจ่างขึ้นอย่างแท้จริง คำถามแสดงให้เห็นถึงความรู้ที่บกพร่องเกี่ยวกับวิธีการทำงานของ ActiveRecord @ จัสตินกำลังตอกย้ำความไม่เข้าใจว่าเหตุใดจึงไม่ดีที่จะส่งอาร์เรย์ของวัตถุไปรอบ ๆ เพื่อแมปและสร้างแบบสอบถามที่ไม่จำเป็นอีก
james2m

162

คุณสามารถแปลงอาร์เรย์ของอ็อบเจ็กต์arrเป็น ActiveRecord :: Relation ได้เช่นนี้ (สมมติว่าคุณรู้ว่าคลาสใดเป็นอ็อบเจ็กต์ซึ่งคุณอาจทำ)

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

คุณต้องใช้whereมันเป็นเครื่องมือที่มีประโยชน์ซึ่งคุณไม่ควรลังเลที่จะใช้ และตอนนี้คุณมีซับเดียวที่แปลงอาร์เรย์เป็นความสัมพันธ์

map(&:id)จะเปลี่ยนอาร์เรย์ของวัตถุของคุณให้เป็นอาร์เรย์ที่มีเฉพาะ id และส่งอาร์เรย์ไปยังโดยที่ clause จะสร้างคำสั่ง SQL โดยมีINลักษณะดังนี้:

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

โปรดทราบว่าลำดับของอาร์เรย์จะหายไป - แต่เนื่องจากวัตถุประสงค์ของคุณคือเรียกใช้เมธอดคลาสในคอลเล็กชันของอ็อบเจ็กต์เหล่านี้เท่านั้นฉันคิดว่ามันจะไม่มีปัญหา


3
ทำได้ดีสิ่งที่ฉันต้องการ คุณสามารถใช้สิ่งนี้สำหรับแอตทริบิวต์ใด ๆ ในโมเดล ทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน
nfriend21

7
ทำไมต้องสร้างตัวอักษร SQL ด้วยตัวเองในเมื่อคุณทำได้where(id: arr.map(&:id))? และพูดอย่างเคร่งครัดสิ่งนี้ไม่ได้แปลงอาร์เรย์เป็นความสัมพันธ์ แต่จะได้รับอินสแตนซ์ใหม่ของอ็อบเจ็กต์แทน (เมื่อทราบความสัมพันธ์แล้ว) ด้วย ID เหล่านั้นซึ่งอาจมีค่าแอตทริบิวต์ที่แตกต่างจากอินสแตนซ์ในหน่วยความจำที่มีอยู่แล้วในอาร์เรย์
Andrew Marshall

7
สิ่งนี้จะสูญเสียการสั่งซื้อแม้ว่า
Velizar Hristov

1
@VelizarHristov นั่นเป็นเพราะตอนนี้มันเป็นความสัมพันธ์ซึ่งสามารถเรียงลำดับได้ด้วยคอลัมน์เท่านั้นไม่ใช่วิธีที่คุณต้องการ ความสัมพันธ์เร็วกว่าในการจัดการกับชุดข้อมูลขนาดใหญ่และจะมีการแลกเปลี่ยนบางอย่าง
Marco Prins

8
ไร้ประสิทธิภาพมาก! คุณได้เปลี่ยนคอลเล็กชันของวัตถุที่คุณมีอยู่แล้วในหน่วยความจำให้เป็นวัตถุที่คุณกำลังจะทำแบบสอบถามฐานข้อมูลเพื่อเข้าถึง ฉันจะดูการปรับโครงสร้างเมธอดคลาสเหล่านั้นที่คุณต้องการทำซ้ำบนอาร์เรย์
james2m

5

ในกรณีของฉันฉันต้องแปลงอาร์เรย์ของวัตถุเป็น ActiveRecord :: Relationรวมทั้งจัดเรียงด้วยคอลัมน์เฉพาะ (เช่น id) เนื่องจากฉันใช้ MySQL ฟังก์ชันฟิลด์จึงมีประโยชน์

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

SQL ดูเหมือนว่า:

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

ฟังก์ชันฟิลด์ MySQL


สำหรับเวอร์ชันนี้ที่ใช้งานได้กับ PostgreSQL ไม่ต้องมองไปไกลกว่าเธรดนี้: stackoverflow.com/questions/1309624/…
armchairdj

0

ActiveRecord::Relation ผูกคิวรีฐานข้อมูลซึ่งดึงข้อมูลจากฐานข้อมูล

สมมติว่าเข้าท่าเรามีอาร์เรย์ที่มีอ็อบเจ็กต์คลาสเดียวกันแล้วเราคิดว่าจะผูกกับแบบสอบถามใด

เมื่อฉันวิ่ง

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

ด้านบนusersส่งคืนRelationวัตถุ แต่ผูกการสืบค้นฐานข้อมูลไว้ด้านหลังและคุณสามารถดูได้

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

ดังนั้นจึงไม่สามารถกลับActiveRecord::Relationจากอาร์เรย์ของวัตถุที่ไม่ขึ้นอยู่กับแบบสอบถาม sql


0

ก่อนอื่นนี่ไม่ใช่กระสุนเงิน จากประสบการณ์ของฉันฉันพบว่าการแปลงความสัมพันธ์บางครั้งง่ายกว่าทางเลือกอื่น ฉันพยายามใช้แนวทางนี้เท่าที่จำเป็นและเฉพาะในกรณีที่ทางเลือกนั้นซับซ้อนกว่า

ที่พูดนี่คือทางออกของฉันฉันได้ขยายArrayชั้นเรียนแล้ว

# 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

array.to_activerecord_relation.update_all(status: 'finished')ตัวอย่างการใช้งานจะเป็น ตอนนี้ฉันจะใช้มันได้ที่ไหน?

บางครั้งคุณต้องกรองออกActiveRecord::Relationเช่นนำองค์ประกอบที่ไม่สมบูรณ์ออก ในกรณีดังกล่าวที่ดีที่สุดคือการใช้ขอบเขตและคุณยังจะเก็บelements.not_finishedActiveRecord::Relation

แต่บางครั้งเงื่อนไขนั้นก็ซับซ้อนกว่า นำองค์ประกอบทั้งหมดที่ยังไม่เสร็จและที่ผลิตในช่วง 4 สัปดาห์ที่ผ่านมาและได้รับการตรวจสอบ เพื่อหลีกเลี่ยงการสร้างขอบเขตใหม่คุณสามารถกรองอาร์เรย์แล้วแปลงกลับได้ โปรดทราบว่าคุณยังคงทำการสืบค้นไปยังฐานข้อมูลอย่างรวดเร็วเนื่องจากค้นหาโดยidแต่ยังคงเป็นแบบสอบถาม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.