Модель пользователя Ruby On Rails для нескольких типов

Я изучаю RoR, исходящий из многих лет С# и MSSQL.

Я выбрал проект для создания веб-сайта для моего брата, который является менеджером аренды недвижимости. Я полагал, что это должно быть довольно легко, так как модели должны быть прямолинейными, но он думает, что я, возможно, передумал все, или у меня возникли проблемы с выходом из "старого пути". Во всяком случае здесь проблема. Я начинаю с двух моделей (User и Property). Модель собственности проста, пользователь не так много. Я понял, что у нас есть три типа пользователей в системе. Арендаторы, владельцы и управляющие (мой брат будет единственным менеджером, но я решил, что я буду его разрабатывать). Он управляет свойствами нескольких владельцев, каждый из которых может владеть многими свойствами. В каждом имуществе будет один владелец, один арендатор и один ясли.

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

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

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

class AddProfileDataToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :first_name, :string
    add_column :users, :last_name, :string
     add_column :users, :address1, :string
     add_column :users, :address2, :string
     add_column :users, :city,:string
     add_column :users, :state, :string
     add_column :users, :zip, :string
     add_column :users, :phone, :string
     add_column :users, :email, :string
     add_column :users, :user_type, integer
  end

  def self.down 
    remove_column :users, :first_name 
    remove_column :users, :last_name
   remove_column :users, :address1
   remove_column :users, :address2
   remove_column :users, :city
   remove_column :users, :state
   remove_column :users, :zip 
   remove_column :users, :phone 
   remove_column :users, :email 
   remove_column :users, :user_type
  end
end

Вот код для создания таблицы свойств

class CreateProperties < ActiveRecord::Migration
  def self.up
    create_table :properties do |t|
      t.string :address
      t.string :city
      t.string :type
      t.integer :beds
      t.float :baths
      t.float :price
      t.float :deposit
      t.string :terms
      t.string :laundry
      t.datetime :date_available
      t.integer :sqft
      t.integer :owner_id
      t.integer :manager_id
      t.integer :tenant_id
      t.timestamps
    end
  end

  def self.down
    drop_table :properties
  end
end

Я добавил в модель пользователя, созданную генератором nifty_authentication

class User < ActiveRecord::Base

  #other stuff in the user model up here......
  validates_length_of :password, :minimum => 4, :allow_blank => true

  #this is the stuff that I have added to the user model
  has_many :managed_properties, :class_name => "Property", :foreign_key => "manager_id"
  has_many :owned_properties, :class_name => "Property", :foreign_key => "owner_id"
  has_one :rented_property, :class_name => "Property", :foreign_key => "tenant_id"

Затем я добавил это в модель свойств....

class Property < ActiveRecord::Base
    belongs_to :manager, :class_name => "User" #picked up by the manager_id
    belongs_to :owner, :class_name => "User"  #picked up by the owner_id
    belongs_to :tenant, :class_name => "User"  #picked up by the tenant_id
end

Мой вопрос в том, выглядит ли это как приемлемый способ моделирования ситуации, которую я описал?

Должен ли я использовать наследование одиночной таблицы и создавать модель арендатора; модель менеджера; и модель владельца? Проблема, с которой я столкнулся, заключалась в том, что один пользователь может быть как менеджером, так и владельцем. Это может быть разрешено к тому времени, имея таблицы ролей для пользователя, где у пользователя много ролей, а роль имеет много пользователей. Я также посмотрел таблицу профилей с индивидуальным совпадением с пользовательской таблицей и сделал это полиморфным, но я не думал, что эта ситуация действительно требует этого, и это не решило проблему, когда пользователь может быть владельцем и менеджер.....

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

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

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

Спасибо за понимание, которое у вас может быть.

Ответ 1

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

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

Для этого я могу создать User factory, который может проверять пользователя за различные роли, которые он играет, и расширить singleton class этого пользователя с соответствующим модулем: расширение с помощью модуля Tenant для пользователей, обладающих свойствами арендатора, расширения с помощью модуля Manager для пользователей, у которых есть управляемые свойства и т.д.

С точки зрения declarative_authorization вы можете объявить role_symbols аналогичным образом на основе того, были ли заполнены такие ассоциации, как managed_properties, например:

def role_symbols  
  @roles ||= {:manager => :managed_properties, 
    :tenant => :rented_property, 
    :owner => :owned_properties}.map do |k,v|
      k if !send(v).blank?
    end.compact
end

Или что-то подобное. Вероятно, вы захотите установить роли и включить соответствующий модуль одновременно. Ruby and Rails предоставляет вам много возможностей с помощью методов метапрограммирования для динамического декорирования с учетом потребностей отдельных моделей. Мой подход может быть или не быть подходящим для вашего приложения, но есть множество других способов, по которым вы могли бы подойти к проблеме, и сохранить код чистым и сухим.

В целом мне кажется, что ваша модель данных звучит, и ваш инстинкт не использовать STI для управления несколькими ролями. Мне не кажется, что ты слишком задумываешься - я думаю, ты на правильном пути. Это на самом деле довольно Rails-y для первого прохода.

EDIT: Знаешь, чем больше я думаю об этом, тем не менее я не уверен, что на самом деле полезно сохранить функциональность менеджера/арендатора/владельца в отдельных модулях. В моем бывшем воплощении в качестве Java/С# я был бы в курсе SRP/IOC и всего разделение проблем. Но в Ruby and Rails это не так много, как сделка, так как она динамически типизирована и сочетается не так велика или, по крайней мере, такая же, что и в статически типизированных средах. Вы вполне можете просто разместить все отдельные функции роли в единой модели пользователя и не волноваться с модулями, по крайней мере, пока.

Я нахожусь здесь на заборе и буду приветствовать вклад от других. Для меня одно из преимуществ классов Ruby, в отличие от классов/пакетов Java или классов/сборок .NET, заключается в том, что вы всегда можете рефакторировать по мере необходимости и не быть почти обеспокоены тем, какой класс, пакет, пространство имен, dll или jar связанный с другим и т.д. Я не говорю, что SRP не важен в Ruby, совсем нет. Но я не так параноик об этом, как раньше.

EDIT: Пол Рассел отлично подходит. Я думаю, вы должны серьезно подумать о том, чтобы позволить нескольким арендаторам/управляющим/помещикам на собственность. В Rails это может быть выражено через реляционную таблицу и has_many: через ассоциацию, плюс STI для описания различных типов отношений. Я также думаю, что необходимо будет изменить отношения между Пользователем (как Арендатором) и Имуществом. Имущество может иметь более одного Арендатора, но Арендатор не может проживать в более чем одном собственности. (или, может быть, они могут? Не кажутся прав, но...)

Может быть, что-то вроде этого (это очень быстро и грязно, поэтому простите пропущенные подробности):

class PropertyRole < ActiveRecord::Base
  belongs_to :user
  belongs_to :property
end

class Managership < PropertyRole
  # Manager functionality here
end

class Ownership < PropertyRole
  # Owner functionality here
end

class User < ActiveRecord::Base
  belongs_to :residence, :class_name => 'Property', :foreign_key => 'residence_id'
  has_many :managerships
  has_many :ownerships

  has_many :owned_properties, :through => :ownerships, :classname => 'Property'
  has_many :managed_properties, :through => :managerships, :classname => 'Property'
end

class Property < ActiveRecord::Base
  has_many :tenants, :class_name => 'User', :foreign_key => 'residence_id'
  has_many :managerships
  has_many :ownerships
  has_many :owners, :class_name => 'User', :through => :ownerships
  has_many :managers, :class_name => 'User', :through => :managerships
end

Как я уже сказал, это быстро и грязно, на первом уровне. Обратите внимание, что теперь мы создали роли управления и владения, которые могут содержать функциональные возможности менеджера и владельца, устраняя прежнюю дилемму относительно того, следует ли использовать эту функциональность в отдельных модулях.

** Обратите внимание, что я также перевернул роль "Арендатор/Свойство" - я думаю, это было необходимым изменением для вашего домена. Очевидно, что в резиденции может быть более одного арендатора. Мне кажется (на данный момент), что вы можете сохранить функциональность, связанную с арендатором, в модели User.

Ответ 2

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

В Великобритании, по крайней мере, очень часто возникает ситуация, когда собственность принадлежит супружеской паре и поэтому может иметь несколько помещиков (например, моя жена и я на этом месте). Точно так же для некоторых типов допуска очень часто существует несколько арендаторов в одном свойстве.

Если вы думаете, что я здесь, стоит подумать о введении объектного объекта user_property_role между пользователем и его собственностью. Тогда у вас должно быть отношение has_many от пользователя и свойства к user_property_role. Если эта роль имела поле типа отношения, которое вы могли бы установить, например, "landlord", вы могли бы использовать объекты has_many: through и named (на объекте user_property_role), например, property.users.landlords или property.users.tenants.

Принятие этого подхода также позволит вам делать такие вещи, как давать эти отношения "начать" и "завершать" даты, записывая тот факт, что, например, свойство имеет несколько арендаторов с течением времени или что свойство может управляться разными люди со временем. Опять же, вы должны иметь возможность построить это в наборе названных областей, чтобы вы могли сделать это, например. property.users.current.tenants или даже user.properties.current.tenants.

Надеюсь, что это поможет, и это не просто добавляет к вашей путанице!