Rails: PolyMorphic или STI или что-то еще для управления пользователями?

Я бился головой о стену, пытаясь обернуть голову вокруг этого, так что любые рекомендации были бы очень признательны ...

Я хочу, чтобы настройка системы пользователя отражала следующую иерархию:

User
|- email address
|- password
|- billing information
|- contact information
|- account preferences
|
|=> Agent
|=> - agent-specific information
|=> - has_many Users
|=> - belongs_to Manager
|
|=> Manager
|=> - manager-specific information
|=> - has_many Agents, Users
|
|=> Administrator
|=> - can manage everything

У меня уже есть User модель с Devise и CanCan настройка для обработки аутентификации и авторизации, поэтому я знаю, как использовать роли, чтобы ограничить тип пользователя определенными действиями и т. д.

Я не понимаю, как организовать эти отношения между подклассами как в моем коде Rails, так и в базе данных. Как видно из вышеизложенного, все Agent, Manager и Administrator совместно используют информацию, содержащуюся в User, но каждый из них имеет дополнительные функции И связанную с ним информацию.

Я читал кое-что о STI, полиморфные ассоциации и самореференциальные ассоциации.

Если я использую STI, таблица User должна будет содержать поля для всей моей [_8 _ / _ 9 _ / _ 10 _] информации, верно? Это сделало бы мою User таблицу огромной, чего я бы хотел избежать. И наоборот, если я использую полиморфизм, разве мне не придется дублировать всю общую информацию в User на все другие типы User таблиц подклассов?

И чтобы добавить к моему недоумению, я не могу обернуться вокруг того, как ответ на вышеупомянутый вопрос будет работать с отношениями между подклассами (например, что Manager has_many Agents, но оба являются подклассами _16 _...? ?).

Я был бы очень признателен, если бы кто-то объяснил мне это с помощью подробного ответа, в котором должным образом учитывались бы читаемость кода и целостность данных, который просто объясняет (как если бы новичку Rails), почему A - лучший подход и почему B или n - для сравнения - не лучший подход для этой ситуации, и это дает пример кода для реализации отношений, описанных выше. Я хочу решить эту проблему, но, что более важно, я хочу узнать, почему это решение работает!


person neezer    schedule 11.12.2010    source источник


Ответы (2)


Я не думаю, что есть простое объяснение тому, почему любой подход является лучшим в любых обстоятельствах. Значительное количество вопросов по Stack Overflow - это вопрос о том, как проектировать отношения между их моделями. Это сложная тема, и правильные решения требуют глубокого знания проблемы, которую вы решаете. И даже тогда вы, вероятно, не поймете это правильно первые пару раз.

На данный момент лучший способ - полностью проработать TDD / BDD в этом вопросе и позволить тестам / спецификациям вытеснить ваш дизайн. И не бойтесь рефакторинга, если найдете лучший способ. В большинстве случаев вы увидите правильное решение только после того, как попробуете пару неправильных. Вы познакомитесь с крайними случаями. Как выразился Уорд Каннингем в своей аналогии с «Техническим долгом»: «Рефакторинг впоследствии, как если бы вы знали, что делаете с самого начала». Не забудьте пройти приемочные испытания, чтобы потом проверить его поведение.

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

У меня есть модели администратора, рекрутера, поставщика и посетителя. Все они являются отдельными моделями, разделяющими некоторое поведение с миксинами. У всех есть свои собственные контроллеры с именами, с которыми нужно работать. Например, все действия для администраторов находятся в пространстве имен Backend. Также существует ApplicationController с пространством имен. Backend :: ApplicationController просто указывает before_filter :authorize_admin!. Никаких переключений, никаких сложных case-операторов, ничего.

Особое внимание нужно уделять условностям. Если вы используете одни и те же имена во всех моделях, ваши миксины могут стать очень простыми. Прочтите ActiveSupport :: Concern, чтобы упростить работу с миксинами. У меня такой миксин:

module Account
  extend ActiveSupport::Concern
  included do
    devise :database_authenticatable, :trackable, :recoverable, :rememberable
  end
end

И в моих маршрутах:

devise_for :recruiters
devise_for :suppliers
# etc...

И app/controllers/backend/application_controller.rb выглядит так:

class Backend::ApplicationController < ::ApplicationController
  layout "backend"
  before_filter :authenticate_admin!
  def current_ability
    @current_ability ||= AdminAbility.new(current_admin)
  end
end

Итак, в заключение. Подойдет любая архитектура. STI и полиморфизм имеют свое место, но не забудьте смоделировать свою архитектуру в соответствии с вашей областью. Ruby - очень гибкий язык, и вы можете использовать это в своих интересах. Devise и CanCan - отличные жемчужины, с легкостью справляющиеся с подобными ситуациями. Я показал вам свое решение для проекта, над которым сейчас работаю. Мне он подходит, но я не могу сказать, подходит ли он вам. Не бойтесь экспериментировать и проводить рефакторинг, когда чувствуете, что приняли неправильное решение, вместо того, чтобы продолжать исправлять свою первоначальную идею.

PS. Кстати об ИППП и отношениях: они тоже отлично работают вместе. Вы можете определять отношения от одного подкласса к другому подклассу. Все будет работать как положено.

person iain    schedule 11.12.2010
comment
Я прочитал несколько других статей, посвященных модулям и миксинам, но понятия не имею, что это такое. Есть ли в Интернете хороший ресурс, где я могу прочитать, что это такое и как их использовать? Я не мог почерпнуть много из официальных документов Rails ... - person neezer; 11.12.2010
comment
Модули не являются специфическими для Rails, поэтому они не упоминаются активно в документации Rails. В любой документации по метапрограммированию Ruby они будут подробно упоминаться, потому что они являются важной частью Ruby. Больше всего я узнал из скринкастов Дэйва Томаса < / а>. Подходит не только для изучения Ruby, но и для изучения объектно-ориентированного программирования в целом. - person iain; 11.12.2010
comment
Спасибо; Я обязательно посмотрю на них! - person neezer; 11.12.2010
comment
Я читал о модулях и миксинах - любезно предоставлено книгой Picaxe - но все примеры, которые я нашел до сих пор, говорят о модулях и миксинах в чистой среде Ruby, а не в Rails. Где я могу определить модули в Rails? - person neezer; 12.12.2010
comment
Отличный ответ! Миксины используются недостаточно. - person zetetic; 12.12.2010
comment
@neezer где хочешь. Большинство людей создают каталог с именем app/concerns, app/behavior или app/traits или используют lib-каталог. Если вы выходите за пределы каталога приложений, вам необходимо указать autoload_paths в config / application.rb. - person iain; 12.12.2010

Может возникнуть соблазн переоценить это, но я не уверен, что то, что вы хотите сделать, очень сложно.

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

Что касается хранения информации о членстве, я не уверен, что ваши таблицы будут «огромными», когда вы добавите столбцы для STI. Если они равны нулю, они на самом деле не потребляют никаких ресурсов. Если вам не нужно выбирать эти данные, вы можете подумать о том, чтобы создать один столбец role_information, а затем использовать serialize :role_information, чтобы вы могли определить хеш и сохранить его в столбце.

Другой вариант - переопределить методы таким образом, чтобы они проверяли принадлежность к роли при ответе, то есть:

has_many :agents
def agents
  return nil unless self.has_role(:manager) 
  return self[:agents]
end

Замените has_role на то, что вам нужно для проверки членства в роли.

Вы также можете сделать это на стороне настройки.

Авторизация для определенных функций должна выполняться в вашем контроллере, поэтому вам следует проверить членство в роли на этом этапе.

person aceofspades    schedule 11.12.2010
comment
Это создаст огромное количество операторов if / if по всему вашему коду. Это сложно протестировать, и это может привести к большим сложностям и ошибкам. Старайтесь не делать классы, которые представляют более одного предмета. - person iain; 11.12.2010