perldbix-classtemplate-toolkitdancer2

Perl DBIC: extend resultset with a virtual column


For an app I'm currently writing I need to create links. The link is composed from the article name and a category name, this has to be done on all places where I want to use the link. Repeating that code strikes me as hard to maintain and error prone. For this reason I want this link/url to be a virtual column in the table/class.

I did find a module to add virtual columns, but the documentation states this is just a "proof of concept", over 10 years old and has unresolved issues. DBIx::Class::VirtualColumns

Allso came across an way of extending the resultset with custom methods: Predefined-searches and Create a ResultSet Class

Both use the method for predefined searches.

So I created myself a resultset directory and the following module

package MySite::Schema::ResultSet::Article;
use strict;
use warnings;
use Data::Dumper;
use base 'DBIx::Class::ResultSet';

sub returnURL {
    my ($self, $which) = @_;
    my $blaat =$self->find(
        {article_id => $which}
    );
    my $result = $blaat->slug;
    print "\n>>$result<<\n";
    return $result;
}
1;

Called this method for debug purposses (in a Dancer2 app):

debug schema.resultset('Article')->returnURL(2);

which results in the following output

>>artikel_2<<
[MySite:94935] debug @2024-11-25 10:08:13> MySite::Schema=HASH(0x6216cb9aae60)artikel_2 in /var/www/MySite/bin/../lib/MySite.pm l. 24

The first line is exactly what I was expecting. The "MySite::Schema=HASH(0x6216cb9aae60)" in the second line is not. Where does that come from?

Further: I loop the articles in a TT template and try to get the result of the method:

    <% url1 = schema.resultset('Article').returnURL(1) %>
    <% url2 = article.returnURL(1) %>
    >><% url1 %><<
    >><% url2 %><<

(Yes the article indexes are hardcoded for now) Both url1 and url2 come back empty.

Pretty sure I'm doing something really studid, just can't see what.

Tried like Dave suggested (I think)

Moved the method to the the class that wat created for me:

sub returnURL {
  print ">>blaat<<";
  my ($self, $which) = shift;
    # my $blaat =$self->find(
    #     {article_id => $which}
    # );
    # my $result = $blaat->slug;
    # print "\n>>$result<<\n";
    # return $result;
    return("hey there ".$self->slug);
}

Calling that method in the Perl code returns an error.

Can't locate object method "returnURL" via package "DBIx::Class::ResultSet" at /var/www/MySite/bin/../lib/MySite.pm line 24.

so I thought, who cares... need it in the template anyway. And commented that out. After that the second method I user in the template did in fact return a result:

<% article.returnURL() %>

gave me

hey there artikel_4

Which is what I expected and something I can work with. Turns out I don't even have to supply the article index.

Thank you Dave


Solution

  • I think you're overthinking this. This should be a method that you add to the Result class.

    Update: I realise my original answer was quite curt. So he's a little more useful information.

    The "Result" class basically models a row from the database table. If you're in a method in the result class then the $self variable you have is one row from the table. It will have methods that match the various columns on the table. So that is the obvious place to add methods that extend the information you have about a single row.

    Imagine, for example, a person table that contains a date_of_birth column - but you want the person's age. In the result class, you create a new method called age(). And inside that, you can subtract the data of birth from the current date to get the age:

    sub age {
      my $self = shift;
    
      my $age = DateTime->now - $self->date_of_birth;
    
      return $age;
    }
    

    Your url() method works on exactly the same principle.

    The "ResultSet" class, on the other hand, is where you want to put clever search methods. Methods that return a subset of rows from the table. Keeping with our person example, you might want search methods that return all men or all women.

    sub women {
      my $self = shift;
    
      return $self->search({ sex => 'F' });
    }