11

Trong dự án tôi hiện đang phát triển theo đường ray 4.0.0beta1, tôi có nhu cầu xác thực dựa trên người dùng trong đó mỗi người dùng có thể được liên kết với một thực thể. Tôi là người mới đến đường ray và có một số rắc rối làm như vậy.has_one thông qua và các mối quan hệ đa hình trên thừa kế đa bảng

Mô hình này là như sau:

class User < ActiveRecord::Base 
end 

class Agency < ActiveRecord::Base 
end 

class Client < ActiveRecord::Base 
    belongs_to :agency 
end 

gì tôi cần là cho một người dùng để có thể liên kết đến hoặc là một cơ quan hay một khách hàng nhưng không phải cả hai (hai là những gì tôi sẽ gọi các đơn vị). Nó có thể không có liên kết nào cả và nhiều nhất là một liên kết.

Điều đầu tiên tôi tìm kiếm là cách thực hiện Mutli-Table inheritance (MTI) trong đường ray. Nhưng một số điều bị chặn tôi:

  • nó không có sẵn out of the box
  • MTI trông kinda khó để thực hiện cho một newbie như tôi
  • đá quý thực hiện các giải pháp có vẻ cũ và hoặc là quá Complexe hoặc không hoàn thành
  • đá quý sẽ có lẽ đã phá vỡ dưới rails4 như họ đã không được cập nhật trong một thời gian

vì vậy, tôi đã tìm kiếm một giải pháp khác và tôi tìm thấy polymorphic associations.

Tôi đã có về vấn đề này từ hôm qua và mất một thời gian để làm cho nó hoạt ngay cả với sự giúp đỡ của Rails polymorphic has_many :throughActiveRecord, has_many :through, and Polymorphic Associations

tôi quản lý để thực hiện các ví dụ từ câu hỏi trên công việc nhưng phải mất một thời gian và tôi cuối cùng có hai vấn đề:

  1. Làm cách nào để chuyển mối quan hệ trong người dùng thành liên kết has_one và có thể truy cập "một cách mù quáng" đối tượng được liên kết?
  2. Cách đặt ràng buộc để không người dùng nào có thể có nhiều hơn một thực thể?
  3. Có cách nào tốt hơn để làm những gì tôi muốn không?

Trả lời

11

Dưới đây là một ví dụ làm việc đầy đủ:

Các tập tin chuyển đổi:

class CreateUserEntities < ActiveRecord::Migration 
    def change 
    create_table :user_entities do |t| 
     t.integer :user_id 
     t.references :entity, polymorphic: true 

     t.timestamps 
    end 

    add_index :user_entities, [:user_id, :entity_id, :entity_type] 
    end 
end 

Các mô hình:

class User < ActiveRecord::Base 
    has_one :user_entity 

    has_one :client, through: :user_entity, source: :entity, source_type: 'Client' 
    has_one :agency, through: :user_entity, source: :entity, source_type: 'Agency' 

    def entity 
    self.user_entity.try(:entity) 
    end 

    def entity=(newEntity) 
    self.build_user_entity(entity: newEntity) 
    end 
end 

class UserEntity < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :entity, polymorphic: true 

    validates_uniqueness_of :user 
end 

class Client < ActiveRecord::Base 
    has_many :user_entities, as: :entity 
    has_many :users, through: :user_entities 
end 

class Agency < ActiveRecord::Base 
    has_many :user_entities, as: :entity 
    has_many :users, through: :user_entities 
end 

Như bạn có thể thấy tôi đã thêm một getter và setter mà tôi có tên "thực thể". Đó là vì has_one :entity, through: :user_entity tăng lỗi sau:

ActiveRecord::HasManyThroughAssociationPolymorphicSourceError: Cannot have a has_many :through association 'User#entity' on the polymorphic object 'Entity#entity' without 'source_type'. Try adding 'source_type: "Entity"' to 'has_many :through' definition. 

Cuối cùng, đây là các thử nghiệm tôi đã thiết lập. Tôi cung cấp cho họ để mọi người hiểu biết ho bạn có thể thiết lập và truy cập dữ liệu giữa các đối tượng đó. Tôi sẽ không nêu chi tiết các mô hình FactoryGirl của tôi nhưng chúng khá rõ ràng

require 'test_helper' 

class UserEntityTest < ActiveSupport::TestCase 

    test "access entity from user" do 
    usr = FactoryGirl.create(:user_with_client) 

    assert_instance_of client, usr.user_entity.entity 
    assert_instance_of client, usr.entity 
    assert_instance_of client, usr.client 
    end 

    test "only right entity is set" do 
    usr = FactoryGirl.create(:user_with_client) 

    assert_instance_of client, usr.client 
    assert_nil usr.agency 
    end 

    test "add entity to user using the blind rails method" do 
    usr = FactoryGirl.create(:user) 
    client = FactoryGirl.create(:client) 

    usr.build_user_entity(entity: client) 
    usr.save! 

    result = UserEntity.where(user_id: usr.id) 
    assert_equal 1, result.size 
    assert_equal client.id, result.first.entity_id 
    end 

    test "add entity to user using setter" do 
    usr = FactoryGirl.create(:user) 
    client = FactoryGirl.create(:client) 

    usr.client = client 
    usr.save! 

    result = UserEntity.where(user_id: usr.id) 
    assert_equal 1, result.size 
    assert_equal client.id, result.first.entity_id 
    end 

    test "add entity to user using blind setter" do 
    usr = FactoryGirl.create(:user) 
    client = FactoryGirl.create(:client) 

    usr.entity = client 
    usr.save! 

    result = UserEntity.where(user_id: usr.id) 
    assert_equal 1, result.size 
    assert_equal client.id, result.first.entity_id 
    end 

    test "add user to entity" do 
    usr = FactoryGirl.create(:user) 
    client = FactoryGirl.create(:client) 

    client.users << usr 

    result = UserEntity.where(entity_id: client.id, entity_type: 'client') 

    assert_equal 1, result.size 
    assert_equal usr.id, result.first.user_id 
    end 

    test "only one entity by user" do 

    usr = FactoryGirl.create(:user) 
    client = FactoryGirl.create(:client) 
    agency = FactoryGirl.create(:agency) 

    usr.agency = agency 
    usr.client = client 
    usr.save! 

    result = UserEntity.where(user_id: usr.id) 
    assert_equal 1, result.size 
    assert_equal client.id, result.first.entity_id 

    end 

    test "user uniqueness" do 

    usr = FactoryGirl.create(:user) 
    client = FactoryGirl.create(:client) 
    agency = FactoryGirl.create(:agency) 

    UserEntity.create!(user: usr, entity: client) 

    assert_raise(ActiveRecord::RecordInvalid) { 
     UserEntity.create!(user: usr, entity: agency) 
    } 

    end 

end 

Tôi hy vọng điều này có thể giúp ích cho ai đó.Tôi quyết định đưa toàn bộ giải pháp ở đây khiến tôi có vẻ như là một giải pháp tốt hơn so với MTI và tôi nghĩ rằng không nên dành nhiều thời gian để thiết lập một thứ như thế.

+0

@Crystark Tôi nhận được lỗi sau khi kiểm tra tệp NameError ở trên: hằng số không được khởi tạo UserWithClient –

0

Câu trả lời trên khiến tôi gặp rắc rối. Sử dụng tên cột thay vì tên mô hình khi xác thực tính duy nhất. Thay đổi validates_uniqueness_of: user thành validates_uniqueness_of: user_id.

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