File Coverage

blib/lib/DBICx/Modeler.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package DBICx::Modeler;
2              
3 9     9   1322987 use warnings;
  9         26  
  9         362  
4 9     9   50 use strict;
  9         17  
  9         585  
5              
6             =head1 NAME
7              
8             DBICx::Modeler - A Moose-based model layer over DBIx::Class
9              
10             =head1 VERSION
11              
12             Version 0.005
13              
14             =cut
15              
16             our $VERSION = '0.005';
17              
18             =head1 SYNOPSIS
19              
20             # Given the following schema:
21              
22             My::Schema::Artist
23             My::Schema::Cd
24             My::Schema::Track
25              
26             # ... and the following model:
27              
28             My::Model::Artist
29              
30             use DBICx::Modeler::Model
31              
32             My::Model::Cd ...
33             My::Model::Track ...
34              
35             ...
36              
37             my $modeler = DBICx::Modeler->new( schema => ..., namespace => My::Model );
38              
39             my $artist = $modeler->create( Artist => { ... } ) # $artist is My::Model::Artist
40              
41             my $cd = $artist->create_related( cds => { ... } ) # $cd is My::Model::Cd
42              
43             ...
44              
45             my $search = $artist->cds( { title => ... } ) # Start a search ...
46             $search->search( { ... } ) # Refine the search ...
47             my @cds = $search->slice( 0, 9 ) # Get the first 10
48             # Each is of type My::Model::Cd
49              
50             =head1 DESCRIPTION
51              
52             DBICx::Modeler is tool for making a thin, Moose-based model layer over a DBIx::Class schema
53              
54             =head1 CAVEAT EMPTOR
55              
56             =head2 Many-to-many is not handled
57              
58             Many-to-many relationships are ignored, for now. You'll have to access C<_model__storage> (which is the DBIx::Class::Row) if you want
59             to play with them
60              
61             =head2 The same storage object can be present in different model objects
62              
63             # With the following example:
64             $artist->cds( ... )->slice( 0 )->artist # $artist and ->artist are different objects
65              
66             This shouldn't be too difficult to fix.
67              
68             =head2 The modeler will probably barf when trying to modify immutable Model:: classes
69              
70             This shouldn't be too difficult to fix, either.
71              
72             =head2 Use C<DBIC_MODELER> to trace modeler setup
73              
74             Set C<DBIC_MODELER> to 1 if you want to trace what is going on in the modeler internally
75              
76             $ENV{DBIC_MODELER} = 1
77              
78             =head1 METHODS
79              
80             DBICx::Modeler->new( ... )
81              
82             schema The connected DBIx::Class schema to use/inspect
83              
84             namespace The package containing the Moose classes that will mimic the class structure of <schema>
85              
86             $modeler->model( <moniker> )
87              
88             Return the model source for <moniker>
89              
90             $modeler->create( <moniker> => ... )
91              
92             Create a new row for <moniker> and return the modeled object
93              
94             $modeler->search( <moniker> => ... )
95              
96             Make a search of <moniker> that will inflate into modeled objects
97              
98             =cut
99              
100 9     9   14986 use Moose;
  0            
  0            
101              
102             use DBICx::Modeler::Carp;
103             use constant TRACE => DBICx::Modeler::Carp::TRACE;
104              
105             use Class::Inspector();
106             use Scalar::Util qw/weaken/;
107              
108             use DBICx::Modeler::Model::Source;
109              
110             #########
111             # Class #
112             #########
113              
114             sub ensure_class_loaded {
115             shift;
116             my $class = shift;
117             return $class if Class::Inspector->loaded( $class );
118             eval "require $class;";
119             die "Couldn't load class $class: $@" if $@;
120             return $class;
121             }
122              
123             sub _expand_relative_name {
124             my ($self, $name) = @_;
125             my $class = ref $self || $self;
126              
127             return unless $name;
128              
129             my $parent_class = $class;
130              
131             if ($name =~ s/^\+//) {
132             # Hammatime: Don't touch this!
133             }
134             else {
135             if ($name =~ s/^\-//) {
136             # User wants the parent (wants to be a sibling)
137             my @class = split m/::/, $parent_class;
138             pop @class;
139             $parent_class = join '::', @class;
140             }
141             $name = $parent_class . '::' . $name;
142             }
143             return $name;
144             }
145              
146             ###########
147             # Object ##
148             ###########
149              
150             has schema => qw/is ro required 1/;
151             has schema_class => qw/is ro lazy_build 1/;
152              
153             has [qw/
154             namespace
155             skip_moniker
156             /] => qw/is rw/;
157              
158             has [qw/
159             create_refresh
160             sibling_namespace
161             /] => qw/is rw default 1/;
162              
163             has skip_schema_modeler_accessor => qw/is rw default 0/;
164             has [qw/ _model_source_list /] => qw/is ro required 1 lazy 1 isa ArrayRef/, default => sub { [] };
165             has [qw/ _namespace_list /] => qw/is ro lazy_build 1 isa ArrayRef/;
166             sub _build__namespace_list {
167             my $self = shift;
168             my $class = ref $self || $self;
169              
170             my $default_namespace = do {
171             my @default = split m/::/, $class;
172             if ( my $name = $self->sibling_namespace ) {
173             $name = "Model" if $name eq 1;
174             pop @default; # Use Example::${name} instead of Example::Modeler::${name} (e.g. Example::Model)
175             push @default, $name;
176             }
177             "+" . join "::", @default;
178             };
179              
180             my $namespace = $self->namespace;
181             $namespace = [] unless defined $namespace;
182             $namespace = [ $namespace ] unless ref $namespace eq "ARRAY";
183             unless (@$namespace) {
184             croak "You didn't specify a namespace" if $class eq __PACKAGE__;
185             @$namespace = ("?"); # Use the default namespace if none specified
186             }
187             @$namespace = map { $_ eq "?" ? $default_namespace : $_ } @$namespace;
188              
189             $_ = $self->_expand_relative_name( $_ ) for @$namespace;
190              
191             return [ @$namespace ];
192             }
193             has [qw/
194             _model_source_lookup_map
195             _model_class_by_moniker_map
196             _moniker_by_model_class_map
197             /] => qw/is ro required 1 lazy 1 isa HashRef/, default => sub { {} };
198              
199             sub _build_schema_class {
200             my $self = shift;
201             return ref $self->schema;
202             }
203              
204             sub BUILD {
205             my $self = shift;
206             my $given = shift;
207              
208             $self->skip_moniker( $given->{skip} ) if ! exists $given->{skip_moniker} && $given->{skip};
209              
210             my $schema = $self->schema;
211             my $schema_class = $self->schema_class;
212              
213             $self->_setup_schema_modeler_accessor unless $self->skip_schema_modeler_accessor;
214             $self->_setup_base_model_sources;
215             {
216             $self->schema->modeler( $self );
217             weaken $self->schema->{modeler};
218             }
219              
220             return 1;
221             }
222              
223             sub _setup_schema_modeler_accessor {
224             my $self = shift;
225             return if $self->schema_class->can( qw/modeler/ );
226             $self->schema_class->mk_group_accessors( simple => qw/modeler/ );
227             }
228              
229             sub _setup_base_model_sources {
230             my $self = shift;
231             my %option = @_;
232              
233             for my $moniker ($self->schema->sources) {
234             my $model_class = $self->model_class_by_moniker( $moniker ); # Initialize base model classes & moniker_by_model_class/model_class_by_moniker
235             my $model_source = DBICx::Modeler::Model::Source->new(
236             moniker => $moniker,
237             modeler => $self,
238             schema => $self->schema,
239             model_class => $model_class,
240             );
241             $model_class->_model__meta->initialize_base_model_class( $model_source );
242             $self->_register_model_source( $model_source );
243             }
244             }
245              
246             sub namespaces {
247             my $self = shift;
248             return @{ $self->_namespace_list }
249             }
250              
251             sub moniker_by_model_class {
252             my $self = shift;
253             my $model_class = shift;
254              
255             return $self->model_source_by_model_class( $model_class )->moniker;
256             # croak "Couldn't find moniker for (model class) $model_class" unless $moniker;
257             }
258              
259             sub find_model_class {
260             my $self = shift;
261             my $query = shift;
262              
263             if ($query =~ s/^\+//) {
264             return $self->ensure_class_loaded( $query );
265             }
266              
267             # A relative class... 'moniker'
268             return $self->model_class_by_moniker( $query );
269             }
270              
271             sub model_class_by_moniker {
272             my $self = shift;
273             my $moniker = shift;
274              
275             # Has to be done this way, because the model source might not be loaded yet
276              
277             my $model_class = $self->_model_class_by_moniker_map->{$moniker};
278             return $model_class if $model_class;
279              
280             for my $namespace ( $self->namespaces ) {
281             my $potential_model_class = "${namespace}::${moniker}";
282              
283             if (Class::Inspector->loaded( $potential_model_class )) {
284             }
285             else {
286             eval "require $potential_model_class;";
287             if ($@) {
288             my $file = join '/', split '::', $potential_model_class;
289             if ($@ =~ m/^Can't locate $file/) {
290             TRACE->( "[$self] Unable to load file ($file) for $potential_model_class" );
291             next;
292             }
293             else {
294             die "Couldn't load class $potential_model_class for $moniker: $@" if $@;
295             }
296             }
297             }
298             $model_class = $potential_model_class;
299             last; # We found something!
300             }
301              
302             croak "Couldn't find model class for (moniker) $moniker" unless $model_class;
303              
304             $self->_moniker_by_model_class_map->{$model_class} = $moniker;
305             return $self->_model_class_by_moniker_map->{$moniker} = $model_class;
306             }
307              
308             sub model_class_by_result_class {
309             my $self = shift;
310             my $result_class = shift;
311             my $moniker = $self->schema_class->source( $result_class )->source_name;
312             return $self->model_class_by_moniker( $moniker );
313             }
314              
315             sub model_sources {
316             my $self = shift;
317             return @{ $self->_model_source_list };
318             }
319              
320             sub _model_source {
321             my $self = shift;
322             my $model_source = shift;
323              
324             $model_source = $self->_model_source_lookup_map->{$model_source} while defined $model_source && ! ref $model_source;
325              
326             return $model_source;
327             }
328              
329             sub model_source {
330             my $self = shift;
331             my $model_source = shift;
332             return $self->_model_source( $model_source ) or croak "Couldn't find model source with key $model_source";
333             }
334              
335             sub model {
336             my $self = shift;
337             return $self->model_source( @_ );
338             }
339              
340             sub model_source_by_moniker {
341             my $self = shift;
342             my $moniker = shift;
343             my $model_source = $self->_model_source( "::${moniker}" ) or
344             croak "Couldn't find model source for (moniker) $moniker";
345             return $model_source;
346             }
347              
348             sub model_source_by_model_class {
349             my $self = shift;
350             my $model_class = shift;
351              
352             my $model_source = $self->_model_source( "+${model_class}" );
353              
354             return $model_source if $model_source;
355            
356             TRACE->( "[$self] Building model source for $model_class" );
357             # The model class might not have been loaded yet
358             $self->ensure_class_loaded( $model_class );
359              
360             die "Can't get model source for $model_class since it doesn't have a model meta" unless $model_class->can( '_model__meta' );
361              
362             my $parent_model_meta = $model_class->_model__meta->parent;
363              
364             die "Strange, model source for $model_class doesn't exist, but it doesn't have a parent" unless $parent_model_meta;
365              
366             my $parent_model_class = $parent_model_meta->model_class;
367             my $parent_model_source = $self->model_source_by_model_class( $parent_model_class );
368              
369             $model_source = $parent_model_source->clone( model_class => $model_class );
370            
371             $self->_register_model_source( $model_source );
372              
373             return $model_source;
374             }
375              
376             sub _register_model_source {
377             my $self = shift;
378             my $model_source = shift;
379             push @{ $self->_model_source_list }, $model_source;
380              
381             my $moniker = $model_source->moniker;
382             my $moniker_key = "::${moniker}";
383             my $model_class = $model_source->model_class;
384             my $model_class_key = "+${model_class}";
385              
386             $self->_model_source_lookup_map->{$model_class_key} = $model_source;
387              
388             $self->_model_source_lookup_map->{$model_class} = $model_class_key;
389             $self->_model_source_lookup_map->{$moniker} = $model_class_key;
390             $self->_model_source_lookup_map->{$moniker_key} = $model_class_key;
391             # TODO Add more aliasing
392             }
393              
394             sub create {
395             my $self = shift;
396             my $key = shift;
397             return $self->model_source( $key )->create( @_ );
398             }
399              
400             sub inflate {
401             my $self = shift;
402             my $key = shift;
403             return $self->model_source( $key )->inflate( @_ );
404             }
405              
406             sub search {
407             my $self = shift;
408             my $key = shift;
409             return $self->model_source( $key )->search( @_ );
410             }
411              
412             =head1 AUTHOR
413              
414             Robert Krimen, C<< <rkrimen at cpan.org> >>
415              
416             =head1 BUGS
417              
418             Please report any bugs or feature requests to C<bug-dbicx-modeler at rt.cpan.org>, or through
419             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=DBICx-Modeler>. I will be notified, and then you'll
420             automatically be notified of progress on your bug as I make changes.
421              
422              
423              
424              
425             =head1 SUPPORT
426              
427             You can find documentation for this module with the perldoc command.
428              
429             perldoc DBICx::Modeler
430              
431              
432             You can also look for information at:
433              
434             =over 4
435              
436             =item * RT: CPAN's request tracker
437              
438             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=DBICx-Modeler>
439              
440             =item * AnnoCPAN: Annotated CPAN documentation
441              
442             L<http://annocpan.org/dist/DBICx-Modeler>
443              
444             =item * CPAN Ratings
445              
446             L<http://cpanratings.perl.org/d/DBICx-Modeler>
447              
448             =item * Search CPAN
449              
450             L<http://search.cpan.org/dist/DBICx-Modeler/>
451              
452             =back
453              
454              
455             =head1 ACKNOWLEDGEMENTS
456              
457              
458             =head1 COPYRIGHT & LICENSE
459              
460             Copyright 2009 Robert Krimen, all rights reserved.
461              
462             This program is free software; you can redistribute it and/or modify it
463             under the same terms as Perl itself.
464              
465              
466             =cut
467              
468             1; # End of DBICx::Modeler