2011-01-19 45 views
43

Trong Ruby 1.9.2 trên Rails 3.0.3, Tôi đang cố gắng kiểm tra sự bình đẳng đối tượng giữa hai đối tượng Friend (lớp kế thừa từ ActiveRecord::Base).Cách kiểm tra đối tượng (đối tượng ActiveRecord)

Đối tượng đều bình đẳng, nhưng các thử nghiệm thất bại:

Failure/Error: Friend.new(name: 'Bob').should eql(Friend.new(name: 'Bob')) 

expected #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil> 
    got #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil> 

(compared using eql?) 

Chỉ cần cho những nụ cười, tôi cũng kiểm tra nhận dạng đối tượng, mà không như tôi mong đợi:

Failure/Error: Friend.new(name: 'Bob').should equal(Friend.new(name: 'Bob')) 

expected #<Friend:2190028040> => #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil> 
    got #<Friend:2190195380> => #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil> 

Compared using equal?, which compares object identity, 
but expected and actual are not the same object. Use 
'actual.should == expected' if you don't care about 
object identity in this example. 

Ai đó có thể giải thích với tôi tại sao thử nghiệm đầu tiên cho sự bình đẳng đối tượng thất bại, và làm thế nào tôi có thể khẳng định thành công hai đối tượng đó bằng nhau?

Trả lời

36

Đường ray cố ý ủy quyền kiểm tra bình đẳng cho cột nhận dạng. Nếu bạn muốn biết liệu hai đối tượng AR có chứa cùng nội dung hay không, hãy so sánh kết quả của việc gọi #attributes trên cả hai.

+3

Nhưng trong trường hợp này, cột nhận dạng là 'nil' cho cả hai trường hợp vì không có con ong n đã lưu. 'eql?()' kiểm tra cả giá trị và kiểu thuộc tính. 'nil.class == nil.class' là' true' và 'nil == nil' là' true', vì vậy ví dụ đầu tiên của OP vẫn phải là true. Câu trả lời của bạn không giải thích tại sao nó trả về sai. – Jazz

+2

Nó không chỉ so sánh một cách mù quáng các id, nó chỉ so sánh các id nếu các id có ý nghĩa. Như đã đề cập trong câu trả lời của Andy Lindeman: "Các hồ sơ mới khác với bất kỳ hồ sơ nào khác theo định nghĩa". – Lambart

33

Hãy nhìn vào các API docs về hoạt động == (bí danh eql?) cho ActiveRecord::Base

trả về true nếu comparison_object là đối tượng chính xác giống nhau, hoặc comparison_object là cùng loại và tự có một ID và nó bằng compare_object.id.

Lưu ý rằng các bản ghi mới khác với bất kỳ bản ghi nào khác theo định nghĩa, trừ khi bản ghi khác là bản thân người nhận. Ngoài ra, nếu bạn tìm nạp các bản ghi hiện có có chọn và để lại ID, bạn đang ở chế độ riêng của bạn, vị từ này sẽ trả về false.

Cũng lưu ý rằng việc hủy hồ sơ lưu giữ ID của nó trong trường hợp mô hình, do đó, các mô hình đã xóa vẫn có thể so sánh được.

+1

Cập nhật liên kết tài liệu API cho Rails 3.2.8 http://www.rubydoc.info/docs/rails/3.2.8/frames Ngoài ra, đáng chú ý là 'eql? 'Bị ghi đè, nhưng không phải là bí danh' bằng?' mà vẫn so sánh 'object_id' –

16

Nếu bạn muốn so sánh hai trường hợp mô hình dựa trên các thuộc tính của họ, bạn có thể sẽ muốn loại trừ nhất định thuộc tính không liên quan từ so sánh của bạn, chẳng hạn như: id, created_at, và updated_at. (Tôi sẽ xem xét những được nhiều metadata về kỷ lục hơn một phần của dữ liệu của kỷ lục của chính nó.)

Điều này có thể không quan trọng khi bạn đang so sánh hai kỷ lục mới (chưa được lưu) (kể từ id, created_at, và updated_at sẽ tất cả là nil cho đến khi được lưu), nhưng đôi khi tôi thấy cần phải so sánh một đối tượng được lưu được lưu với số chưa được lưu một (trong trường hợp == sẽ cho bạn sai từ nil! = 5). Hoặc tôi muốn so sánh hai đối tượng được lưu để tìm hiểu xem chúng có chứa cùng một dữ liệu hay không (vì vậy nhà cung cấp ActiveRecord == không hoạt động, vì nó trả về false nếu chúng khác nhau id.).

Giải pháp của tôi cho vấn đề này là thêm một cái gì đó như thế này trong các mô hình mà bạn muốn được so sánh sử dụng thuộc tính:

def self.attributes_to_ignore_when_comparing 
    [:id, :created_at, :updated_at] 
    end 

    def identical?(other) 
    self. attributes.except(*self.class.attributes_to_ignore_when_comparing.map(&:to_s)) == 
    other.attributes.except(*self.class.attributes_to_ignore_when_comparing.map(&:to_s)) 
    end 

Sau đó, trong thông số kỹ thuật của tôi, tôi có thể viết những điều có thể đọc được và ngắn gọn như thế này:

Address.last.should be_identical(Address.new({city: 'City', country: 'USA'})) 

Tôi đang lên kế hoạch cho đá quý active_record_attributes_equality và thay đổi nó để sử dụng hành vi này để có thể dễ dàng sử dụng lại.

Một số câu hỏi tôi có, tuy nhiên, bao gồm:

  • Có một viên ngọc như vậy đã tồn tại ??
  • Phương pháp này nên được gọi là gì? Tôi không nghĩ rằng các nhà điều hành hiện tại == là một ý tưởng tốt, vì vậy bây giờ tôi gọi nó là identical?. Nhưng có lẽ cái gì đó như practically_identical? hoặc attributes_eql? sẽ chính xác hơn, vì nó không phải kiểm tra nếu họ đang Nghiêm giống hệt nhau (một số các thuộc tính được phép khác nhau.) ...
  • attributes_to_ignore_when_comparing là quá dài dòng. Không phải là điều này sẽ cần phải được thêm một cách rõ ràng vào mỗi mô hình nếu họ muốn sử dụng các giá trị mặc định của gem. Có lẽ cho phép mặc định để được ghi đè bằng một macro lớp như ignore_for_attributes_eql :last_signed_in_at, :updated_at

Comments được hoan nghênh ...

Cập nhật: Thay vì forking các active_record_attributes_equality, tôi đã viết một viên ngọc hoàn toàn mới, active_record_ignored_attributes , có sẵn tại http://github.com/TylerRick/active_record_ignored_attributeshttp://rubygems.org/gems/active_record_ignored_attributes

1
META = [:id, :created_at, :updated_at, :interacted_at, :confirmed_at] 

def eql_attributes?(original,new) 
    original = original.attributes.with_indifferent_access.except(*META) 
    new = new.attributes.symbolize_keys.with_indifferent_access.except(*META) 
    original == new 
end 

eql_attributes? attrs, attrs2 
Các vấn đề liên quan