HABTM mongoid following/follower
Asked Answered
C

1

5

Mongoid ships with .push on a habtm, which sets a habtm relationship in both directions. Although delete will #delete an associated record, there's no documented way to delete only a relationship that I have seen. Is there a better way of doing this?

Is there a better way of ensuring uniqueness?

has_and_belongs_to_many :following, {class_name: 'User', inverse_of: :followers, inverse_class_name: 'User'}
  has_and_belongs_to_many :followers, {class_name: 'User', inverse_of: :following, inverse_class_name: 'User'}

  def follow!(user)
    self.following.push(user) # this pushes the inverse as well
    self.following_ids.uniq!
    self.save!
    user.follower_ids.uniq!
    user.save!
  end

  def unfollow!(user)
    self.following.delete(user.id)
    self.save!
    user.followers.delete(self.id)
    user.save!
  end
Commie answered 20/12, 2011 at 19:25 Comment(0)
G
18

Following code worked fine for me (mongoid 2.3.x):

class User
  include Mongoid::Document

  field :name, type: String

  has_and_belongs_to_many :following, class_name: 'User', inverse_of: :followers, autosave: true
  has_and_belongs_to_many :followers, class_name: 'User', inverse_of: :following

  def follow!(user)
    if self.id != user.id && !self.following.include?(user)
      self.following << user
    end
  end

  def unfollow!(user)
    self.following.delete(user)
  end
end

No inverse_class_name, no save calls, no special handling, but with exclusion of self-following.

The reason is, that mongoid automatically uses dependent: nullify if not added to the relation statement. And with autosave: true the update of relationships get saved (and is only needed for following, because we do not alter followers directly). Without autosave option you need to add a save call in the methods, because mongoid doesn't automatically save relationship updates (since 2.0.0.x).

I put the if-clause as block, so you can alter it with exception handling (else raise FooException).

The .delete(user) is okay, also mentioned in the mongoid docs: http://mongoid.org/docs/relations/referenced/n-n.html (scroll down to "DEPENDENT BEHAVIOUR").

Gutenberg answered 21/12, 2011 at 0:37 Comment(6)
Some great stuff here. However, I did some testing earlier, and fell in to the same trap (unless I'm mistaken): .delete(user) deletes the user from the database, and nullify refers to any references made to the now-deleted userCommie
The .delete is on the .following hash not on the user object itself. Like doc says "Orphan a single child relation". It does the job, I tested it and it works.Gutenberg
The code !self.following.include?(user) is cool when you have not too much users inside the following relationship, but if you've got like 10K users… The performance are not very great :/Camiecamila
@K'ao what can be done then? Would uniq! be any faster? Perhaps the best way would be to handle duplicates gracefully-- although creating 10k tweets wouldn't be the fastest either..Commie
@PeterEhrlich: We're now trying to use the most often atomic persistence (link). Take a look at #add_to_set, your condition ‘include?‘ will be no necessary with it.Camiecamila
Awesome dude. I needed just this feature. You saved my life.Tulley

© 2022 - 2024 — McMap. All rights reserved.