Mike S
Mike S

Reputation: 11409

Mongoid has_many relation returning fake data when enumerated

I will give a quick background. I am building a directed graph in Rails with the following (summarized) models:

Node
has_many :edges, foreign_key: "source_id"

and

Edge
field :source_id, :type => String
field :destination_id, :type => String
belongs_to :source, class_name: "Node"
belongs_to :destination, class_name: "Node"

I have come across 2 bizarre issues with Mongoid has_many and belongs_to relationship. The results of the relational query seem to vary depending on what method I use to retrieve the Node object.

First, calling to_a on the relation (as well as enumerating over the relation, i.e. each, map, collect) causes an extra Edge to be retrieved, as seen below where the count returns as 31.

Second, the issue with the extra Edge only seems to occur when the query that retrieves the Node is different than a straight forward find. But as you can see, according to Rails node1 is equal to node2.

I would greatly appreciate if anyone can shed any light on these issues. Let me know if additional information would be helpful.

1.9.3-p327 :145 > node1 = some_other_node.neighbors.select {|n| n[:node].city == "983"}.first[:node]
 => #<Node _id: 54da32b1756275343ed70300, city: "983"> 

1.9.3-p327 :140 > node2 = Node.find_by(:city => "983")
 => #<Node _id: 54da32b1756275343ed70300, city: "983"> 

1.9.3-p327 :141 > node1 == node2
 => true 

1.9.3-p327 :150 > node1.edges.count
 => 30 
1.9.3-p327 :151 > node1.edges.to_a.count
 => 31 
1.9.3-p327 :152 > node2.edges.count
 => 30 
1.9.3-p327 :153 > node2.edges.to_a.count
 => 30

EDIT Providing information about the extra edge.

The extra Edge that is returned should absolutely not be returned. You can see below that the extra Edge has a source that is not equal to node1. Every other Edge that is returned has node1 as the source, which is precisely what I expect based on the relationship.

1.9.3-p327 :167 >   node1.edges.to_a.last.source
 => #<Node _id: 54da32b0756275343e000000, city: "0"> 

Here is more evidence to support my claim. Notice that all of node2 edges have node2 as the source, but this is not true for node1.

1.9.3-p327 :170 > node2.edges.to_a.collect {|e| e.source == node2}.include? false
 => false 
1.9.3-p327 :171 > node1.edges.to_a.collect {|e| e.source == node1}.include? false
 => true

However, it is peculiar that the extra Edge would have city: "0" as the source because in the initial assignment of node1 the some_other_node is city: "0". That does not seem like a coincidence.

Upvotes: 1

Views: 191

Answers (1)

Mike S
Mike S

Reputation: 11409

Issue resolved! Thanks for the suggestion mu is too short. When I attempted to implement both out_edges and in_edges on the Node I discovered that Mongoid was complaining about the inverse relation being ambiguous. Interestingly, it was happily silent when I was only defining the out_edges.

Just to clarify, It is not required to define both out_edges and in_edges, I was able to make the defect go away by including the inverse_of definitions in the Edge model.

Node
    has_many :edges, foreign_key: "source_id"

Edge
    field :source_id, :type => String
    field :destination_id, :type => String
    belongs_to :source, inverse_of: "edges", class_name: "Node"
    belongs_to :destination, inverse_of: "in_edges", class_name: "Node"

EDIT

Rails/Mongoid do not appear to care that destination is defined with inverse_of: "in_edges" even though in_edges does not exist in Node. But if I remove that part then the issue returns. Mongoid definitely has some defects here.

Upvotes: 1

Related Questions