Chuyển đổi một mảng đối tượng thành ActiveRecord :: Relation


104

Tôi có một mảng các đối tượng, hãy gọi nó là một Indicator. Tôi muốn chạy các phương thức lớp Indicator (những phương thức def self.subjectsđa dạng, phạm vi, v.v.) trên mảng này. Cách duy nhất tôi biết để chạy các phương thức lớp trên một nhóm đối tượng là đặt chúng là một ActiveRecord :: Relation. Vì vậy, tôi kết thúc bằng cách thêm một to_indicatorsphương thức vào Array.

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

Đôi khi tôi xâu chuỗi khá nhiều phạm vi này để lọc ra kết quả, trong các phương thức của lớp. Vì vậy, mặc dù tôi gọi một phương thức trên ActiveRecord :: Relation, tôi không biết cách truy cập đối tượng đó. Tôi chỉ có thể xem nội dung của nó thông qua all. Nhưng alllà một mảng. Vì vậy, sau đó tôi phải chuyển đổi mảng đó thành ActiveRecord :: Relation. Ví dụ, đây là một phần của một trong các phương pháp:

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

Tôi đoán điều này ngưng tụ thành hai câu hỏi.

  1. Làm cách nào để chuyển đổi một Mảng đối tượng thành ActiveRecord :: Relation? Tốt hơn là không làm wheremỗi lần.
  2. Khi chạy một def self.subjectsphương thức kiểu trên ActiveRecord :: Relation, làm cách nào để truy cập vào chính đối tượng ActiveRecord :: Relation đó?

Cảm ơn. Nếu tôi cần làm rõ bất cứ điều gì, hãy cho tôi biết.


3
Nếu lý do duy nhất của bạn để cố gắng chuyển đổi mảng đó trở lại một mối quan hệ là vì bạn đã hiểu nó qua .all, chỉ cần sử dụng .scopednhư câu trả lời của Andrew Marshall đã chỉ ra (Mặc dù trong rails 4, nó sẽ hoạt động với .all). Nếu bạn thấy mình cần phải biến một mảng vào một mối quan hệ bạn đã ở đâu đó sai ra đi ...
nzifnab

Câu trả lời:


46

Làm cách nào để chuyển đổi một Mảng đối tượng thành ActiveRecord :: Relation? Tốt hơn là không làm ở đâu mỗi lần.

Bạn không thể chuyển đổi một Mảng thành một ActiveRecord :: Relation vì một Relation chỉ là một trình tạo cho một truy vấn SQL và các phương thức của nó không hoạt động trên dữ liệu thực tế.

Tuy nhiên, nếu những gì bạn muốn là một mối quan hệ thì:

  • đối với ActiveRecord 3.x, không gọi allvà thay vào đó gọi scoped, điều này sẽ trả lại một Quan hệ đại diện cho cùng các bản ghi allsẽ cung cấp cho bạn trong một Mảng.

  • đối với ActiveRecord 4.x, chỉ cần gọi all, trả về một Quan hệ.

Khi chạy một def self.subjectsphương thức kiểu trên ActiveRecord :: Relation, làm cách nào để truy cập vào chính đối tượng ActiveRecord :: Relation đó?

Khi phương thức được gọi trên một đối tượng Relation, selflà quan hệ (trái ngược với lớp mô hình mà nó được định nghĩa).


1
Xem @Marco Prins bên dưới về giải pháp.
Justin

những gì về phương pháp NỮA .push trong đường ray 5
Jaswinder

@GstjiSaini Tôi không chắc về phương pháp chính xác mà bạn đang đề cập đến, vui lòng cung cấp liên kết tài liệu hoặc nguồn. Mặc dù vậy, nếu nó không được dùng nữa, nó không phải là một giải pháp khả thi vì nó có thể sẽ sớm biến mất.
Andrew Marshall

Và đó là lý do tại sao bạn có thể làm class User; def self.somewhere; where(location: "somewhere"); end; end, sau đóUser.limit(5).somewhere
Dorian

Đây là câu trả lời duy nhất thực sự khai sáng OP. Câu hỏi tiết lộ kiến ​​thức thiếu sót về cách hoạt động của ActiveRecord. @Justin chỉ đang củng cố sự thiếu hiểu biết về lý do tại sao thật tệ khi tiếp tục chuyển các mảng đối tượng xung quanh chỉ để ánh xạ lên chúng và xây dựng một truy vấn không cần thiết khác.
james2m

161

Bạn có thể chuyển đổi một mảng các đối tượng arrthành ActiveRecord :: Relation như thế này (giả sử bạn biết các đối tượng là lớp nào, bạn có thể làm như vậy)

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

whereTuy nhiên, bạn phải sử dụng , đó là một công cụ hữu ích mà bạn không nên miễn cưỡng sử dụng. Và bây giờ bạn có một lớp lót chuyển đổi một mảng thành một quan hệ.

map(&:id)sẽ chuyển mảng đối tượng của bạn thành mảng chỉ chứa id của chúng. Và chuyển một mảng đến mệnh đề where sẽ tạo ra một câu lệnh SQL có INdạng như sau:

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

Hãy nhớ rằng thứ tự của mảng sẽ bị mất - Nhưng vì mục tiêu của bạn chỉ là chạy một phương thức lớp trên tập hợp các đối tượng này, tôi cho rằng nó sẽ không có vấn đề gì.


3
Làm tốt, chính xác những gì tôi cần. Bạn có thể sử dụng điều này cho bất kỳ thuộc tính nào trên mô hình. hoạt động hoàn hảo cho tôi.
nfriend21

7
Tại sao phải tự xây dựng SQL theo nghĩa đen khi bạn có thể làm được where(id: arr.map(&:id))? Và nói một cách chính xác, điều này không chuyển đổi mảng thành một quan hệ, nhưng thay vào đó, nhận các phiên bản mới của các đối tượng (khi mối quan hệ được nhận ra) với những ID đó có thể có các giá trị thuộc tính khác với các phiên bản đã có trong bộ nhớ trong mảng.
Andrew Marshall,

7
Tuy nhiên, điều này làm mất thứ tự.
Velizar Hristov

1
@VelizarHristov Đó là bởi vì nó bây giờ là một quan hệ, chỉ có thể được sắp xếp theo một cột chứ không phải theo bất kỳ cách nào bạn muốn. Các mối quan hệ xử lý các tập dữ liệu lớn nhanh hơn và sẽ có một số sự đánh đổi.
Marco Prins

8
Rất kém hiệu quả! Bạn đã biến một tập hợp các đối tượng bạn đã có trong bộ nhớ thành những đối tượng mà bạn sẽ thực hiện truy vấn cơ sở dữ liệu để truy cập. Tôi sẽ xem xét việc cấu trúc lại các phương thức lớp mà bạn muốn lặp qua mảng.
james2m

5

Trong trường hợp của tôi, tôi cần chuyển đổi một mảng đối tượng thành ActiveRecord :: Relation cũng như sắp xếp chúng với một cột cụ thể (ví dụ: id). Vì tôi đang sử dụng MySQL nên hàm trường có thể hữu ích.

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

SQL trông giống như:

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

Hàm trường MySQL


Đối với một phiên bản của này làm việc với PostgreSQL, nhìn không xa hơn chủ đề này: stackoverflow.com/questions/1309624/...
armchairdj

0

ActiveRecord::Relation ràng buộc truy vấn cơ sở dữ liệu lấy dữ liệu từ cơ sở dữ liệu.

Giả sử cho hợp lý, Chúng ta có mảng với các đối tượng của cùng một lớp, sau đó chúng ta giả sử truy vấn để ràng buộc chúng với truy vấn nào?

Khi tôi chạy,

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

Ở trên, đối tượng userstrả về Relationnhưng liên kết truy vấn cơ sở dữ liệu đằng sau nó và bạn có thể xem nó,

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

Vì vậy, không thể trả về ActiveRecord::Relationtừ mảng đối tượng độc lập với truy vấn sql.


0

Trước hết, đây KHÔNG phải là một viên đạn bạc. Theo kinh nghiệm của mình, tôi thấy rằng chuyển đổi sang quan hệ đôi khi dễ dàng hơn các lựa chọn thay thế. Tôi cố gắng sử dụng cách tiếp cận này rất ít và chỉ trong những trường hợp mà phương pháp thay thế sẽ phức tạp hơn.

Điều đó đang được nói ở đây là giải pháp của tôi, tôi đã mở rộng Arraylớp học

# 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

Một ví dụ sử dụng sẽ là array.to_activerecord_relation.update_all(status: 'finished'). Bây giờ tôi phải sử dụng nó ở đâu?

Đôi khi bạn cần phải lọc ra, ActiveRecord::Relationchẳng hạn như loại bỏ các phần tử chưa hoàn thành. Trong những trường hợp đó, tốt nhất là sử dụng phạm vi elements.not_finishedvà bạn sẽ vẫn giữ ActiveRecord::Relation.

Nhưng đôi khi điều kiện đó phức tạp hơn. Loại bỏ tất cả các yếu tố chưa hoàn thiện và đã được sản xuất trong 4 tuần qua và đã được kiểm tra. Để tránh tạo phạm vi mới, bạn có thể lọc thành một mảng và sau đó chuyển đổi trở lại. Hãy nhớ rằng bạn vẫn thực hiện một truy vấn tới DB, nhanh chóng vì nó tìm kiếm theo idnhưng vẫn là một truy vấn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.