11

In dem Projekt, das ich derzeit unter Rails 4.0.0beta1 entwickle, benötigte ich eine benutzerbasierte Authentifizierung, bei der jeder Benutzer mit einer Entität verknüpft werden konnte. Ich bin neu in Schienen und hatte einige Probleme damit.has_one through und polymorphe Assoziationen über multi-table Vererbung

Das Modell ist wie folgt:

class User < ActiveRecord::Base 
end 

class Agency < ActiveRecord::Base 
end 

class Client < ActiveRecord::Base 
    belongs_to :agency 
end 

Was ich brauche, ist für einen Benutzer in der Lage sein, zu verknüpfen entweder eine Agentur oder einen Client, aber nicht beide (die beide sind, was ich Entitäten nennen würde). Es kann überhaupt keine Verbindung und höchstens eine Verbindung haben.

Das erste, was ich suchte war, wie Mutli-Table inheritance (MTI) in Schienen zu tun. Aber einige Dinge blockierten mich:

  • es aus der Box nicht verfügbar war
  • MTI sah irgendwie schwer für einen Neuling zu implementieren, wie ich
  • die Edelsteine ​​der Umsetzung der Lösungen schienen alt und entweder zu complexe oder nicht vollständig
  • die Edelsteine ​​wahrscheinlich brach unter rails4 hätte, wie sie

so eine Zeit lang nicht aktualisiert worden war ich für eine andere Lösung gesucht und ich fand polymorphic associations.

Ich habe seit gestern auf dieser und dauerte einige Zeit, um es noch von Rails polymorphic has_many :through mit Hilfe funktioniert und ActiveRecord, has_many :through, and Polymorphic Associations

ich es geschafft, die Beispiele von der Frage über Arbeit zu machen, aber es dauerte eine Weile, und ich habe endlich zwei Probleme:

  1. Wie transformiert man die Beziehungen in einem Benutzer in eine has_one-Assoziation und kann "blind" auf die verknüpfte Entität zugreifen?
  2. Wie wird eine Einschränkung festgelegt, sodass kein Benutzer mehr als eine Entität haben kann?
  3. Gibt es einen besseren Weg zu tun, was ich will?

Antwort

11

Hier ist ein voll funktionsfähiges Beispiel:

Die Migrationsdatei:

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 

Die Modelle:

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 

Wie Sie ich einen Getter sehen können und einen Setter fügte hinzu, dass i benannte "Entität". Das ist, weil has_one :entity, through: :user_entity die folgenden Fehler auslöst:

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. 

Schließlich sind hier die Tests i eingerichtet. Ich gebe sie so, dass jeder versteht, dass Sie Daten zwischen diesen Objekten festlegen und darauf zugreifen können. Ich werde meine Modelle nicht factory werden detailliert, aber sie sind ziemlich offensichtlich

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 

Ich Hoffe, dass dies eine Hilfe für jemanden sein kann.Ich habe mich entschieden, die ganze Lösung hier zu platzieren, weil es mir im Vergleich zu MTI wie eine gute Sache erscheint und ich denke, dass es nicht viel Zeit brauchen sollte, um so etwas aufzusetzen.

+0

@ Crystark Ich bekomme den folgenden Fehler beim Testen der obigen Datei NameError: nicht initialisierte Konstante UserWithClient –

0

Die obige Antwort gab mir einige Schwierigkeiten. Verwenden Sie bei der Überprüfung der Eindeutigkeit einen Spaltennamen anstelle eines Modellnamens. Ändern Sie validates_uniqueness_of: user in validates_uniqueness_of: user_id.

Verwandte Themen