File Coverage

blib/lib/DBIx/Class/Wrapper.pm
Criterion Covered Total %
statement 34 37 91.8
branch 6 8 75.0
condition n/a
subroutine 7 7 100.0
pod 1 1 100.0
total 48 53 90.5


line stmt bran cond sub pod time code
1             package DBIx::Class::Wrapper;
2             $DBIx::Class::Wrapper::VERSION = '0.009';
3 5     5   1161954 use Moose::Role;
  5         1136250  
  5         22  
4 5     5   23420 use Moose::Meta::Class;
  5         12  
  5         130  
5 5     5   1890 use Module::Pluggable::Object;
  5         25093  
  5         149  
6 5     5   35 use Class::Load;
  5         9  
  5         1867  
7              
8             =head1 NAME
9              
10             DBIx::Class::Wrapper - A Moose role to allow your business model to wrap business code around a dbic model.
11              
12             =head1 BUILD STATUS
13              
14             =begin html
15              
16             <a href="https://travis-ci.org/jeteve/DBIx-Class-Wrapper"><img src="https://travis-ci.org/jeteve/DBIx-Class-Wrapper.svg?branch=master"></a>
17              
18             =end html
19              
20             =head1 SYNOPSIS
21              
22             This package allows you to easily extend your DBIC Schema by Optionally wrapping its resultsets and result objects
23             in your own business classes.
24              
25             =head2 Basic usage with no specific wrapping at all
26              
27             package My::Model;
28             use Moose;
29             with qw/DBIx::Class::Wrapper/;
30             1
31              
32             Later
33              
34             my $schema = instance of DBIx schema
35             my $app = My::Model->new( { dbic_schema => $schema } );
36             ## And use the dbic resultsets-ish methods.
37             my $products = $app->dbic_factory('Product'); ## Get a new instance of the Product resultset.
38              
39             ## Use classic DBIC methods as usual.
40             my $p = $products->find(2);
41             my $blue_ps = $products->search({ colour => blue });
42              
43              
44             =head2 Implement your own product class with business methods.
45              
46             First you need a DBIC factory that will wrap the raw dbic object into your own class of product
47              
48             package My::Model::Wrapper::Factory::Product;
49             use Moose; extends qw/DBIx::Class::Wrapper::Factory/ ;
50             sub wrap{
51             my ($self , $o) = @_;
52             return My::Model::O::Product->new({o => $o , factory => $self });
53             }
54             1;
55              
56             Then your Product business object class
57              
58             package My::Model::O::Product;
59             use Moose;
60             has 'o' => ( isa => 'My::Schema::Product', ## The raw DBIC object class.
61             is => 'ro' , required => 1,
62             handles => [ 'id' , 'name', 'active' ] ## handles standard properties
63             );
64             ## A business method
65             sub activate{
66             my ($self) = @_;
67             $self->o->update({ active => 1 });
68             }
69              
70             Then from your main code, continue using the Product resultset as normal.
71              
72             my $product = $app->dbic_factory('Product')->find(1);
73             ## But you can do
74             $product->activate();
75             ## so now
76             $product->active() == 1;
77              
78              
79             =head2 Your own specialised resultset
80              
81             Let's say you decide that from now, the bulk of your application should access only active products,
82             leaving unlimited access to all product to a limited set of places.
83              
84             package My::Model::Wrapper::Factory::Product;
85             use Moose;
86             extends qw/DBIx::Class::Wrapper::Factory/;
87             sub build_dbic_rs{
88             my ($self) = @_;
89             ## Note that you can always access your original business model
90             ## from a factory (method bm).
91             return $self->bm->dbic_schema->resultset('Product')->search_rs({ active => 1});
92             ## This is a simple example. You can restrict your products set
93             ## according to any current property of your business model for instance.
94             }
95             sub wrap{ .. same .. }
96             1;
97              
98             Everywhere your application uses $app->dbic_factory('Product') is now
99             restricted to active products only.
100              
101             Surely you want admin parts of your application to access all products.
102             So here's a very basic AllProducts:
103              
104             package My::Model::Wrapper::Factory::AllProduct;
105             use Moose; extends qw/My::Model::Wrapper::Factory::Product/;
106             sub build_dbic_rs{
107             my ($self) = @_;
108             ## Some extra security.
109             unless( $self->bm->current_user()->is_admin() ){ confess "Sorry you cant access that"; }
110              
111             return $self->bm()->dbic_schema->resultset('Product')->search_rs();
112             }
113              
114              
115             =head2 Changing the factory base class.
116              
117             Until now, all your custom factories were named My::Model::Wrapper::Factory::<something>.
118              
119             If you want to customise the base class of those custom factories, you can do so by overriding
120             the method _build_dbic_factory_baseclass in your model:
121              
122             package My::Model;
123              
124             use Moose;
125             with qw/DBIx::Class::Wrapper/;
126              
127             sub _build_dbic_factory_baseclass{
128             return 'My::Model::DBICFactory'; # for instance.
129             }
130              
131             Then implement your factories as subpackages of My::Model::DBICFactory
132              
133             =cut
134              
135             has 'dbic_schema' => ( is => 'rw' , isa => 'DBIx::Class::Schema' , required => 1 );
136             has 'dbic_factory_baseclass' => ( is => 'ro' , isa => 'Str' , lazy_build => 1);
137              
138             has '_dbic_dbic_fact_classes' => ( is => 'ro' , isa => 'HashRef[Bool]' , lazy_build => 1);
139              
140             sub _build_dbic_factory_baseclass{
141 2     2   7 my ($self) = @_;
142 2         76 return ref ($self).'::Wrapper::Factory';
143             }
144              
145             sub _build__dbic_dbic_fact_classes{
146 3     3   9 my ($self) = @_;
147 3         78 my $baseclass = $self->dbic_factory_baseclass();
148 3         9 my $res = {};
149 3         36 my $mp = Module::Pluggable::Object->new( search_path => [ $baseclass ]);
150 3         38 foreach my $candidate_class ( $mp->plugins() ){
151 9         3793 Class::Load::load_class( $candidate_class );
152             # Code is loaded
153 9 50       21636 unless( $candidate_class->isa('DBIx::Class::Wrapper::Factory') ){
154 0         0 warn "Class $candidate_class does not extend DBIx::Class::Wrapper::Factory.";
155 0         0 next;
156             }
157             # And inherit from the right class.
158 9         31 $res->{$candidate_class} = 1;
159             }
160 3         144 return $res;
161             }
162              
163             =head1 METHODS
164              
165             =head2 dbic_factory
166              
167             Returns a new instance of L<DBIx::Class::Wrapper::Factory> that wraps around the given DBIC ResultSet name
168             if such a resultset exists. Dies otherwise.
169              
170             Additionaly, you can set a ad-hoc resulset if you want to locally restrict your original resultset.
171              
172             usage:
173              
174             my $f = $this->dbic_factory('Article');
175              
176             my $f = $this->dbic_factory('Article' , { dbic_rs => $schema->resultset('Article')->search_rs({ is_active => 1 }) });
177              
178             =cut
179              
180             sub dbic_factory{
181 22     22 1 38626 my ($self , $name , $init_args ) = @_;
182 22 100       77 unless( defined $init_args ){
183 20         43 $init_args = {};
184             }
185 22 50       67 unless( $name ){
186 0         0 confess("Missing name in call to dbic_factory");
187             }
188 22         676 my $class_name = $self->dbic_factory_baseclass().'::'.$name;
189              
190             ## Build a class dynamically if necessary
191 22 100       548 unless( $self->_dbic_dbic_fact_classes->{$class_name} ){
192             ## We need to build such a class.
193 6         51 Moose::Meta::Class->create($class_name => ( superclasses => [ 'DBIx::Class::Wrapper::Factory' ] ));
194 6         16246 $self->_dbic_dbic_fact_classes->{$class_name} = 1;
195             }
196             ## Ok, $class_name is now there
197              
198             ## Note that the factory will built its own resultset from this model and the name
199 22         749 my $instance = $class_name->new({ bm => $self , name => $name , %$init_args });
200             ## This will die instantly if cannot find a dbic_rs
201 22         30111 my $dbic_rs = $instance->dbic_rs();
202 20         146 return $instance;
203             }
204              
205             1;