2009-07-14 7 views
31

Xem nhận xét để cập nhật.Đường ray: Nhiều mối quan hệ đa hình

Tôi đã đấu tranh để có được câu trả lời rõ ràng và thẳng thắn về vấn đề này, tôi hy vọng lần này tôi sẽ hiểu! : D Tôi chắc chắn có rất nhiều thứ để học vẫn còn với Rails, tuy nhiên tôi hiểu vấn đề tôi đang đối mặt và sẽ thực sự đánh giá cao sự trợ giúp bổ sung.

  • Tôi có một mô hình gọi là "Tác vụ".
  • Tôi có một mô hình trừu tượng có tên là "Mục tiêu".
  • Tôi muốn liên hệ nhiều phiên bản của các lớp con của Target to Task.
  • Tôi không sử dụng thừa kế bảng đơn.
  • Tôi muốn truy vấn mối quan hệ đa hình để trả về một tập kết quả hỗn hợp của các lớp con của Target.
  • Tôi muốn truy vấn từng cá thể các lớp con của Mục tiêu để có được các tác vụ mà chúng có mối quan hệ với.

Vì vậy, tôi hình nhiều mối quan hệ nhiều mối quan hệ giữa Nhiệm vụ và các lớp con của Mục tiêu theo thứ tự. Cụ thể hơn, tôi sẽ có thể làm những việc như thế này trong giao diện điều khiển (và dĩ nhiên là ở nơi khác):

task = Task.find(1) 
task.targets 
[...array of all the subclasses of Target here...] 

Nhưng! Giả sử mô hình "Store", "Phần mềm", "văn phòng", "xe", đó là tất cả các lớp con của "Target" tồn tại, nó sẽ được tốt đẹp cũng phải đi qua các mối quan hệ theo một hướng khác:

store = Store.find(1) 
store.tasks 
[...array of all the Tasks this Store is related to...] 
software = Software.find(18) 
software.tasks 
[...array of all the Tasks this Software is related to...] 

Các bảng cơ sở dữ liệu ngụ ý bởi các mối quan hệ đa hình dường như có khả năng làm traversal này, nhưng tôi thấy một số chủ đề lặp đi lặp lại trong cố gắng tìm một câu trả lời mà tôi đánh bại tinh thần của các mối quan hệ đa hình:

  • Sử dụng ví dụ của tôi vẫn còn, người xuất hiện muốn xác định Cửa hàng, Phần mềm, Văn phòng, Phương tiện trong Công việc, mà chúng tôi có thể nói ngay lập tức không phải là mối quan hệ đa hình vì nó là chỉ trả về một loại mô hình.
  • Tương tự như điểm cuối cùng, mọi người vẫn muốn xác định Cửa hàng, Phần mềm, Văn phòng và Phương tiện trong Công việc theo hình dạng hoặc hình thức một chiều. Các bit quan trọng ở đây là mối quan hệ là mù để subclassing. Dạng đa hình của tôi ban đầu sẽ chỉ được tương tác với các mục tiêu, không phải là các kiểu lớp con riêng lẻ của chúng. Xác định mỗi phân lớp trong Task một lần nữa bắt đầu ăn đi với mục đích của mối quan hệ đa hình.
  • Tôi thấy rằng một mô hình cho bảng kết nối có thể theo thứ tự, điều đó có vẻ hơi đúng với tôi, ngoại trừ việc nó thêm một số phức tạp mà tôi cho là Rails sẽ sẵn sàng bỏ đi. Tôi rất thiếu kinh nghiệm về điều này.

Dường như đây là một lỗ nhỏ trong chức năng đường ray hoặc kiến ​​thức cộng đồng tập thể. Vì vậy, hy vọng stackoverflow có thể biên niên sử tìm kiếm của tôi cho câu trả lời!

Nhờ tất cả những người trợ giúp!

+0

Trong sáu điểm đạn của bạn, năm trong số đó là tầm thường để đạt được nếu bạn thả thứ sáu, "Tôi không sử dụng thừa kế bảng duy nhất". Về điểm của bạn trên STI bên dưới, vì các cột bổ sung thực sự gây lỗi cho bạn, hãy cân nhắc sử dụng ủy quyền để đẩy dữ liệu và hành vi bổ sung vào các mô hình khác. – austinfromboston

+2

Đẩy nó ra ngoài là những gì dẫn đến điều này. STI mặc dù không phải là một lựa chọn. Tôi ước nó là bởi vì, vâng ... Mọi người đều là một fan hâm mộ lớn của nó. Nhưng tôi muốn dữ liệu được lưu trữ được gắn kết và sẽ có khá nhiều loại Mục tiêu khác nhau. Tôi vẫn thấy nó hơi nổi bật rằng không có cách nào để kéo một bộ sưu tập hỗn hợp như thế này. Thiết kế của tôi có vẻ khá âm thanh. –

+0

Tôi đã có thể thực hiện hầu hết các chức năng mà tôi mong muốn thông qua việc sử dụng has_many_polymorphs. Giới hạn còn lại là tôi vẫn bị kẹt xác định từng loại đa hình trong bố mẹ của tôi (Tác vụ). Các giải pháp bổ sung đều được chào đón, nhưng tôi không chắc chắn một giải pháp sẽ xuất hiện cho đến khi có phiên bản mới của đường ray hoặc cập nhật cho has_many_polymorphs! –

Trả lời

0

Đây có thể không phải là câu trả lời đặc biệt hữu ích, nhưng được nêu một cách đơn giản, tôi không nghĩ có cách dễ dàng hoặc tự động để thực hiện việc này. Ít nhất, không dễ dàng như với các hiệp hội đơn giản hoặc nhiều người.

Tôi nghĩ rằng việc tạo mô hình ActiveRecord cho bảng kết nối là cách đúng để tiếp cận vấn đề. Mối quan hệ has_and_belongs_to_many bình thường giả định tham gia giữa hai bảng được chỉ định, trong trường hợp của bạn có vẻ như bạn muốn tham gia giữa tasks và bất kỳ một trong số stores, softwares, offices hoặc vehicles (bằng cách này, có lý do không sử dụng STI) Có vẻ như nó sẽ giúp giảm độ phức tạp bằng cách giới hạn số lượng bảng bạn có). Vì vậy, trong trường hợp của bạn, bảng tham gia cũng sẽ cần biết tên của lớp con Target có liên quan. Một cái gì đó như

create_table :targets_tasks do |t| 
    t.integer :target_id 
    t.string :target_type 
    t.integer :task_id 
end 

Sau đó, trong lớp Task của bạn, Target lớp con của bạn, và lớp TargetsTask, bạn có thể thiết lập các hiệp hội has_many sử dụng từ khóa :through như được nêu trên ActiveRecord::Associations::ClassMethods rdoc pages.

Tuy nhiên, điều đó chỉ giúp bạn trở thành một phần của con đường, bởi vì :through sẽ không biết sử dụng trường target_type làm tên lớp con Target. Để làm được điều đó, bạn có thể viết một số đoạn SQL chọn/tìm tùy chỉnh, cũng được ghi trong ActiveRecord::Associations::ClassMethods.

Hy vọng điều này giúp bạn di chuyển đúng hướng. Nếu bạn tìm thấy một giải pháp hoàn chỉnh, tôi rất muốn nhìn thấy nó!

+0

Tin tôi đi, tôi đã đập đầu vào cái này một lúc. Ngay sau khi tôi đưa ra một cái gì đó, nó sẽ được lưu hành rất rộng rãi. Tôi hơi ngạc nhiên rằng nó đã không được giải quyết sớm hơn như bộ kết quả hỗn hợp là tất cả các cơn thịnh nộ ngày nay! :) –

+0

Ngoài ra, tôi không sử dụng STI vì nó không hiệu quả và không phải là cách tôi muốn lưu trữ dữ liệu của mình. Tôi có thể sẽ liên kết các hành vi bổ sung với các lớp con của Target ở trên và vượt ra ngoài các giá trị mặc định.Các bảng lớn với nhiều cột không hấp dẫn tôi :) –

0

Tôi đồng ý với những người khác tôi sẽ tìm giải pháp sử dụng hỗn hợp STI và ủy quyền sẽ dễ thực hiện hơn nhiều.

Tại trung tâm của sự cố của bạn là nơi lưu trữ bản ghi của tất cả các lớp con của Target. ActiveRecord chọn cơ sở dữ liệu thông qua mô hình STI.

Bạn có thể lưu trữ chúng trong một biến lớp trong Target và sử dụng gọi lại được thừa kế để thêm những cái mới vào nó. Sau đó, bạn có thể tự động tạo mã mà bạn sẽ cần từ nội dung của mảng đó và tận dụng method_missing.

0

Bạn đã theo đuổi cách tiếp cận mà brute force:

class Task 
    has_many :stores 
    has_many :softwares 
    has_many :offices 
    has_many :vehicles 

    def targets 
    stores + softwares + offices + vehicles 
    end 
    ... 

Nó có thể không được mà tao nhã, nhưng phải trung thực nó không phải là dài dòng, và không có gì vốn không hiệu quả về mã là.

+0

Phải, nhưng sau đó Nhiệm vụ phải nhận thức được từng loại có thể nhắm mục tiêu. Tôi muốn có thể tạo ra một mục tiêu và không phải sửa đổi nhiệm vụ mỗi khi một cái mới được thêm vào. –

+0

Nó là ruby. Bạn có thể viết một phương thức class (hay một module) để bao gồm trong các cửa hàng, văn phòng của bạn. Nó có thể sửa đổi lớp Task, làm những gì được thực hiện ở trên (thêm has_many và vá các mục tiêu để bao gồm cả lớp) . Tôi không đề xuất cách tiếp cận đó, tôi chỉ nói ... – ndp

53

Bạn có thể kết hợp đa hình và has_many :through để có được một ánh xạ linh hoạt:

class Assignment < ActiveRecord::Base 
    belongs_to :task 
    belongs_to :target, :polymorphic => true 
end 

class Task < ActiveRecord::Base 
    has_many :targets, :through => :assignment 
end 

class Store < ActiveRecord::Base 
    has_many :tasks, :through => :assignment, :as => :target 
end 

class Vehicle < ActiveRecord::Base 
    has_many :tasks, :through => :assignment, :as => :target 
end 

... Và vân vân.

+2

Điều này dường như giải quyết vấn đề rất đơn giản và được hỗ trợ 100% bởi Rails. –

+0

sạch sẽ và đơn giản – brettish

+1

Lưu ý rằng ': as => target' phải là': as =>: target' trong cả hai trường hợp. Giải pháp tốt đẹp –

1

Giải pháp has_many_polymorphs mà bạn đề cập không phải là xấu.

class Task < ActiveRecord::Base 
    has_many_polymorphs :targets, :from => [:store, :software, :office, :vehicle] 
end 

Dường như làm mọi thứ bạn muốn.

Nó cung cấp các phương pháp sau:

Task:

t = Task.first 
t.targets # Mixed collection of all targets associated with task t 
t.stores # Collection of stores associated with task t 
t.softwares # same but for software 
t.offices # same but for office 
t.vehicles # same but for vehicles 

để phần mềm, Cửa hàng, Văn phòng, Xe:

s = Software.first # works for any of the subtargets. 
s.tasks    # lists tasks associated with s 

Nếu tôi sau những nhận xét chính xác, chỉ còn lại vấn đề là bạn không muốn phải sửa đổi app/models/task.rb mỗi khi bạn tạo một loại Subtarget mới. Cách Rails dường như yêu cầu bạn sửa đổi hai tệp để tạo một liên kết hai chiều. has_many_polymorphs chỉ yêu cầu bạn thay đổi tệp Tác vụ. Có vẻ như một chiến thắng với tôi. Hoặc ít nhất là nếu bạn không phải chỉnh sửa tệp Mô hình mới.

Có một số cách để giải quyết vấn đề này, nhưng chúng dường như quá nhiều công việc để tránh thay đổi từng tệp một lần. Nhưng nếu bạn là người đã chết khi tự mình sửa đổi Nhiệm vụ để thêm vào mối quan hệ đa hình, đây là gợi ý của tôi:

Giữ danh sách các mục tiêu, tôi sẽ đề xuất trong mục lib/subtargets được định dạng một mục trên mỗi dòng về cơ bản table_name.underscore. (Chữ Capital đã một dấu gạch dưới tiền tố và sau đó tất cả mọi thứ được làm bằng chữ thường)

store 
software 
office 
vehicle 

Tạo config/initializers/subtargets.rb và điền nó với điều này:

SubtargetList = File.open("#{RAILS_ROOT}/lib/subtargets").read.split.reject(&:match(/#/)).map(&:to_sym) 

Tiếp theo bạn sẽ muốn hoặc tạo một trình tạo tùy chỉnh hoặc một tác vụ cào mới. Để tạo mục tiêu con mới của bạn và thêm tên mô hình vào tệp danh sách nhắm mục tiêu phụ, được xác định ở trên. Có lẽ bạn sẽ kết thúc làm một cái gì đó xương trần mà làm cho sự thay đổi và vượt qua các đối số cho máy phát điện tiêu chuẩn.

Xin lỗi, tôi không thực sự cảm thấy như đi bộ bạn qua ngay bây giờ, nhưng đây là someresources

Cuối cùng thay thế danh sách trong việc kê khai has_many_polymorphs với SubtargetList

class Task < ActiveRecord::Base 
    has_many_polymorphs :targets, :from => SubtargetList 
end 

Từ lúc này trở đi bạn có thể thêm mục tiêu phụ mới với

$ script/generate subtarget_model home 

Và điều này sẽ tự động cập nhật đa hình của bạn danh sách ic khi bạn tải lại bảng điều khiển của mình hoặc khởi động lại máy chủ sản xuất.

Như tôi đã nói có rất nhiều công việc để tự động cập nhật danh sách mục tiêu. Tuy nhiên, nếu bạn đi tuyến đường này, bạn có thể tinh chỉnh trình tạo tùy chỉnh đảm bảo tất cả các phần bắt buộc của mô hình mục tiêu phụ có khi bạn tạo nó.

1

Sử dụng STI:

class Task < ActiveRecord::Base 
end 

class StoreTask < Task 
    belongs_to :store, :foreign_key => "target_id" 
end 

class VehicleTask < Task 
    belongs_to :vehicle, :foreign_key => "target_id" 
end 

class Store < ActiveRecord::Base 
    has_many :tasks, :class_name => "StoreTask", :foreign_key => "target_id" 
end 

class Vehicle < ActiveRecord::Base 
    has_many :tasks, :class_name => "VehicleTask", :foreign_key => "target_id" 
end 

Trong databse của bạn, bạn sẽ cần: Task type:stringTask target_id:integer

Ưu điểm là bây giờ bạn có một đường mô hình đối với từng loại công việc có thể được cụ thể.

Xem thêm STI and polymorphic model together

Chúc mừng!

10

Mặc dù câu trả lời bằng cách đề xuất bởi SFEley là rất tốt, có một số sai sót:

  • Các hồi các nhiệm vụ từ mục tiêu (Cửa hàng/xe) hoạt động, nhưng ngược wont. Đó là cơ bản bởi vì bạn không thể đi qua một: thông qua kết hợp với một kiểu dữ liệu đa hình vì SQL không thể biết bảng đó là gì.
  • Mỗi mô hình với: thông qua liên kết cần liên kết trực tiếp với bảng trung gian
  • các: thông qua hiệp hội Phân phải ở trong số nhiều
  • các: như tuyên bố sẽ không làm việc cùng với: thông qua, bạn cần phải xác định nó đầu tiên với sự liên kết trực tiếp cần thiết với bảng trung gian

với ý nghĩ đó, giải pháp đơn giản nhất của tôi là:

class Assignment < ActiveRecord::Base 
    belongs_to :task 
    belongs_to :target, :polymorphic => true 
end 

class Task < ActiveRecord::Base 
    has_many :assignments 
    # acts as the the 'has_many targets' needed 
    def targets 
    assignments.map {|x| x.target} 
    end 
end 

class Store < ActiveRecord::Base 
    has_many :assignments, as: :target 
    has_many :tasks, :through => :assignment 
end 

class Vehicle < ActiveRecord::Base 
    has_many :assignments, as: :target 
    has_many :tasks, :through => :assignment, :as => :target 
end 

Tài liệu tham khảo: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through