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

Roman Daniel roman.daniel at davosro.cz
Mon Sep 26 21:19:17 GMT 2011


2011/9/25 Eden Cardim <edencardim at gmail.com>:
>>>>>> "Roman" == Roman Daniel <roman.daniel at davosro.cz> writes:
>
>    Roman> Sorry, but your comment about maintenance nightmare is
>    Roman> irrelevant.  Usually my column 'something' is called 'class'
>    Roman> and is immutable. When I create the object I know whether I
>    Roman> create Student or Worker. The example above can be rewritten
>    Roman> as
>
>    Roman> my $subclass = 'Me::Schema::Result::Person::'. $me->{class};
>    Roman> return $subclass->inflate_result($result_source, $me,
>    Roman> $prefetch);
>
>    Roman> Also this approach is by no means mutually exclusive with
>    Roman> your three tables, but it is complementing (I use extension
>    Roman> tables, I have one for almost each 'class').
>
> It's not mutually exclusive but it's also redudant and useless, besides
> leaving more gaps for your system to become inconsistent during
> runtime. If you already have a moniker for the class name, you might as
> well canonize that to the name of the table implementing each specific
> class. If the specialization of the row object doesn't involve data,
> you'll be a lot better off using delegation for that, in fact, given
> you're using a relational database, delegation is your best shot at
> everything.

I may agree with the delegation to keep inheritance away (one table -
one class).

>
>    Roman>  If I load the object from db and it is the instance of
>    Roman> MySchema::Result::Person::Student, in application logic
>    Roman> written in MySchema::Result::Person::Student I can use
>    Roman> student relationship to other table, while in
>    Roman> MySchema::Result::Person::Worker I will use worker
>    Roman> relationship.
>
> You should use a homogeneous relationship name in that case, call them
> all "person", you might want to declare proxy methods, which is DBIC's
> implementation of delegation, like this:
>
> __PACKAGE__->belongs_to(
>  person => 'MySchema::Result::Person'
>         => { 'me.person_id' => 'foreign.id' }
>         => { proxy => ['name', 'surname', etc... ] }
> );
>
> when you invoke $worker->name, it'll magically traverse the person
> relationship and fetch the right data, same for $student->name. If you
> need to optimize, add a prefetch to your queries on the specific tables.

Your relationship is from other side - from worker to person, while my
relationship worker is from person table to worker table.
I probably didn't stress enough that MySchema::Result::Person::Worker
(better called MySchema::Subresult::Person::Worker)
is just the subclass of MySchema::Result::Person (sharing the same
result_source_instance),
it is not the result class on worker table, it has no associated
source, there is nothing like $schema->source('Person::Worker').

If there is worker extension table, its result class
MySchema::Result::Worker is mere data container.
This is the confusing, I admit, because people expects some functionality here.

>
>    Roman> If you load MySchema::Result::Person instance from database
>    Roman> how do you know which (mutually exclusive) relationship you
>    Roman> should use? Here you have 2 of them, but when do you have 20
>    Roman> types of person?
>
> You don't, because polymorphism works by walking from specific classes
> to generic classes, not the other way around. If you've made a
> generalization, you're stuck at that level of the abstraction and you
> can't assume anything about the more specific levels of the class
> hierarchy.
>
> The solution in practical terms is to not query the person table
> directly, if you need the data from a lower level of the hierarchy. This
> is analogous to not being able to create an instance of an abstract
> class called Person in traditional class-based OO. If you need Person to
> be a concrete class, whenever you load a person row, you're conceptually
> stuck on that level of the hierarchy unless you somehow provide specific
> information of what class you want to upgrade that object to. In
> practical terms, you have to know what relationship you'll be using to
> get to the specific data, because inheritance can only walk from a
> specific class to a generic class.

Disagree. You pointed twice to KiokuDB. As I read the manual you have
object of some class,
you store it, you get the id, you load it using only the id and you
got back the instance of the original
class.

Same here only the person table works as storage only for Person subclasses.

In the process of loading I never create the instance of the abstract
Person class.
I don't use MySchema::Result::Person as result_class. The result_class
of Person source (resultset)
is completely other class (I would be better with object) outside of
DBIC hierarchy, it just have the
inflate_result and new methods, calling respective methods of Person subclasses.

I just let the Person class build the source and then I replace the
result_class of Person source on schema.

That was actually the point of my entry. That people do dynamic
subclassing and do it
wrong abusing the abstract base class instance as a dispatcher to
create its subclasses.

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

I dislike the example above because it creates the second source for
same table behaving not very intuitively

$schema->resultset('User')->find(XYZ) returns My::Schema::Result::User instance
while $schema->resultset('User::Admin')->find(the same XYZ) returns
My::Schema::Result::User::Admin instance


>
>    Roman> What are Neil's suggestions? I don't argue. Just give me the
>    Roman> 2 lines of code clue about resultset processors.
>
> You've been pointed to kiokudb twice, and I've detailed Neil's other
> suggestion in this message and the previous one. To summarize, your
> model is botched and you should fix it, otherwise you'll spend a fair
> amount of time working around DBIC's ORM implementation.

Wrongfully accused, I just asked what is "resultset processor".

The whole approach may be blotched why people bother with dynamic
subclassing in DBIC at all?
http://search.cpan.org/~abraxxa/DBIx-Class-0.08195/lib/DBIx/Class/Manual/Cookbook.pod#Dynamic_Sub-classing_DBIx::Class_proxy_classes

Why they:
$c->resultset('User')->find(...) to obtain either
My::Schema::Result::User or My::Schema::Result::User::Admin instance
according to data loaded.

http://search.cpan.org/~syber/DBIx-Class-DynamicSubclass-0.03/lib/DBIx/Class/DynamicSubclass.pm

Nevertheless I see one big advantage of your  "from the extension
table (worker, student)
to base table (person)" approach, which is flat hierarchy of classes.
When building Catalyst application,
my templates and controllers roughly correspond with models. When I
have my url as /worker/123/view
I am immediately in worker controller (with associated template) while
in /person/123/view I need to struggle
after load_person(123) how to get to worker controller and let the
worker do the job.

The discussion here went far away (my fault) from the original
subject.I wanted to know what do you think of  the object as a value
of result class.
Except of dynamic subclassing I can imagine other application of
playing with result class, for example injecting the dependencies into
result objects.



2011/9/25 Eden Cardim <edencardim at gmail.com>:
>>>>>> "Roman" == Roman Daniel <roman.daniel at davosro.cz> writes:
>
>    Roman> Sorry, but your comment about maintenance nightmare is
>    Roman> irrelevant.  Usually my column 'something' is called 'class'
>    Roman> and is immutable. When I create the object I know whether I
>    Roman> create Student or Worker. The example above can be rewritten
>    Roman> as
>
>    Roman> my $subclass = 'Me::Schema::Result::Person::'. $me->{class};
>    Roman> return $subclass->inflate_result($result_source, $me,
>    Roman> $prefetch);
>
>    Roman> Also this approach is by no means mutually exclusive with
>    Roman> your three tables, but it is complementing (I use extension
>    Roman> tables, I have one for almost each 'class').
>
> It's not mutually exclusive but it's also redudant and useless, besides
> leaving more gaps for your system to become inconsistent during
> runtime. If you already have a moniker for the class name, you might as
> well canonize that to the name of the table implementing each specific
> class. If the specialization of the row object doesn't involve data,
> you'll be a lot better off using delegation for that, in fact, given
> you're using a relational database, delegation is your best shot at
> everything.

I may agree with the delegation to keep inheritance away (one table -
one class).

>
>    Roman>  If I load the object from db and it is the instance of
>    Roman> MySchema::Result::Person::Student, in application logic
>    Roman> written in MySchema::Result::Person::Student I can use
>    Roman> student relationship to other table, while in
>    Roman> MySchema::Result::Person::Worker I will use worker
>    Roman> relationship.
>
> You should use a homogeneous relationship name in that case, call them
> all "person", you might want to declare proxy methods, which is DBIC's
> implementation of delegation, like this:
>
> __PACKAGE__->belongs_to(
>  person => 'MySchema::Result::Person'
>         => { 'me.person_id' => 'foreign.id' }
>         => { proxy => ['name', 'surname', etc... ] }
> );
>
> when you invoke $worker->name, it'll magically traverse the person
> relationship and fetch the right data, same for $student->name. If you
> need to optimize, add a prefetch to your queries on the specific tables.

Your relationship is from other side - from worker to person, while my
relationship worker is from person table to worker table.
I probably didn't stress enough that MySchema::Result::Person::Worker
(better called MySchema::Subresult::Person::Worker)
is just the subclass of MySchema::Result::Person (sharing the same
result_source_instance),
it is not the result class on worker table, it has no associated
source, there is nothing like $schema->source('Person::Worker').

If there is worker extension table, its result class
MySchema::Result::Worker is mere data container.
This is the confusing, I admit, because people expects some functionality here.

>
>    Roman> If you load MySchema::Result::Person instance from database
>    Roman> how do you know which (mutually exclusive) relationship you
>    Roman> should use? Here you have 2 of them, but when do you have 20
>    Roman> types of person?
>
> You don't, because polymorphism works by walking from specific classes
> to generic classes, not the other way around. If you've made a
> generalization, you're stuck at that level of the abstraction and you
> can't assume anything about the more specific levels of the class
> hierarchy.
>
> The solution in practical terms is to not query the person table
> directly, if you need the data from a lower level of the hierarchy. This
> is analogous to not being able to create an instance of an abstract
> class called Person in traditional class-based OO. If you need Person to
> be a concrete class, whenever you load a person row, you're conceptually
> stuck on that level of the hierarchy unless you somehow provide specific
> information of what class you want to upgrade that object to. In
> practical terms, you have to know what relationship you'll be using to
> get to the specific data, because inheritance can only walk from a
> specific class to a generic class.

Disagree. You pointed twice to KiokuDB. As I read the manual you have
object of some class,
you store it, you get the id, you load it using only the id and you
got back the instance of the original
class.

Same here only the person table works as storage only for Person subclasses.

In the process of loading I never create the instance of the abstract
Person class.
I don't use MySchema::Result::Person as result_class. The result_class
of Person source (resultset)
is completely other class (I would be better with object) outside of
DBIC hierarchy, it just have the
inflate_result and new methods, calling respective methods of Person subclasses.

I just let the Person class build the source and then I replace the
result_class of Person source on schema.

That was actually the point of my entry. That people do dynamic
subclassing and do it
wrong abusing the abstract base class instance as a dispatcher to
create its subclasses.

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

I dislike the example above because it creates the second source for
same table behaving not very intuitively

$schema->resultset('User')->find(XYZ) returns My::Schema::Result::User instance
while $schema->resultset('User::Admin')->find(the same XYZ) returns
My::Schema::Result::User::Admin instance


>
>    Roman> What are Neil's suggestions? I don't argue. Just give me the
>    Roman> 2 lines of code clue about resultset processors.
>
> You've been pointed to kiokudb twice, and I've detailed Neil's other
> suggestion in this message and the previous one. To summarize, your
> model is botched and you should fix it, otherwise you'll spend a fair
> amount of time working around DBIC's ORM implementation.

Wrongfully accused, I just asked what is "resultset processor".

If the whole approach may be blotched so why people bother with
dynamic subclassing in DBIC at all?
http://search.cpan.org/~abraxxa/DBIx-Class-0.08195/lib/DBIx/Class/Manual/Cookbook.pod#Dynamic_Sub-classing_DBIx::Class_proxy_classes

Why they:
$c->resultset('User')->find(...) to obtain either
My::Schema::Result::User or My::Schema::Result::User::Admin instance
according to data loaded.

http://search.cpan.org/~syber/DBIx-Class-DynamicSubclass-0.03/lib/DBIx/Class/DynamicSubclass.pm

Nevertheless I see one big advantage of your  "from the extension
table (worker, student)
to base table (person)" approach, which is flat hierarchy of classes.
When building Catalyst application,
my templates and controllers roughly correspond with models. When I
have my url as /worker/123/view
I am immediately in worker controller (with associated template) while
in /person/123/view I need to struggle
after load_person(123) how to get to worker controller and let the
worker do the job.

The discussion here went far away (my fault) from the original
subject. I wanted to know what do you think of  the object as a value
of result_class.
Except of dynamic subclassing I can imagine other application of
playing with result_class, for example injecting the dependencies into
result objects.

in schema:

package MySchema::Result::Subscriber;

use Moose;
use MooseX::NonMoose;
use namespace::autoclean;
extends 'MySchema::Result';
....
has mailchimp_client => (is => 'rw');

....
meanwhile in container (assuming Bread::Board)

service schema => (
    block => sub {
        my $s = shift;
        my $schema = $s->param('raw_schema');
        my $mailchimp = $s->param('mailchimp');
        my $subscriber_source = $schema->source('Subscriber');
        $subscriber_source->result_class(
             My::Injector->new($subscriber_source->result_class,
mailchimp_client => $mailchimp)
        );
        return $schema;
    }
);



{
    package My::Injector;
    use Moose;

    has [qw(result_class setter value_injected)]   => ( is => 'ro' );

    sub BUILDARGS {
        my $this = shift;

        my %fields;
        @fields{qw(result_class setter value_injected)} = @_;
        return \%fields;
    }

    sub inflate_result {
        my $this     = shift;
        my $inflated = $this->result_class->inflate_result(@_);
        my $setter   = $this->setter;
        $inflated->$setter( $this->value_injected );
        return $inflated;
    }
}



More information about the DBIx-Class mailing list