bswinnerton
bswinnerton

Reputation: 4721

Proper way to set up "friends" (bidirectional relationship) in Neo4j.rb

What would be the proper way to create a relationship between Users that have a bidirectional relationship?

Say I have:

class User
  include Neo4j::ActiveNode

  property :name,   type: String
  property :created_at, type: DateTime
  property :updated_at, type: DateTime

  has_many :both, :friends, model_class: 'User', type: 'connection' unique: true
  has_many :in, :followers, model_class: 'User', type: 'connection', unique: true
  has_many :out, :following, model_class: 'User', type: 'connection', unique: true
end

And then,

users = [
  User.create(name: 'Foo'),
  User.create(name: 'Bar'),
  User.create(name: 'Baz'),
]

Would this be the appropriate way to do it? It seems wildly inefficient:

users.each do |user|
  user.friends << users.reject { |u| u == user }
end

Upvotes: 2

Views: 283

Answers (1)

subvertallchris
subvertallchris

Reputation: 5472

If you want to ensure that user.friends returns the other two users, I'd actually do this:

users.each do |user|
  other_users = users.reject { |u| u == user || user.friends.include?(u) }
  user.friends << other_users
end

Now that is wildly inefficient! It creates a new array and performs an additional lookup against the database for each user. (FWIW, the include? lookup is very fast. You can also remove that reject and iterate through users once again to speed it up.) Still, it's necessary to accomplish this because otherwise, you'll create duplicate relationships.

Setting unique: true in your association is only going to save you in one direction. Queries against has_many :both associations use a direction-agnostic Cypher MATCH but CREATE actions always need direction, so they go from the node on the left to the node on the right. user1.friends << user2 and user2.friends << user1 will create two relationships between the two nodes, one in each direction.

EDIT

As you pointed out in your own comment to an issue on Neo4j.rb, using user.friends = [other_friends] will always clear out all existing relationships and prevent duplicates. It's a really cool way of solving the problem.

Upvotes: 2

Related Questions