You can clone with
HTTPS or Subversion.
I'm experimenting with the public domain MusicBrainz database and learning DataMapper at the same time. So I'm starting off slow:
property :id, Serial
property :gid, String, :auto_validation => true
belongs_to :name, :model => 'ArtistName'
property :id, Serial
property :name, String
This would work great except for one thing: The parent table, "artist", really doesn't use the name "name_id" to link to the "artist_name" table; instead, it uses "name".
For the life of me I cannot work out any parameters to :belongs_to that will make this work. Adding :parent_key => 'name' fails because then it looks for a column named "name_name". Adding :child_key => 'name' errors out with the message "SystemStackError: stack level too deep".
I have a sneaky feeling the problem is the code in associations/many_to_one.rb that checks to see if the source key (a.k.a. parent key, yes?) has been renamed. But it's too late in the evening for me to wrap my brain around it. :-)
UPDATE: Okay, so I figured out that if I give the relationship a different name like :the_name and use :child_key => "name", I can get this to work. I was just hoping there was a way to do it while still using :name for the relationship and without having to rename columns in the table. Am I missing something obvious or could this be considered a bug?
The issue here is attempting to define both a Property and a Relationship with the same name. By default, belongs_to implicitly defines a Property to house a foreign key, which can be overridden by specifying an existing Property using the :child_key option. In this case, passing [:name] as the :child_key will cause a SystemStackError because DataMapper goes round in circles.
Try giving the child key Property a distinct name, eg:
belongs_to :name, :model => 'ArtistName', :child_key => [:name_value]
Or alternately, give the Relationship a distinct name, eg:
belongs_to :artist_name, :model => 'ArtistName', :child_key => [:name]
Hope that helps.
Actually, I did not look closely enough at your schema. The :name_id value DataMapper proferred is perfectly appropriate here: Artist.name_id will hold a foreign key relating to the ArtistName.id primary key. Thus, Artist.name_id is not going to be a string value, it is going to be the integer that identifies the ArtistName resource whose name property holds the string value.
If you want to relate Artist and ArtistName on the artist's actual name as a string value, be forewarned that there are good arguments for using synthetic keys (ie., when natural keys change—as they have a tendency to do, despite expectations to the contrary—it can cause headaches). If you still want to relate the two models on the artist name string, try this:
belongs_to :artist_name, :model => 'ArtistName', :child_key => [:name], :parent_key => [:name]
This will configure the Relationship to define an Artist.name property as a foreign key to the ArtistName.name property.
Hope that helps :).
As an addendum to what emmanuel says about it being a pain when natural keys change, you might want to look into using dm-constraints. If you have a compatible database (MySQL or Postgres definitely are) you can update the FKs appropriately.
@L2G — your update and the issue both leave vague the question of how you are trying to relate these two Models. Either they can be related on an integer foreign key to primary key (eg., Artist.name_id -> ArtistName.id). This is the 'synthetic key' approach.
OR the two models can be related on a natural key. In this case the ArtistName.name string is the only candidate I see. Going this route would result in a mapping like Artist.name_value => ArtistName.name. This is the 'natural key' approach.
If you pursue the natural key approach, understand the restriction that a property and relationship can't share a single name. If control of the column name in the DB is your concern, another thing you could do is to explicitly declare the foreign property on Artist and give it an alternate field name:
property :name_value, Integer, :field => :name
belongs_to :name, :model => 'ArtistName', :child_key => [:name_value], :parent_key => [:name]
This creates an Artist.name_value property in memory that DataMapper maps to an artists.name column in your DB. I thin k this is probably what you want to do.
BTW, please add any updates as new comments, because I get notified when new comments are added, but I don't get any notifications when existing content is updated.
Gotcha. Thanks for your help. I will try your last example when I get a chance, as it looks like an ideal solution. I really appreciate the help with this!
The last solution does not work on the master branch. This seems to be a bug. This may be the only solution.