Rails tạo hay cập nhật phép thuật?


93

Tôi có một lớp được gọi là CachedObjectlưu trữ các đối tượng được tuần tự hóa chung được lập chỉ mục bằng khóa. Tôi muốn lớp này triển khai một create_or_updatephương thức. Nếu một đối tượng được tìm thấy, nó sẽ cập nhật nó, nếu không nó sẽ tạo một đối tượng mới.

Có cách nào để thực hiện việc này trong Rails hay tôi phải viết phương thức của riêng mình?

Câu trả lời:


172

Đường ray 6

Rails 6 đã thêm một upsertupsert_allcác phương thức cung cấp chức năng này.

Model.upsert(column_name: value)

[upert] Nó không khởi tạo bất kỳ mô hình nào cũng như không kích hoạt các cuộc gọi lại hoặc xác nhận Active Record.

Đường ray 5, 4 và 3

Không phải nếu bạn đang tìm kiếm kiểu câu lệnh "upert" (nơi cơ sở dữ liệu thực hiện cập nhật hoặc câu lệnh chèn trong cùng một thao tác). Ngoài ra, Rails và ActiveRecord không có tính năng này. Bạn có thể sử dụng upsert đá quý, tuy nhiên.

Nếu không, bạn có thể sử dụng: find_or_initialize_byhoặc find_or_create_by, cung cấp chức năng tương tự, mặc dù với chi phí của một lần truy cập cơ sở dữ liệu bổ sung, mà trong hầu hết các trường hợp, hầu như không phải là một vấn đề. Vì vậy, trừ khi bạn có mối quan tâm nghiêm trọng về hiệu suất, tôi sẽ không sử dụng đá quý.

Ví dụ: nếu không tìm thấy người dùng nào có tên "Roger", một phiên bản người dùng mới sẽ được khởi tạo với bộ của nó namethành "Roger".

user = User.where(name: "Roger").first_or_initialize
user.email = "email@example.com"
user.save

Ngoài ra, bạn có thể sử dụng find_or_initialize_by.

user = User.find_or_initialize_by(name: "Roger")

Trong Rails 3.

user = User.find_or_initialize_by_name("Roger")
user.email = "email@example.com"
user.save

Bạn có thể sử dụng một khối, nhưng khối chỉ chạy nếu bản ghi là mới .

User.where(name: "Roger").first_or_initialize do |user|
  # this won't run if a user with name "Roger" is found
  user.save 
end

User.find_or_initialize_by(name: "Roger") do |user|
  # this also won't run if a user with name "Roger" is found
  user.save
end

Nếu bạn muốn sử dụng một khối bất kể độ bền của bản ghi, hãy sử dụng taptrên kết quả:

User.where(name: "Roger").first_or_initialize.tap do |user|
  user.email = "email@example.com"
  user.save
end


1
Mã nguồn mà tài liệu trỏ tới cho thấy rằng điều này không hoạt động theo cách bạn ngụ ý - khối chỉ được chuyển tới một phương thức mới nếu bản ghi tương ứng không tồn tại. Dường như không có phép thuật "nâng cấp" nào trong Rails - bạn phải tách nó thành hai câu lệnh Ruby, một cho lựa chọn đối tượng và một cho cập nhật thuộc tính.
cùng

@sameers Tôi không chắc mình hiểu ý bạn. Bạn nghĩ tôi đang ám chỉ điều gì?
Mohamad

1
Ồ ... tôi hiểu ý bạn bây giờ - cả hai đều hình thành find_or_initialize_byfind_or_create_bychấp nhận một khối. Tôi nghĩ ý của bạn là cho dù bản ghi có tồn tại hay không, một khối sẽ được truyền lại với đối tượng bản ghi làm đối số, để thực hiện cập nhật.
cùng

3
Nó hơi gây hiểu lầm, không nhất thiết là câu trả lời, mà là API. Người ta sẽ mong đợi khối được chuyển bất kể và do đó có thể được tạo / cập nhật cho phù hợp. Thay vào đó, chúng ta phải chia nó thành các câu lệnh riêng biệt. Ụt. <3 Rails mặc dù :)
Volte

32

Trong Rails 4, bạn có thể thêm vào một mô hình cụ thể:

def self.update_or_create(attributes)
  assign_or_new(attributes).save
end

def self.assign_or_new(attributes)
  obj = first || new
  obj.assign_attributes(attributes)
  obj
end

và sử dụng nó như

User.where(email: "a@b.com").update_or_create(name: "Mr A Bbb")

Hoặc nếu bạn muốn thêm các phương thức này vào tất cả các mô hình được đặt trong bộ khởi tạo:

module ActiveRecordExtras
  module Relation
    extend ActiveSupport::Concern

    module ClassMethods
      def update_or_create(attributes)
        assign_or_new(attributes).save
      end

      def update_or_create!(attributes)
        assign_or_new(attributes).save!
      end

      def assign_or_new(attributes)
        obj = first || new
        obj.assign_attributes(attributes)
        obj
      end
    end
  end
end

ActiveRecord::Base.send :include, ActiveRecordExtras::Relation

1
Sẽ không assign_or_newtrả lại hàng đầu tiên trong bảng nếu nó tồn tại và sau đó hàng đó sẽ được cập nhật? Nó dường như đang làm điều đó cho tôi.
steve klein

User.where(email: "a@b.com").firstsẽ trả về nil nếu không tìm thấy. Hãy chắc chắn rằng bạn có một wherephạm vi
montrealmike

Chỉ cần một ghi chú để nói rằng updated_atsẽ không được chạm vào vì đã assign_attributesđược sử dụng
lshepstone

Nó sẽ không được bạn đang sử dụng assing_or_new nhưng sẽ nếu bạn sử dụng update_or_create vì sự tiết kiệm
montrealmike

13

Thêm cái này vào mô hình của bạn:

def self.update_or_create_by(args, attributes)
  obj = self.find_or_create_by(args)
  obj.update(attributes)
  return obj
end

Với điều đó, bạn có thể:

User.update_or_create_by({name: 'Joe'}, attributes)

Phần thứ hai tôi sẽ không làm việc. Không thể cập nhật một bản ghi từ cấp lớp mà không có ID của bản ghi để cập nhật.
Aeramor

1
obj = self.find_or_create_by (args); obj.update (thuộc tính); trả lại obj; sẽ hoạt động.
veeresh yh

12

Điều kỳ diệu bạn đang tìm kiếm đã được thêm vào Rails 6 Bây giờ bạn có thể nâng cấp (cập nhật hoặc chèn). Để sử dụng một bản ghi:

Model.upsert(column_name: value)

Đối với nhiều bản ghi, hãy sử dụng upsert_all :

Model.upsert_all(column_name: value, unique_by: :column_name)

Ghi chú :

  • Cả hai phương pháp đều không kích hoạt các lệnh gọi lại hoặc xác nhận Active Record
  • unique_by => Chỉ PostgreSQL và SQLite

1

Câu hỏi cũ nhưng ném giải pháp của tôi vào vòng cho hoàn chỉnh. Tôi cần cái này khi tôi cần một tìm cụ thể nhưng một bản tạo khác nếu nó không tồn tại.

def self.find_by_or_create_with(args, attributes) # READ CAREFULLY! args for finding, attributes for creating!
        obj = self.find_or_initialize_by(args)
        return obj if obj.persisted?
        return obj if obj.update_attributes(attributes) 
end

0

Bạn có thể làm điều đó trong một câu lệnh như sau:

CachedObject.where(key: "the given key").first_or_create! do |cached|
   cached.attribute1 = 'attribute value'
   cached.attribute2 = 'attribute value'
end

9
Điều này sẽ không hoạt động, vì nó sẽ chỉ trả lại bản ghi gốc nếu một bản ghi được tìm thấy. OP yêu cầu một giải pháp luôn thay đổi giá trị, ngay cả khi một bản ghi được tìm thấy.
JeanMertz

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.