Has a semantics wish list

I wish has_a didn't force you to use the column name as the method name for getting at the object in question.

For example, say I had a column named image_id that refered to a row in the image table. Currently (2004-02-18), I have to declare my has_a relation like this:

__PACKAGE__->has_a(image_id => My::Image);

Later on, when I want to grab the My::Image object, I have to say

my $image = $foo->image_id;

This can be kind of confusing, because I'm not getting an id at all, but a whole object. I suppose I could name my column, "image", but has_many doesn't force me to name my columns this way, so why should has_a?

''In my humble and gracious opinion, image_id is not a sensible column name, given how has_a works. You may not fully understand the best way to use has_a.''

''If you don't see why having to rewrite your database schema just to be able to use Class::DBI is stupid, you're not going to understand why some people have this need. accessor_name is a hack at best.''

I second this. (John, is this comment yours?) --DavidIberri 6/23/04

''Hey, Dave. Starting from the paragraph that begins with *"I wish has_a didn't force you to"* and finishing with the paragraph that ends with *"so why should has_a?"* ...those words were written by me.''

--JohnBeppu 2004-09-14

''Sorry, no, it was my comment, David. Apologies for the tone of the comment, I just felt the preceding comment (before its edit) was a rather unhelpful response to an innocent query. My complaint, reworded, is simply that it seems somewhat arbitrary to require that the name by which you access the relationship _programmatically_ be the same as its name in the database. I suspect the _id convention to denote foreign keys in tables might be quite common. I've learned to live with it, but would prefer something like the ability to specify an alias for the relationship, that could be used anywhere the column name is currently used.''

-- James 2004/07/19

Maybe it's not that big of a deal, because what I do in practice is manually write an accessor for the object and avoid setting up has_a relations entirely. Whatever.

''I do declare, I wonder why you use Class:DBI? I ask this politely and helpfully, to make the point that you might be missing the point, no offense intended, because it would be a shame to litter this page with patronizing insults.''

Other than that, Class::DBI is good stuff. 2004-02-18 --JohnBeppu

ANSWER: You should be able to achieve this through a custom accessor_name. If that doesn't work, then it's a bug, and should be reported on the CpanBugInterface (ideally with a failing test case).

COMMENT: I've have the same wish (that Class::DBI wouldn't force you to use the column name as the accessor name in has-a relationships), but the above answer doesn't always satisfy my needs. In particular, I'd like to grab the object (e.g. a My::Image object), with

my $image = $foo->image;

and grab the ID of that object with

my $image_id = $foo->image_id;

But if I use _accessor_name_ to strip the "_id" off of my accessor names like so:

sub accessor_name { ( my $col = pop ) =~ s/_id$//; return $col; }

then the second example ([=$foo->image_id]) doesn't work, since no "image_id" method will exist given the above _accessor_name_ definition.

What I need is a way to grab the full-blown object (My::Image), but also to get the original ID of the object in question. Sure I could use [=$foo->image->id] in most cases to get the object ID, but that requires a database call to fetch a whole image object when all I need is its ID.

''No it doesn't. It won't look at the database at all, not for the id. It's not stupid!''

Yes, it *will* look at the database for the ID. Consider this:

package My::User; # ... inherit, assign table __PACKAGE__->columns( All => qw( id   image_id ) ); __PACKAGE__->has_a( image_id => 'My::Image' ); # Allow user's My::Image object to be accessed # via $user->image rather than $user->image_id sub accessor_name { my $name = pop; return 'image' if $name eq 'image_id'; return $name; } package main; my $user = My::User->retrieve($id); # Returns a My::Image object my $image = $user->image;

At this point in the code, are you suggesting that saying

my $image_id = $user->image->id;

won't query the database to fetch the My::Image object?

What you're saying is that the _My::User::image_ function returns the image ID because it knows that the _id_ method is to be called on the resulting My::Image object? I didn't realize Perl ∞ was available! ;-)

All kidding aside... Clearly, [=$user->image->id] *will* issue a database query (that's what the _image_ method does, after all), and the _id_ method will be called on the My::Image object returned by [=$user->image].

In short, it will look at the database. And FWIW, that doesn't make CDBI stupid, as you suggest.

--DavidIberri 6/23/04

FWIW, I think our wishes can be answered with some careful modifications to Class::DBI::Relationship::!HasA. Unfortunately, I didn't put my hacking hat on today :-) --DavidIberri 2004-06-07

''After careful and considered thought, I believe that those modifications are not necessary - you may wish to rethink your conclusions. Have a nice day!''

After careful consideration, it seems as though you do not understand the motivation for our suggestions:


 * I like naming my columns "image_id", "user_id", etc.
 * I like that CDBI can map those values onto bona fide Perl objects, like My::Image, and My::User
 * I want [=$user->image] to return a My::Image object and [=$user->image_id] to return the ID of that object (basically [=$user->{image_id}])
 * This is not possible using /accessor_name/. Sure, it will give me the [=$user->image] method that I want, but it will also remove the [=$user->image_id] method that I need

--DavidIberri 6/23/04

CDBI Objects stringify to their primary key value, by default. Doesn't this give you what you need? Of course if you like to override the stringify method yourself (generally a dubious proposition IMHO) this is moot.

However, the thing that gets me is, even if you do some [=accessor_name] magic, search et al. still need the original column name.

--JeremyMuhlich 2004/06/24

Actually it looks like [=$user->image->id] will *not* do a database fetch. CDBI will instantiate the image object, but since it already knows the primary key (the FK from the user object), no database fetch is necessary. If you try to access any other column then a fetch is necessary, of course.

I turned on query logging and it sure doesn't look like it's doing a fetch in that situation.

However, if you want accessors named both image and image_id, will this work? It's a hack, but it creates aliases to the original names too.

sub accessor_name { my($class, $column) = @_; return $column unless $column =~ /^(.+)_id$/; no strict 'refs'; *{"$class\::$column"} = \&{"$class\::$1"}; return $1; }

--Maurice 2004/06/25

This will do. Thanks, Maurice.

--JohnBeppu 2005/03/07

Below is a tiny modification of the code above to achieve even more code readability:

__PACKAGE__->has_a(image   => 'My::Image'); my $image   = $user->image;    # image object my $image_id = $user->image_id; # image ID
 * 1) instead of has_a(image_id => ...)
 * 1) ...and later...

And here's the code for your package:

sub accessor_name_for { my ($class, $column) = @_; return $column unless my ($accessor_name_for_obj) = $column =~ /^(.+)_id$/; # make original accessor return object ID (force call to stringify) no strict 'refs'; *{"$class\::$column"} = sub { ''. &{"$class\::$accessor_name_for_obj"} }; return $accessor_name_for_obj; } sub has_a { my $class = shift; my $accessor = shift; # suffix accessor name with '_id' to get real column name $accessor .= '_id' if $class->find_column($accessor . '_id'); return $class->SUPER::has_a($accessor => @_); }

--NikitaDedik 2007/01/25