File Coverage

blib/lib/DBIx/Class/DynamicSubclass.pm
Criterion Covered Total %
statement 42 46 91.3
branch 11 18 61.1
condition 4 12 33.3
subroutine 8 8 100.0
pod 2 5 40.0
total 67 89 75.2


line stmt bran cond sub pod time code
1             package DBIx::Class::DynamicSubclass;
2 1     1   145131 use base qw/DBIx::Class/;
  1         2  
  1         76  
3 1     1   6 use strict;
  1         2  
  1         17  
4 1     1   4 use warnings;
  1         2  
  1         448  
5              
6             our $VERSION = 0.04;
7              
8             __PACKAGE__->mk_group_accessors(inherited => qw/_typecast_map typecast_column/);
9              
10             =head1 NAME
11              
12             DBIx::Class::DynamicSubclass - Convenient way to use dynamic subclassing.
13              
14             =head1 SYNOPSIS
15              
16             package My::Schema::Game;
17              
18             __PACKAGE__->load_components(qw/DynamicSubclass Core/);
19             __PACKAGE__->add_column(qw/id name data type/);
20              
21             __PACKAGE__->typecast_map(type => {
22             1 => 'My::Schema::Game::Online',
23             2 => 'My::Schema::Game::Shareware',
24             3 => 'My::Schema::Game::PDA',
25             });
26              
27             $game = $schema->resultset('Game')->new({..., type => 1});
28             # ref $game = 'My::Schema::Game::Online'
29              
30             @games = $game->search({type => 2});
31             # @games are all of class My::Schema::Game::Shareware
32              
33             $game->type(3); # game is now of class My::Schema::Game::PDA
34              
35             $game = $schema->resultset('Game')->new({});
36             # or
37             $game->type(undef);
38             # game is now of type My::Schema::Game
39              
40              
41             #Dynamic properties with DBIx::Class::FrozenColumns
42             package My::Schema::Game;
43             __PACKAGE__->load_components(qw/... FrozenColumns .../);
44              
45             package My::Schema::Game::Online;
46             use base 'My::Schema::Game';
47             __PACKAGE__->add_frozen_columns(data => qw/flash server_host server_port/);
48              
49             package My::Schema::Game::Shareware;
50             use base 'My::Schema::Game';
51             __PACKAGE__->add_frozen_columns(data => qw/price download_url/);
52              
53             ...
54              
55             $game->type(1); #game would have now additional columns 'flash', 'server_host', etc.
56             $game->server_host('...'); #(stored in 'data')
57              
58             $game->type(2);
59             $game->server_host; #error
60             $game->price('$3.00'); #ok
61              
62             $game = $rs->new({
63             type => 1,
64             flash => 'game.swf',
65             }); #ok
66              
67             #More flexible way
68              
69             package My::Schema::Game;
70             __PACKAGE__->typecast_column('type');
71              
72             sub classify { #called each time the object gets or losses its 'type'
73             my $self = shift;
74             #decide which class do you want
75             bless $self, $class;
76             }
77              
78             =head1 DESCRIPTION
79              
80             This plugin implements methodics described here
81             L.
82              
83             DynamicSubclass has 2 ways to work: static defining and dynamic defining.
84              
85             Static defining is used in most cases. This is when you define
86              
87             __PACKAGE__->typecast_map(defining_column => {column_value => 'subclass', ...});
88              
89             The plugin preloads all of the subclasses and changes the class of the row object
90             when you are creating new object or fetching it from a database or changing
91             'defining_column' value.
92             If the value is not exists in the 'typecast_map' then object is blessed into
93             the base class and losses all of its additional methods/columns/etc.
94              
95             Dynamic defining is when you only say
96              
97             __PACKAGE__->typecast_column('defining_column');
98              
99             and define a method 'classify' that would bless a row object into proper class.
100             This method is called when object is created, fetched or have its
101             'defining_column' value changed.
102              
103             =head1 METHODS
104              
105             =head2 typecast_map
106              
107             Arguments: $column, %typecast_hash
108              
109             %typecast_hash is a hash with keys equal to possible $column values and with
110             subclasses as values.
111              
112             =head2 classify
113              
114             A standart method for static subclassing. You should redefine this method in your
115             result source in order to use dynamic subclassing (second way).
116              
117             =head1 OVERLOADED METHODS
118              
119             new, inflate_result, store_column
120              
121             =head1 SEE ALSO
122              
123             L, L.
124              
125             =head1 AUTHOR
126              
127             Pronin Oleg
128              
129             =head1 LICENSE
130              
131             You may distribute this code under the same terms as Perl itself.
132              
133             =cut
134              
135             sub typecast_map {
136 1     1 1 76852 my ($this, $column, $map) = @_;
137 1 50       13 $this->throw_exception("cannot find column '$column'")
138             unless $this->has_column($column);
139 1 50 33     481 $this->throw_exception("typecast map must be a hash reference")
      33        
140             unless $map && ref $map && ref $map eq 'HASH';
141 1         13 $this->ensure_class_loaded($_) for values %$map;
142 1         955 $this->_typecast_map($map);
143 1         54 $this->typecast_column($column);
144             }
145              
146             sub inflate_result {
147 4     4 0 30137 my $self = shift;
148 4         37 my $ret = $self->next::method(@_);
149 4         150 $ret->classify;
150 4         136 return $ret;
151             }
152              
153             sub new {
154 4     4 0 426419 my $this = shift;
155 4         8 my $data = shift;
156              
157 4         6 my $deferred;
158 4 50       64 if ($this->can('add_frozen_columns')) {
159 0         0 my $real_columns = $this->result_source_instance->_columns;
160             map {
161 0         0 $deferred->{$_} = delete $data->{$_}
162 0 0 0     0 unless index($_, '-') == 0 or exists $real_columns->{$_};
163             } keys %$data;
164             }
165              
166 4         21 my $ret = $this->next::method($data, @_);
167 4         35 $ret->classify;
168              
169 4 50       77 if ($deferred) {
170 0         0 $ret->set_columns($deferred);
171             }
172              
173 4         10 return $ret;
174             }
175              
176             sub classify {
177 9     9 1 16 my $self = shift;
178 9 50       160 my $col = $self->typecast_column or $self->throw_exception(
179             'Neither typecast_map defined nor "classify" method redefined in your result source'
180             );
181              
182 9         295 my $val = $self->get_column($col);
183 9 100       69 $val = '' unless defined $val;
184 9 100       159 if (my $target_class = $self->_typecast_map->{$val}) {
185 5         135 bless $self, $target_class;
186             }
187             else {
188 4         98 bless $self, $self->result_source->result_class;
189             }
190              
191 9         159 return $self;
192             }
193              
194             sub store_column {
195 12     12 0 28776 my ($self, $column, $value) = @_;
196 12         20 my $tc_col;
197 12 100 66     281 if ($tc_col = $self->typecast_column and $tc_col eq $column) {
198 10         356 my $ret = $self->next::method($column, $value);
199 10         265 $self->classify;
200 10         190 return $ret;
201             }
202              
203 2         77 $self->next::method($column, $value);
204             }
205              
206             1;