[Dbix-class] object as a value of result_class (resultset_class)

Roman Daniel roman.daniel at davosro.cz
Wed Sep 21 22:40:02 GMT 2011


Yes, it is exactly from the mould of Person -> Student,  Person -> Worker, ...

In past I got quite frustrated with how little information I found
about coping with polymorphism in DBIC environment. It seemed that
this is the problem that almost everyone has to come across :-( I
appreciate any link to this topic.

Regarding ResultSet processor I can't imagine how you use it. Can you
just ilustrate it on line or two:

my $rs = $schema->resultset('Person');
# how you load the object of appropriate subclass from db


A few personal discoveries - sorry for being lengthy, I just needed to
sum up my mind

Let us have base class  MySchema::Result::Person and subclasses
MySchema::Result::Person::Student, MySchema::Result::Person::Worker
(or MySchema::Subresult::Person::Student,
MySchema::Subresult::Person::Worker explained later) objects of all
classes stored in table person.

Now I don't solve how to store the additional attributes of Worker and
Student subclasses the only issue is how to load the instance of
appropriate class from db. I found even this problem quite
underdocumented in DBIC.

I strongly dislike the idea of base class being also the dispatcher
(value of result_class). When MySchema::Result::Person->inflate_result
is called the only way how to produce
MySchema::Result::Person::Student instance is to rebless self. You
can't call MySchema::Result::Person::Student->inflate_result because
it would probably lead to endless loop
(MySchema::Result::Person::Student->inflate_result is likely inherited
from MySchema::Result::Person)

But there is no such problem if you use something outside of
MySchema::Result::Person class hierarchy. As far as I understand
result_class, it is a class (or object) with methods inflate_result
and new. It doesnot need to be DBIC row class or anything like this.
If  inflate_result is called it just finds the appropriate subclass of
MySchema::Result::Person and calls its inflate_result.

You can set the dispatcher (result_class) in your schema class after
load_namespaces:

package MySchema::Dispatcher::Person;

sub inflate_result {
     my ($this, $result_source, $me, $prefetch) = @_;

     my $subclass = $me->{something} eq 'anything' ?
'MySchema::Result::Person::Worker':
'MySchema::Result::Person::Student';
     return $subclass->inflate_result($result_source, $me, $prefetch);
}


package MySchema;
....
#after namespaces are loaded
__PACKAGE__->load_namespaces;
# I will change the result_class of the source
__PACKAGE__->source('Person')->result_class('MySchema::Dispatcher::Person');

But this is not the complete story. I found no analysis whether this
subclasses should be independent sources or not.

If you have MySchema::Result::Person::Student (inherited from
MySchema::Result::Person) and you use MySchema->load_namespaces, then
your ::Student, ::Worker classes turn to independent result_sources as
well (you should also create result_source_instance for the class).

package MySchema::Result::Person::Student;
use base qw(MySchema::Result::Person);

# creates the result_source_instance
__PACKAGE__->table(__PACKAGE->table);

doing so you have sources Person, Person::Student, Person::Worker in
your schema. AFAIK all relationships from Person source are copied
into Person::Student source (when table is called). You can also add
specific relationships to Person::Student.
But to use these relationships for Student you need to rewrite the
inflate_result

sub inflate_result {
     my ($this, $result_source, $me, $prefetch) = @_;

     my $subclass = $me->{something} eq 'anything' ?
'MySchema::Result::Person::Worker':
'MySchema::Result::Person::Student';
     my $subsource = $result_source->schema->source($subclass);
     return $subclass->inflate_result($subsource, $me, $prefetch);
}

otherwise even if you create MySchema::Result::Person::Student
instance it is still bound to original Person result source.

This is the case of example here:

http://search.cpan.org/~abraxxa/DBIx-Class-0.08195/lib/DBIx/Class/Manual/Cookbook.pod#Dynamic_Sub-classing_DBIx::Class_proxy_classes

User::Admin source is created but it is never used, the inflated user
is still bound to User result source.


If you do not want independent sources to be created for
Person::Student and Person::Worker you need to hide your subclasses
from load_namespaces. You probably rename the classes and load them
explicitly in MySchema::Result::Person (after load_namespaces is too
late).

MySchema::Subresult::Person::Student
MySchema::Subresult::Person::Worker

In this case you can still add relationship rel1 to in
MySchema::Subresult::Person::Student and rel2 in
MySchema::Subresult::Person::Worker but they will share the same
result_source_instance (and result_source later), however the rel1
accessor will be created in Student class only and vice versa.

Personally (but this is a feeling only) I prefer the second variant -
i.e. one table, one source


Roman


2011/9/20 neil.lunn <neil at mylunn.id.au>
>
> On 20/09/2011 7:59 PM, Roman Daniel wrote:
>>
>> In my app I store instances of several classes (subclasses) in one table.
>> I try to implement a dispatcher which decides what class to inflate to
>> according to row data loaded from db.
>>
>> It seems handy for this dispatcher to be an object instead of class.
>> In DBIC the setter for result_class is implemented via set_component_class,
>> which expects its argument to be a class - Class::Inspector->loaded is
>> called on it.
>
> I note that this is not really where you are going with this from your following passage, and that perhaps it's something you might find a more timely/lively discussion on irc about. I'm not do sure I'm all to hot about result_class being an object/instance rather than a class though.
>
> However it seems to me that what you are generally describing is something in the mould of a Person -> Student, Person -> Worker, polymorphic set of relations in your data structure defined by the underlying elements.
>
> So for my money, and I know this might sound like a lot of duplicative work, I'd being looking at implementing your dispatcher as a ResultSet processor inflating the result rows into the respective classed objects. Meaning think of this as an abstraction layer on top of the DBIC classes representing the "table" schema and more as the polymorphic object collection, aware of the underlying resultset objects.
>
> There are varying approaches to this, from related tables for different object properties to object serialization/deserialization in something like a clob. Especially with the related tables approach and getting fancy with prefetch, I tend to prefer the abstraction approach.
>
>>
>> I can overwrite set_component_class to fallback to set_inherited when
>> its argument  is a blessed object, but I am not sure whether the whole
>> idea is not weird. If not do you plan to allow result_class to be an
>> object? (it can be set to object even now but a warning is issued by
>> Class::Inspector)
>>
>> I appreciate any comment
>>
>> Roman Daniel
>>
>> After I sent the message I noticed that result_class being a class is
>> also expected in compose_namespace (used in
>> Catalyst::Model::DBIC::Schema), where subclasses of result_classes
>> are created.
>>
>> _______________________________________________
>> List: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/dbix-class
>> IRC: irc.perl.org#dbix-class
>> SVN: http://dev.catalyst.perl.org/repos/bast/DBIx-Class/
>> Searchable Archive: http://www.grokbase.com/group/dbix-class@lists.scsys.co.uk
>



More information about the DBIx-Class mailing list