Has a semantics wish list
From ClassDBI
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'); # instead of has_a(image_id => ...) # ...and later... my $image = $user->image; # image object my $image_id = $user->image_id; # image ID
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

