Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Providing support for :inverse_of as an option to associations.
You can now add an :inverse_of option to has_one, has_many and belongs_to associations. This is best described with an example: class Man < ActiveRecord::Base has_one :face, :inverse_of => :man end class Face < ActiveRecord::Base belongs_to :man, :inverse_of => :face end m = Man.first f = m.face Without :inverse_of m and f.man would be different instances of the same object (f.man being pulled from the database again). With these new :inverse_of options m and f.man are the same in memory instance. Currently :inverse_of supports has_one and has_many (but not the :through variants) associations. It also supplies inverse support for belongs_to associations where the inverse is a has_one and it's not a polymorphic. Signed-off-by: Murray Steele <muz@h-lame.com> Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
- Loading branch information
Showing
18 changed files
with
418 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. Nice work.
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, really nice work murray ;)
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice :)
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be the default behavior for has_one, has_many, and belongs_to? What downsides are there to doing this?
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yesss. It's in! : )
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like jrmehle said: why not use this as default?
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is truely a very nice change. Thanks!
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making this the default would require AR to try and work out what the inverse of a given association is when it's not explicitly specified. It's not too hard for the simple-case, it's what the plugin I wrote that formed the basis of this patch did, but there are many edge-cases. Also the whole concept of bi-directionality needs testing out before we go the whole way.
That said hopefully once enough people have tested this it will become the default. Think of it as inverse deprecation ;)
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something that should have been the default a long time ago. Better yet, having an active record identity map so there was only ever one object per row in existence at a time (well, in the same thread). We need to push for this to be the default behaviour. Testing all the edge cases aside, that would only be a positive change imo
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we can make this the 'default' some time before we ship 3.0. But with this option in early, it lets us test the "having an inverse" code independently of the "figuring out what the inverse is" code.
As for an identity map, the issue has come up DOZENS of times on the mailing lists. We'd be happy to investigate it but there are several cases which we support now which aren't easy to do with an identity map without adding some notion of a persistence session with attaching and detaching and the associated errors. This could well be a positive change, but it's not just:
@objects[id] ||= find(id)
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NZKoz: the way we deal with the persistence session in DataMapper is to wrap the code in a repository block: repository() { .. }
IMHO this is not ideal, but it works well enough and doesn't cause us to leak memory in long running processes.
What I'd really like to see is a decent WeakHash implementation for MRI and JRuby. With it (I think) it might be possible to make an Identity Map that doesn't require an explicit scope be defined -- like inside a block -- and would work well for long running processes.
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool! As thing stands I don't think this will work with :include (ie the inverses won't get set). I'll have a think about that, but (at least for non massive join version of include) I don't think it will be too much hassle.
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dkubb: Do you handle unmarshalled objects? e.g. cache-hits from yaml or marshal.load?
If you have person:1 in memcache as part of some serialized association for accounts, and follow a belongs_to association that refers to person:1, you can end up with duplicated instances. This is why hibernate has concepts like attach() and why it throws a bunch of exceptions. There are several other problems but that kind of thing is a little tricky to do right without messing up the usability of the AR API. Especially when combined with useful things like :select and find_by_sql.
So it's not going to be easy, or perhaps even possible to support 100% identity, without removing some features or adding horrible random exceptions which people can and will hit in 'real life'. I think we can move in the right direction gradually and get to 90% without 'making it hard to use'. That last 10% could be quite a slog though.
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fcheung: good point, let me know if you do take a look at it
ccea983
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've stuck a patch up at https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2621 I've had a chat with h-lame who has also had a stab at it and we independently came up with pretty much the same thing