190

Có một thuộc tính năng động tiện dụng trong hoạt động kỷ lục gọi find_or_create_by:Rails find_or_create bởi nhiều hơn một thuộc tính?

Model.find_or_create_by_<attribute>(:<attribute> => "")

Nhưng nếu tôi cần phải find_or_create bởi nhiều hơn một thuộc tính?

Giả sử tôi có một mô hình để xử lý mối quan hệ M: M giữa Nhóm và Thành viên được gọi là GroupMember. Tôi có thể có nhiều trường hợp trong đó member_id = 4, nhưng tôi không bao giờ muốn nhiều hơn một lần trong đó member_id = 4 và group_id = 7. Tôi đang cố gắng tìm ra nếu có thể làm như sau:

GroupMember.find_or_create(:member_id => 4, :group_id => 7) 

Tôi nhận ra có thể có những cách tốt hơn để xử lý việc này, nhưng tôi thích sự tiện lợi của ý tưởng về find_or_create.

Trả lời

443

Nhiều thuộc tính có thể được kết nối với một and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7) 

(sử dụng find_or_initialize_by nếu bạn không muốn lưu các bản ghi ngay lập tức)

Edit: Các phương pháp trên bị phản đối trong Đường ray 4. Cách mới để làm điều đó sẽ là:

GroupMember.where(:member_id => 4, :group_id => 7).first_or_create 

GroupMember.where(:member_id => 4, :group_id => 7).first_or_initialize 

Chỉnh sửa 2: Không phải tất cả các yếu tố này đều nằm ngoài đường ray.

https://github.com/rails/rails/blob/4-2-stable/guides/source/active_record_querying.md

Ví dụ

GroupMember.find_or_create_by_member_id_and_group_id(4, 7) 

trở thành

GroupMember.find_or_create_by(member_id: 4, group_id: 7) 
+45

Cảm ơn Rails 4 cập nhật. Tôi thích nó;) – tybro0103

+0

Bạn cũng có thể thích https://github.com/seamusabshere/upsert - đối số đầu tiên là một băm thuộc tính được sử dụng để tìm hoặc tạo bản ghi –

+1

Thật hữu ích khi lưu ý rằng khởi tạo cuộc gọi tạo() thay vì new() có thể được gọi trong trường hợp first_or_create –

29

Đối với bất kỳ ai khác tình cờ qua thread này nhưng cần phải tìm hoặc tạo một đối tượng với các thuộc tính mà có thể thay đổi tùy theo hoàn cảnh , thêm phương pháp sau vào mô hình của bạn:

# Return the first object which matches the attributes hash 
# - or - 
# Create new object with the given attributes 
# 
def self.find_or_create(attributes) 
    Model.where(attributes).first || Model.create(attributes) 
end 

Mẹo tối ưu hóa: bất kể bạn chọn giải pháp nào, hãy xem xét thêm chỉ mục cho thuộc tính bạn truy vấn thường xuyên nhất.

+8

Một điều cần lưu ý là điều này sẽ không xử lý các thuộc tính không thể là khối lượng được chỉ định, trong khi 'find_or_create_by' sẽ. – x1a4

+1

Marco, hãy thử 'Model.where (thuộc tính) .instance_eval {| q | q.first || q.create} '. – hiroshi

+3

'' 'find_or_create''' cũng có lợi ích của việc sử dụng một giao dịch, trong đó' '' ở đâu… .đầu tiên || create''' giới thiệu một điều kiện chủng tộc – mrm

12

Bằng cách chuyển khối tới find_or_create, bạn có thể chuyển các tham số bổ sung sẽ được thêm vào đối tượng nếu đối tượng được tạo mới. Điều này rất hữu ích nếu bạn đang xác nhận sự hiện diện của một trường mà bạn không tìm kiếm.

Giả sử:

class GroupMember < ActiveRecord::Base 
    validates_presence_of :name 
end 

sau đó

GroupMember.where(:member_id => 4, :group_id => 7).first_or_create { |gm| gm.name = "John Doe" } 

sẽ tạo ra một GroupMember mới với cái tên "John Doe" nếu nó không tìm thấy một với member_id 4and group_id 7

25

Trong Rails 4 bạn có thể làm:

GroupMember.find_or_create_by(member_id: 4, group_id: 7) 

Và sử dụng where là khác nhau:

GroupMember.where(member_id: 4, group_id: 7).first_or_create 

này sẽ gọi create trên GroupMember.where(member_id: 4, group_id: 7):

GroupMember.where(member_id: 4, group_id: 7).create 

Ngược lại, các find_or_create_by(member_id: 4, group_id: 7) sẽ gọi create trên GroupMember:

GroupMember.create(member_id: 4, group_id: 7) 

Hãy xem điều này có liên quan commit trên đường ray/đường ray.

+0

+1 để trỏ đến cam kết ngữ cảnh. – jrhorn424

3

Bạn có thể làm:

User.find_or_create_by(first_name: 'Penélope', last_name: 'Lopez') 
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_create 

Hoặc chỉ cần khởi tạo:

User.find_or_initialize_by(first_name: 'Penélope', last_name: 'Lopez') 
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_initialize 
+0

http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by – Duke

Các vấn đề liên quan