2013-05-20 26 views
7

Vì vậy, tôi có như sau:STI trong Rails: Làm cách nào để thay đổi từ một lớp cha thành một lớp con mà không truy cập thuộc tính "type" trực tiếp?

class Product < ActiveRecord::Base 
    # Has a bunch of common stuff about assembly hierarchy, etc 
end 

class SpecializedProduct < Product 
    # Has some special stuff that a "Product" can not do! 
end 

Có một quá trình sản xuất và lắp ráp trong đó dữ liệu được chụp về sản phẩm. Tại thời điểm nắm bắt các loại sản phẩm cuối cùng không được biết đến. sau khi hồ sơ sản phẩm đã được tạo ra trong cơ sở dữ liệu (có thể vài ngày sau đó) có thể cần thiết để biến sản phẩm đó thành một sản phẩm chuyên biệt và điền vào thông tin bổ sung. Không phải tất cả các sản phẩm sẽ trở thành chuyên ngành, tuy nhiên.

Tôi đã cố gắng sử dụng như sau:

object_to_change = Product.find(params[:id]) 
object_to_change.becomes SpecializedProduct 
object_to_change.save 

Sau đó, khi tôi làm một SpecializedProduct.all tập kết quả không bao gồm object_to_change. Thay vào đó object_to_change vẫn được liệt kê trong cơ sở dữ liệu như Product

UPDATE "products" SET "type" = ?, "updated_at" = ? WHERE "products"."type" IN ('SpecializedProduct') AND "products"."id" = 30 [["type", "Product"], ["updated_at", Fri, 17 May 2013 10:28:06 UTC +00:00]] 

Vì vậy, sau khi cuộc gọi đến .becomes SpecializedProduct phương pháp .save hiện đang sử dụng đúng loại, nhưng nó không thể cập nhật các bản ghi vì WHERE khoản của bản cập nhật là quá cụ thể.

Tôi có thực sự cần truy cập trực tiếp thuộc tính type của mô hình không? Tôi thực sự không thích.

+1

câu trả lời ngắn gọn: có, bạn cần cập nhật bằng loại. Nói chung, STI không được thiết kế để thay đổi các lớp theo thời gian. –

Trả lời

0

Tôi nghĩ là không! Kiểm tra các sorces của becomesbecomes! phương pháp! https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L199

Hình như bạn cần phải sử dụng becomes! vì nó là bao bọc xung quanh becomes đó cũng thay đổi giá trị cột sti của ví dụ.

UPD:https://github.com/rails/rails/blob/4-0-stable/activerecord/test/cases/persistence_test.rb#L279 Đây là trường hợp kiểm tra mã của bạn.

UPD2: Tôi nghĩ bạn có thể cố gắng để tạo ra một loại lớp của DefaultProject đó là lớp con của Dự án và sau đó bạn có thể thay đổi mỗi từ DefaultProject để SpecializedProduct và ngược lại

+1

Tôi nhận được cùng một kết quả SQL trên bảng điều khiển đường ray với '.becomes!' Như tôi đã làm với '.becomes'' SET "type" =? 'Vẫn còn trong đó, nhưng không may như vậy là' "sản phẩm". "Type" IN ('SpecializedProduct') 'trong mệnh đề' WHERE'. – Jamie

+0

xem câu trả lời cập nhật của tôi, nó sẽ hoạt động ... bạn đang sử dụng phiên bản đường ray nào? – Fivell

1

Nhìn vào nguồn gốc của becomesbecomes!, nó không làm biến đổi đối tượng gốc. Bạn cần phải gán nó vào một biến mới:

some_product = Product.find(params[:id]) 
specialized_product = some_product.becomes SpecializedProduct 
specialized_product.save 

Không chắc chắn cách này sẽ xử lý các khóa chính của hồ sơ, tuy nhiên, do đó bạn có thể cần phải làm một số finagling thêm để đảm bảo mối quan hệ của bạn không nhận được bị xù xì.

0

Tôi có vấn đề tương tự mà tôi muốn thay đổi từ một phân lớp này sang phân lớp khác. Thật không may Rails không làm điều này một cách duyên dáng vì nó muốn giới hạn lưu với một "where type = 'NewSubclass'". ví dụ:

UPDATE parents SET type='NewSubclass' WHERE type IN 'NewSubclass' AND id=1234 

Đào vào đường ray, dường như các phương pháp trong lib/active_record/inheritance.rb tên ActiveRecord của "finder_needs_type_condition?" được gọi và người gọi không đủ thông minh để nhận ra bạn đang thay đổi trường loại vì vậy rõ ràng nó không phải là giá trị đó.

Tôi "giải quyết" điều này trong một vòng về cách.Tôi đã sử dụng lõi ActiveRecord làm cơ sở cho cách tải một thể hiện của bất kỳ lớp nào mà tôi muốn với các thuộc tính và lưu nó mà không phải trải qua toàn bộ ngăn xếp ActiveRecord.

old_instance = Parent.find(id) #returns OldSubclass instance 
tmp = Parent.allocate.init_with('attributes' => old_instance.attributes) 
tmp.type = 'NewSubclass' 
tmp.save 
Parent.find(id) #returns NewSubclass instance 

Thật xấu xí và tôi ghét điều đó. Hy vọng rằng ai đó sẽ nghĩ về việc sửa lỗi này trong ActiveRecord. Tôi tin rằng nó sẽ hữu ích cho các đối tượng để thay đổi các lớp con trong STI theo thời gian. Tôi có một bảng duy nhất với 5 lớp con vì nó dọn dẹp mô hình khá một chút.

Đây là sự xấu xí duy nhất mà tôi phải sống cùng. Hãy chắc chắn để viết kiểm tra thích hợp để khi ActiveRecord phá vỡ "workaround" này, bạn có thể phát hiện nó.

1

Bạn chỉ cần phiên bản bang của phương thức trở thành bằng (!) Và lưu.

Sự khác biệt giữa hai phương pháp: becomes tạo một phiên bản mới của lớp mới với tất cả các giá trị thuộc tính giống nhau của đối tượng gốc. becomes! cũng cập nhật cột loại.

object_to_change = Product.find(params[:id]) 
object_to_change.becomes! SpecializedProduct 
object_to_change.save 
Các vấn đề liên quan