Не знаете, как смоделировать отношение «многие ко многим» с флагом в Mongoid - PullRequest
1 голос
/ 30 октября 2011

У меня есть две сущности, проекты и пользователи. Они моделируются в Rails с использованием Mongoid с двумя экземплярами Document, User и Project.

В этой системе один пользователь может создать один проект, но многие пользователи могут следить за многими проектами. Например, как user_id 1, я создал project_id 1. Но user_ids 10, 11, 40 и 60 все следуют project_id 1. Мне нужно представить отношение «многие ко многим» между пользователями и проектами и представить конкретный user_id как Создатель проекта, назначить ему права на редактирование.

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

В СУБД я представлял бы это с помощью таблиц users, projects и таблицы соединений users_projects с флагом is_creator. Это легко позволило бы мне выбрать, какие проекты может видеть пользователь, а какие пользователи являются последователями, включая пользователей, которые являются создателями проектов.

Mongoid поддерживает отношения «многие ко многим», но, в отличие от СУБД, я не могу установить флаг для отношений. Вместо этого я думаю, что добавлю поле creator к документу projects, которое будет содержать ссылку на поле _id в документе пользователя.

Отношения пользователь-> проекты могут выглядеть следующим образом

class User
  has_and_belongs_to_many :projects
end

class Project
  has_and_belongs_to_many: users
end

Но я не могу понять, как отобразить отношение creator-> creation_projects. Я полагаю, что могу ссылаться на создателя пользователя в Project как belongs_to :creator, :class_name => 'User', но я не уверен, как настроить другую сторону.

Как лучше всего смоделировать эти отношения в Mongoid?

Ответы [ 2 ]

2 голосов
/ 31 октября 2011

вторая версия использует меньше места, но вам нужен дополнительный запрос для получения таких данных пользователя, как имена пользователей. ID объекта имеет 12 байтов, макс. размер документа составляет 16 Мб, поэтому массив может содержать около 1,3 млн пользовательских идентификаторов (теоретически!) ..

здесь вы идете:

user.rb

class User
  # projects this user owns
  has_many :projects
  has_many :followed_projects, 
           :class_name => 'Project', 
           :foreign_key => :follower_ids

  # Uncomment if the relation does not work
  #def followed_projects
  #  Project.where(:follower_ids => self.id)
  #end

  # get projects that this user has created and projects he is following
  def related_projects
    Project.any_of({:user_id  => self.id}, {:follower_ids => self.id})
  end
end

project.rb

class Project
  # creator
  belongs_to :user

  field :follower_ids, :type => Array

  # adds a follower
  def add_follower!(user_obj)
    # adds a user uniquely to the follower_ids array
    self.add_to_set(:follower_ids, user_obj.id)
  end

  def remove_follower!(user_obj)
    # remove the user
    self.pull(:follower_ids, user_obj.id)
  end
end

Как с ним работать:

@project    = Project.first
@some_user  = User.last

@project.add_follower!(@some_user)

@some_user.followed_projects
@some_user.related_projects

# create hash like @ids_to_user[user_id] = user
@ids_to_users = User.find(@project.follower_ids).inject({}) {|hsh, c_user| hsh[c_user.id] = c_user; hsh}

@project.followers.each do |c_follower|
  puts "I'm #{@ids_to_users[c_follower].username} and I'm following this project!"
end

@project.remove_follower!(@some_user)
1 голос
/ 30 октября 2011

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

Преимущества:

  • Ни одного запроса для поиска подписчиков проекта
  • Только один запрос для поиска пользовательских проектов

Недостатки:

  • Если пользователь меняет свое имя, вам придется обновлять все его «подписки», но как часто Вы меняете свое имя по сравнению с тем, как часто вы просматриваете ваши последующие проекты;)

  • Если у вас много тысяч подписчиков на проект, вы можете достичь лимита документа 16 МБ

user.rb

class User
  # projects this user owns
  has_many :projects

  def followed_projects
    Project.where('followers.user_id' => self.id)
  end

  # get projects that this user has created and projects he is following
  def related_projects
    Project.any_of({:user_id  => self.id}, {'followers.user_id' => self.id})
  end
end

project.rb

class Project
  # creator
  belongs_to :user

  embeds_many :followers

  # add an index because we're going to query on this
  index 'followers.user_id'

  # adds a follower
  # maybe you want to add some validations, preventing duplicate followers
  def add_follower!(user_obj)
    self.followers.create({
      :user       => user_obj,
      :username   => user_obj.username
    })
  end

  def remove_follower!(user_obj)
    self.followers.destroy_all(:conditions => {:user_id => user_obj.id})
  end

end

follower.rb

class Follower
  include Mongoid::Document
  include Mongoid::Timestamps

  embedded_in :project

  # reference to the real user
  belongs_to :user

  # cache the username
  field :username, :type => String
end

Как с ним работать:

@project    = Project.first
@some_user  = User.last

@project.add_follower!(@some_user)

@some_user.followed_projects
@some_user.related_projects

@project.followers.each do |c_follower|
  puts "I'm #{c_follower.username} and I'm following this project!"
end

@all_follower_user_ids = @project.followers.map{|c| c.user_id}

# find a specific follower by user_id 
@some_follower = @project.followers.where(:user_id => 1234)
# find a specific follower by username
@some_follower = @project.followers.where(:username => 'The Dude')

@project.remove_follower!(@some_user)

PS: Если вы хотите более простое решение, вы можете просто внедрить массив ObjectID (user_ids) в проект и использовать атомарные обновления $addToSet и $pullAll для добавления / удаления подписчика. Но вам понадобится дополнительный запрос типа User.where(:user_id.in => @project.follower_ids) (при условии, что массив называется follower_ids), чтобы получить всех пользователей и их имена;)

...