-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Circular references cause infinite recursion #45
Comments
I don't know what the serializer code looks like, but I'll assume it looks something like this (I know it'll be more complex): def serialize object
serial = Hash.new
object.attributes.each do |attr|
serial[attr.name] = if attr.has_mapper? then
serialize attr
elsif DBTYPES.include? attr then
attr
end
end
end From there we can do this: def serialize object, visited = Hash.new
return visited[object] if visited.has_key? object # return the previously serialized object
serial = visited[object] = Hash.new
object.attributes.each do |attr|
serial[attr.name] = if visited.has_key? attr then # check that this attr is new
visited[attr] # dump serialized data
elsif attr.has_mapper? then
serialize attr, visited
elsif DBTYPES.include? attr then
attr
end
end
end This is a very simplified example, but hopefully the idea makes sense. Also instead of passing visited recursively it could instead be part of a state object thats initialized when the serializer is run. The fun part is that since we're reusing the same Hash object, we can continue to make changes to the serialized hash so we can continue to add attributes to it in a nested fashion. If we need an additional layer of protection against getting that hash confused we just add a Set with object membership to make the lookups faster. |
We'll definitely have to use some sort of data structure to track which objects we've already begun serializing. A Since hashes use |
I've been thinking about this a lot over the past few days and an idea hit me tonight on my way home. Maybe storing circular references isn't a good idea. If they're meant to be circular references, storing the reference both ways is unnecessarily redundant. We can take care of this when the association is loaded: Perpetuity.generate_mapper_for Foo do
attribute :bar, type: Bar
def load_association! objects, attribute
super
if attribute == :bar
Array(objects).each { |object| object.bar.foo = object }
end
end
end This way, when you call |
Well, ideally you'd have an identity map of some sort which would short circuit this problem, but I assumed you had a reason for avoiding that. |
Sure, the identity map is there. But if I'm freshly pulling the |
That makes sense, but then this seems like it needs to be an extension to the identity map, otherwise it'd be duplicating a lot of functionality/code don't you think? |
It's not the identity map's job to link the two objects together, though. Is that what you mean? |
Not to link two objects, no. Hmm. And it shouldn't be the identity map's job to track the object traversal either. When extracting information from the database, it should keep track of what records have been loaded so they are only loaded once as needed... So to revise my assertion: there must need to be another separate (but related?) mechanism along with it to serve an individual "request's" transversal and detect circular references. |
The identity map tracks what's been loaded and will return the same object if you pull it out twice. If you are finding by id, it won't even touch the database to get the object. If you're finding by other criteria, it'll check with the identity map before returning the object and return the one in there. That won't save a DB query, but it does try to maintain only one copy of an object in memory. This way, if you call Reconstructing objects structures from the database is the mapper's job, whether it does the work itself or delegates to another object. |
I did not mean to imply that the identity map did the extraction or conversion, only that it keeps track of what had been loaded. |
Okay, just wanted to make sure we were on the same page. :-) |
I believe so. :) I'm hardly an expert on DBs though, so explanations may be warranted if just to ensure our terminology has the same definitions. |
This code:
… causes this error:
stack level too deep (SystemStackError)
The problem is that when an object is serialized, referenced objects are persisted to get their ids so we can store those references. That is, when the
Foo
'sbar
attribute is serialized, it is inserted into the database to get its id to store in thebar
column, which then repeats the process the other way around, causing infinite recursion.I have no idea how to solve this.
The text was updated successfully, but these errors were encountered: