Re: setting up relationships

[prev] [thread] [next] [Date index for 2004/06/22]

From: Yuval Kogman
Subject: Re: setting up relationships
Date: 00:16 on 22 Jun 2004
--O5XBE6gyVG5Rl6Rj
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

On Mon, Jun 21, 2004 at 16:34:48 -0700, steve shapero wrote:
> 1.
> thanks so much for your help.  i did some experimenting, and managed to
> get close to what i want, but not quite. =20
>=20
> Supposing I am defining TrackArtist.  If i do this:
>=20
> __PACKAGE__->has_a (artist =3D> 'Artist' );
>=20
> then i get this error:
> has_a needs a valid column at HasA.pm line 12

That's because the has_a relationship definition is, in plain english:

	when I ask for the 'artist' column of TrackArtist, don't give me the
	ID, instead use the ID to retreive the object in class 'Artist' with
	that ID.

it's a translation.

You can rename your column, if you want to be more consistent (there is
a rationale for naming your tables singularly, and not attaching _id to
column names, because they represent more than they are, most of the
time).

For a debate, see
http://www.class-dbi.com/cgi-bin/wiki/index.cgi?CdbiWishList

For a solution, search for 'accessor_name' on that page. IMHO the
solution is confusing when you're trying to maintain something and have
gotten used to the various default behaviors and terse style you can
write for CDBI in. It defies the idioms. But that doesn't mean it's
incorrect. See stuff about monikers below, and consider that to save
time, every time I edit a relationship in DBDesigner 4 [1], I save, grep
the file for '_id"', and replace that with '"', so that when it next
synchronises with the DB schema, I don't have relationships with
'foo_id' in the columns. (DBDesigner, although cute and functional, does
not allow or make obvious the option of not adding this suffix. I find
it limiting enough that I go to this extent of effort to avoid it).

> But if i do this:
>=20
> __PACKAGE__->has_a (artist_id =3D> 'Artist' );
>=20
> it more or less works in that it compiles.  but when i do

That's on the TrackArtist package. Calling a $trackartist->artist_id
will not return an ID, but an object that ISA Artist.

Which has little to do with

> my $artists =3D $track->artist;

unless you also say that

Track->has_a( artists =3D> [ 'TrackArtist' =3D> artist ] );

> then artists is just a collection of track_ids and artist_ids for the
> given track.  useful, but not nearly as cool as just getting the artist
> names themselves.  why does it barf on artist and insist on getting a
> column name (i.e. artist_id)?

This is because without a column name it doesn't know where to get the
data, and assuming that for the accessor 'foo' we are actually looking
for the field 'foo_id' is neither wise, standard, nor flexible.

> in Artist i have (when it works):
>=20
> __PACKAGE__->has_many (tracks =3D> [ TrackArtist =3D> track_id ])

This works without telling it that the artist we're looking for is in
column 'track_id', because TrackArtist already has_a Artist in column
artist_id, and this knowlege is used to set up the map.

If this is not the case, then has_many with a mapping will use the
moniker of the class, and you will get an error. Try commenting out all
the TrackArtist->has_a parts to see them... ;-)

A moniker of a class is something defined by the module
UNIVERSAL::moniker - it's a "name" for a class. It gives all the classes
in the symbol table a new method (see 'perldoc UNIVERSAL' for the root
class, and what it defines) called moniker, which generates a noun based
on the package namespace. Foo::Goat->moniker will return 'goat'. You can
also ask for the plural form, which is deduced using
Lingua::EN::Inflect, and so forth.

Class::DBI uses the moniker of a class in several places, when you don't
give it concrete knowlege to work with. Look out for this bit, and learn
to recognize it.

> i tried this when using (artist =3D> 'Artist') as above...
>=20
> __PACKAGE__->has_many (tracks =3D> [ TrackArtist =3D> track ])

This is because TrackArtist doesn't have a 'track', but a 'track_id'....

What you need to establish is that you want $track->artists to tell you
who is to blame for that track, and $artist->tracks to tell you what the
artist did.

The data is in a lookup table, so you work through it.

has_many in combination with has_a, joined by mapping allows you to say:

	Make the accessor 'tracks' of class Artist return the value of the
	accessor 'artist' from each row where the object 'tracks' was called
	on was also a foreign key.

That's the has_many part. The ambiguities here are

	1. How do we know in which foreign key to look for the Artist we're
	   calling on

	2. What will be returned by 'track'. (not really an ambiguity, but
	   rather, an important detail)

Number 1 is resolved by declaring beforehand that TrackArtist has_a
Artist in column 'artist_id' (or otherwise), so for $tori_amos->tracks,
we are interested in all the rows of TrackArtist where artist =3D
$tori_amos (well, $tori_amos->id to be exact, but there are issues with
this ;-). Then it will return the ->track accessor's return value, for
each one of these rows. If we did not declare a
Trackartist->has_a(artist_id =3D> 'Artist') beforehand, Class::DBI will
assume the column for class 'Artist' is Artist->moniker (eq "artist").
I'm not sure if it will create a has_a relationship based on the
moniker, or just use the moniker for the mapping.

Issue 2 is resolved by declaring the counterpart of TrackArtist has_a
artist - TrackArtist->has_a(track_id =3D> 'Track'). When given a
$trackartist, if you say $trackartist->track you will get back the
object of class Track that can be retreived using the value in the field
'track_id' of TrackArtist. Since this is the data that the mapping
extracts, the effect of calling $tori_amos->tracks, is getting a list of
Track objects, corresponding to the links.


This was written with your point 2 in mind - feel free to copy
verbatim, or modified any part of this email and it's preceeding
correspondance, if you think it can help. I tried to be as specific and
correct as possible, but I'm very new to Class::DBI myself, and I'm not
a good writer either. I'd estimate 30% of this to be unnoticed
assumptions, another 5% (At least, and this is not counting the overlap)
prejudice, and 10% educated guess. The rest is from reading some docs
and some source, as well as the very good material available on the CDBI
wiki [2].

> i've read the documentation and studied the examples many times. =20

They're terse and ambiguous, which is acceptable since 0.96 came out
only 2 months ago, and this was the first version manymany was possible
in, and no updates have come out since.

> 2. when i nail this thing, i am going to write a really clearly laid-out
> HOWTO so that other people like me won't waste the time of you geniuses
> who write this stuff with our simple-minded questions. =20

Excellent idea, but...

Instead, write a doc patch. That part of the doc is pretty lobotomized.
Giving you the missing piece tied to it with a string does not make it
more of a brain ;-)

I doubt a contextual doc patch will be rejected, documentation is one of
the hardest and most annoying bits of a program to get right.

The advantages of this is that it's much more accessible, and since it's
not a common-but-specific usage example of a generic tool, but rather a
feature description of a generic tool, it shouldn't be separated.

IMHO, ofcourse.

1. http://www.fabforce.net/dbdesigner4/ (opensource, despite the good
   looks of the site ;-)
2. http://www.class-dbi.com/cgi-bin/wiki/index.cgi?HomePage

P.S.
I hope that wasn't too long!
Ciao, good luck, and don't feel like you're being redundant. If you're
getting answers with lots of input, someone doesn't think you're asking
a silly question, or the same question again. Which means that the next
person can probably google for it and find the mailing list archive,
thanks to you.

--=20
 ()  Yuval Kogman <nothingmuch@xxxxxxxx.xxx> 0xEBD27418  perl hacker &
 /\  kung foo master: /me groks YAML like the grasshopper: neeyah!!!!!!


--O5XBE6gyVG5Rl6Rj
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.4 (Darwin)

iD8DBQFA13pyVCwRwOvSdBgRAvQkAJ9SXX9uTdvA5EkZNJU2q4szjRtTSQCcDHEL
pqDw69E7fjyj+EyeomSKX1g=
=hd0P
-----END PGP SIGNATURE-----

--O5XBE6gyVG5Rl6Rj--

(message missing)

setting up relationships
steve shapero 19:30 on 21 Jun 2004

Re: setting up relationships
Yuval Kogman 20:40 on 21 Jun 2004

Re: setting up relationships
steve shapero 23:34 on 21 Jun 2004

Re: setting up relationships
Yuval Kogman 00:16 on 22 Jun 2004

Re: setting up relationships
steve shapero 06:34 on 22 Jun 2004

Re: setting up relationships
Tony Bowden 07:41 on 22 Jun 2004

Re: setting up relationships
steve shapero 17:28 on 22 Jun 2004

Generated at 11:35 on 01 Dec 2004 by mariachi v0.52