11

В проекте, который я сейчас разрабатываю под rails 4.0.0beta1, мне потребовалась аутентификация на основе пользователя, в которой каждый пользователь мог быть связан с сущностью. Я новичок в рельсах и испытываю некоторые неприятности.has_one through и полиморфные ассоциации над наследованием нескольких таблиц

Модель выглядит следующим образом:

class User < ActiveRecord::Base 
end 

class Agency < ActiveRecord::Base 
end 

class Client < ActiveRecord::Base 
    belongs_to :agency 
end 

Что мне нужно для пользователя, чтобы иметь возможность связать либо учреждения или клиента, но не оба (те два, что я буду называть объекты). Он не может иметь никакой ссылки вообще и не более одной ссылки.

Первое, что я искал, было как сделать Mutli-Table inheritance (MTI) в рельсах. Но некоторые вещи заблокировали меня:

  • он не был доступен из коробки
  • MTI выглядел своего родом трудно реализовать для новичка вроде меня
  • драгоценных камней, реализующих решения, казалось старыми и либо слишком Complexe или не полный
  • драгоценные камни бы, вероятно, сломал под rails4, поскольку они не были обновлены на некоторое время

так я искал другое решение, и я нашел polymorphic associations.

Я бы на это, так как вчера, и потребовалось некоторое время, чтобы заставить его работать даже с помощью Rails polymorphic has_many :through и ActiveRecord, has_many :through, and Polymorphic Associations

мне удалось сделать примеры из вопроса выше работы, но потребовалось некоторое время, и я наконец, есть две проблемы:

  1. Как преобразовать отношения в пользователе в ассоциацию has_one и иметь доступ к «слепому» связанному объекту?
  2. Как установить ограничение, чтобы ни один пользователь не мог иметь более одного объекта?
  3. Есть ли лучший способ сделать то, что я хочу?

ответ

11

Вот полностью рабочий пример:

Файл миграции:

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 

Модели:

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 

Как вы можете видеть, я добавил геттер и сеттер, что я «сущность». Это потому, что has_one :entity, through: :user_entity вызывает следующую ошибку:

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. 

Наконец, вот тесты я настраивал. Я даю им так, чтобы все понимали, что вы можете установить и получить доступ к данным между этими объектами. я не буду подробно мои модели FactoryGirl, но они довольно очевидно

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 

Я надеюсь, что это может иметь некоторую помощь к кому-то.Я решил поставить здесь все решение, потому что это кажется мне хорошим, по сравнению с MTI, и я думаю, что не стоит брать кого-то столько времени, чтобы установить что-то подобное.

+0

@Crystark При тестировании вышеуказанного файла возникает следующая ошибка NameError: неинициализированная константа UserWithClient –

0

Вышеуказанный ответ дал мне некоторые проблемы. Используйте имя столбца вместо имени модели при проверке уникальности. Изменить validates_uniqueness_of: пользователь для validates_uniqueness_of: user_id.

Смежные вопросы