11

W projekcie, który obecnie rozwijam pod szynami 4.0.0beta1, potrzebowałem uwierzytelnienia na podstawie użytkownika, w którym każdy użytkownik mógłby być połączony z jednostką. Jestem trochę nowy na szynach i miałem pewne kłopoty.ma powiązania i polimorficzne ponad dziedziczenie wielu tabel

Wzór jest następujący:

class User < ActiveRecord::Base 
end 

class Agency < ActiveRecord::Base 
end 

class Client < ActiveRecord::Base 
    belongs_to :agency 
end 

Co potrzebne jest, aby użytkownik mógł połączyć albo agencję lub klienta, ale nie oba (te dwa są co będę nazywając podmioty). Nie może mieć żadnego linku i najwyżej jednego łącza.

Pierwszą rzeczą, na którą patrzyłem, było wykonanie Mutli-Table inheritance (MTI) w szynach. Ale niektóre rzeczy zablokowane mnie:

  • to nie był dostępny po wyjęciu z pudełka
  • MTI wyglądał trochę trudne do wdrożenia dla początkujących takich jak ja
  • kamienie wdrażających rozwiązania wydawał się stary i zbyt Complexe lub nie kompletne
  • kamienie byłby prawdopodobnie złamał pod rails4 ponieważ nie zostały zaktualizowane przez chwilę

Więc szukałem innego rozwiązania i znalazłem polymorphic associations.

mam być na to od wczoraj i zajęło trochę czasu, aby to działało nawet z pomocą Rails polymorphic has_many :through i ActiveRecord, has_many :through, and Polymorphic Associations

udało mi się zrobić przykłady z powyższym pytaniem pracy, ale zajęło to trochę czasu, a ja wreszcie mają dwa problemy:

  1. Jak przekształcić relacje w użytkowniku w stowarzyszenie has_one i mieć możliwość dostępu do "na ślepo" połączonego obiektu?
  2. Jak ustawić ograniczenie, aby żaden użytkownik nie mógł mieć więcej niż jednego obiektu?
  3. Czy istnieje lepszy sposób robienia tego, co chcę?

Odpowiedz

11

Oto przykład całkowicie pracy:

Plik migracji:

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 

Modele:

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 

Jak widać dodałem getter i setter, że o nazwie "entity". To dlatego has_one :entity, through: :user_entity podnosi się następujący błąd:

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. 

Wreszcie Oto testy skonfigurować. Daję je tak, aby każdy wiedział, że możesz ustawić i uzyskać dostęp do danych między tymi obiektami. Nie będę szczegółowo moje modele FactoryGirl ale są one dość oczywiste

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 

Mam nadzieję, że to będzie pomocne dla kogoś.Postanowiłem postawić tutaj całe rozwiązanie, ponieważ wydaje mi się ono dobre w porównaniu z MTI i myślę, że nie powinno to zabrać komuś tak dużo czasu, aby ustawić coś takiego.

+0

@Crystark Podczas testowania powyższego pliku pojawia się następujący błąd NameError: niezainicjowana stała UserWithClient –

0

Powyższa odpowiedź sprawiała mi kłopoty. Podczas sprawdzania wyjątkowości użyj nazwy kolumny zamiast nazwy modelu. Zmień validates_uniqueness_of: user na validates_uniqueness_of: user_id.