File Coverage

blib/lib/Dancer2/Plugin/DBIx/Class.pm
Criterion Covered Total %
statement 76 95 80.0
branch 25 42 59.5
condition 0 2 0.0
subroutine 10 10 100.0
pod 1 2 50.0
total 112 151 74.1


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::DBIx::Class 1.06;
2 5     5   5719074 use Modern::Perl;
  5         2946  
  5         34  
3 5     5   1018 use Carp;
  5         12  
  5         343  
4 5     5   2377 use Class::C3::Componentised;
  5         17453  
  5         193  
5 5     5   2279 use Dancer2::Plugin::DBIx::Class::ExportBuilder;
  5         16  
  5         169  
6 5     5   2848 use Dancer2::Plugin;
  5         68742  
  5         52  
7              
8             my $_schemas = {};
9              
10             sub BUILD {
11 5     5 0 11130 my ($self) = @_;
12 5         107 my $config = $self->config;
13 5     3   242 my $call_rs = sub { shift->schema->resultset(@_) };
  3         177863  
14 5         13 @{ $self->keywords }{'rs'} = $call_rs;
  5         100  
15 5         27 @{ $self->keywords }{'rset'} = $call_rs;
  5         83  
16 5         26 @{ $self->keywords }{'resultset'} = $call_rs;
  5         80  
17 5     4   30 @{ $self->keywords }{'schema'} = sub { shift->schema(@_) };
  5         84  
  4         33739  
18 5 50       33 if ( defined $config->{default} ) {
19 5 50       32 if ( !$config->{default}->{alias} ) {
20             my $export_builder
21             = Dancer2::Plugin::DBIx::Class::ExportBuilder->new(
22 5         14 map { $_ => $config->{default}->{$_} }
  25         95  
23             qw(schema_class dsn user password export_prefix) );
24 5         7708 my %new_keywords = $export_builder->exports;
25 5         18 foreach
26 5         67 my $dsl_keyword ( keys %{ Dancer2::Core::DSL->dsl_keywords } ) {
27 405         1239 delete $new_keywords{$dsl_keyword};
28             }
29 5         149 @{ $self->keywords }{ keys %new_keywords } = values %new_keywords;
  5         154  
30             }
31             }
32 5         1563 foreach my $schema ( keys %$config ) {
33 12 100       991 next if $schema eq 'default';
34 7 100       33 next if $config->{$schema}->{alias};
35             my $export_builder
36             = Dancer2::Plugin::DBIx::Class::ExportBuilder->new(
37 5         21 map { $_ => $config->{$schema}->{$_} }
  25         242  
38             qw(schema_class dsn user password export_prefix) );
39 5         208 my %new_keywords = $export_builder->exports;
40 5         18 foreach my $dsl_keyword ( keys %{ Dancer2::Core::DSL->dsl_keywords } ) {
  5         54  
41 405         1269 delete $new_keywords{$dsl_keyword};
42             }
43 5         189 foreach my $new_keyword ( keys %new_keywords ) {
44 18 100       374 next if defined $self->keywords->{$new_keyword};
45 14         310 $self->keywords->{$new_keyword} = $new_keywords{$new_keyword};
46             }
47             }
48             }
49              
50             sub schema {
51 7     7 1 30 my ( $self, $name, $schema_cfg ) = @_;
52              
53 7         144 my $cfg = $self->config;
54              
55 7 100       95 if ( not defined $name ) {
56 4 50       11 my @names = keys %{$cfg}
  4         30  
57             or croak('No schemas are configured');
58              
59             # Either pick the only one in the config or the default
60 4 50       23 $name = @names == 1 ? $names[0] : 'default';
61             }
62              
63 7 50       39 my $options = $cfg->{$name}
64             or croak "The schema $name is not configured";
65              
66 7 50       30 if ($schema_cfg) {
67 0         0 return $self->_create_schema( $name, $schema_cfg );
68             }
69              
70 7 100       61 return $_schemas->{$name} if $_schemas->{$name};
71              
72 3 100       14 if ( my $alias = $options->{alias} ) {
73 1 50       7 $options = $cfg->{$alias}
74             or croak "The schema alias $alias does not exist in the config";
75 1 50       13 return $_schemas->{$alias} if $_schemas->{$alias};
76             }
77              
78 2         11 my $schema = $self->_create_schema( $name, $options );
79 2         24 return $_schemas->{$name} = $schema;
80             }
81              
82             sub _create_schema {
83 2     2   9 my ( $self, $name, $options ) = @_;
84             my @conn_info
85             = $options->{connect_info}
86 0         0 ? @{ $options->{connect_info} }
87 2 50       17 : @$options{qw(dsn user password options)};
88 2 50       9 if ( exists $options->{pass} ) {
89 0         0 warn 'The pass option is deprecated. Use password instead.';
90 0         0 $conn_info[2] = $options->{pass};
91             }
92              
93 2         6 my $schema;
94 2 50       9 if ( my $schema_class = $options->{schema_class} ) {
95 2         7 $schema_class =~ s/-/::/g;
96             eval {
97             Class::C3::Componentised->ensure_class_loaded(
98 2         25 $options->{schema_class} );
99 2         41 1;
100             }
101             or croak 'Schema class '
102             . $options->{schema_class}
103 2 50       5 . ' unable to load';
104 2 50       10 if ( my $replicated = $options->{replicated} ) {
105 0         0 $schema = $schema_class->clone;
106 0         0 my %storage_options;
107 0         0 my @params = qw( balancer_type balancer_args pool_type pool_args );
108 0         0 for my $p (@params) {
109 0         0 my $value = $replicated->{$p};
110 0 0       0 $storage_options{$p} = $value if defined $value;
111             }
112 0         0 $schema->storage_type( [ '::DBI::Replicated', \%storage_options ] );
113 0         0 $schema->connection(@conn_info);
114             $schema->storage->connect_replicants(
115 0         0 @{ $replicated->{replicants} } );
  0         0  
116             }
117             else {
118 2         41 $schema = $schema_class->connect(@conn_info);
119             }
120             }
121             else {
122 0         0 my $dbic_loader = 'DBIx::Class::Schema::Loader';
123 0 0       0 eval { Class::C3::Componentised->ensure_class_loaded($dbic_loader) }
  0         0  
124             or croak
125             "You must provide a schema_class option or install $dbic_loader.";
126 0   0     0 $dbic_loader->naming( $options->{schema_loader_naming} || 'v7' );
127 0         0 $schema = DBIx::Class::Schema::Loader->connect(@conn_info);
128             }
129              
130 2         3369 return $schema;
131             }
132              
133             1;
134              
135             =pod
136              
137             =encoding UTF-8
138              
139             =head1 NAME
140              
141             Dancer2::Plugin::DBIx::Class - syntactic sugar for DBIx::Class in Dancer2, optionally with DBIx::Class::Schema::ResultSetNames
142              
143             =head1 VERSION
144              
145             version 1.06
146              
147             =head1 SYNOPSIS
148              
149             # In your Dancer2 app, without DBIx::Class::Schema::ResultSetNames
150             # (but why would you?)
151             my $results = resultset('Human')->search( { . . .} );
152             #
153             # or, with DBIx::Class::Schema::ResultSetNames
154             my $results = humans->search( { . . . } );
155             my $single_person = human($human_id);
156              
157             =head1 DESCRIPTION
158              
159             Dancer2::Plugin::DBIx::Class adds convenience keywords to the DSL for L<Dancer2>, in order to make
160             database calls more semantically-friendly. This module is intended to be a forklift-upgrade for
161             L<Dancer2::Plugin::DBIC> enabling the user to deploy this plugin on already-running Dancer2 apps,
162             then add L<DBIx::Class::Schema::ResultSetNames> to new code.
163              
164             =head1 CONFIGURATION
165              
166             The configuration for this plugin can go in your config.yml, or in your environment:
167              
168             plugins:
169             DBIx::Class:
170             default:
171             dsn: dbi:SQLite:dbname=my.db # Just about any DBI-compatible DSN goes here
172             schema_class: MyApp::Schema
173             export_prefix: 'db' # Optional, unless a table name (singular or plural)
174             # is also a DSL keyword.
175             second: # You can use multiple schemas!
176             dsn: dbi:Pg:dbname=foo
177             schema_class: Foo::Schema
178             user: bob
179             password: secret
180             options:
181             RaiseError: 1
182             PrintError: 1
183             third:
184             alias: 'default' # Yep, aliases work too.
185              
186             =head1 YOU HAVE BEEN WARNED
187              
188             The "optional" C<export_prefix> configuration adds the given prefix to the ResultSet names, if you
189             are using L<DBIx::Class::Schema::ResultSetNames>. You don't need to include an underscore at the
190             end, you get that for free. It is wise to do this, if you have table names whose singular or plural
191             terms collide with L<Dancer2::Core::DSL> keywords, or those added by other plugins. In the event
192             that your term collides with a L<Dancer2::Core::DSL> keyword, it will not be added to this plugin,
193             and the functionality of the DSL keyword will take precedence.
194              
195             =head1 FUNCTIONS
196              
197             =head2 schema
198              
199             This keyword returns the related L<DBIx::Class::Schema> object, ready for use. Given without parameters,
200             it will return the 'default' schema, or the first one that was created, or the only one, if there is
201             only one.
202              
203             =head2 resultset, rset, rs
204              
205             These three keywords are syntactically identical, and, given a name of a L<DBIx::Class::ResultSet>
206             object, will return the resultset, ready for searching, or any other method you can use on a ResultSet:
207              
208             my $cars = rs('Car')->search({ . . .});
209              
210             If you specify these without a C<schema> call before it, it will assume the default schema, as above.
211              
212             =head1 NAMED RESULT SETS
213              
214             L<DBIx::Class::Schema::ResultSetNames> adds both singular and plural method accessors for all resultsets.
215              
216             So, instead of this:
217              
218             my $result_set = resultset('Author')->search({...});
219              
220             you may choose to this:
221              
222             my $result_set = authors->search({...});
223              
224             And instead of this:
225              
226             my $result = resultset('Author')->find($id);
227              
228             you may choose to this:
229              
230             my $result = author($id)
231              
232             The usual caveats apply to C<find()> returning multiple records; that behavior is deprecated, so if you
233             try to do something like:
234              
235             my $result = author( { first_name => 'John'} );
236              
237             ...odds are things will blow up in your face a lot. Using a unique key in C<find()> is important.
238              
239             =head1 BUT THAT'S NOT ALL!
240              
241             If you combine this module, L<DBIx::Class::Schema::ResultSetNames>, and L<DBIx::Class::Helper::ResultSet::Shortcut>,
242             you can do some really fabulous, easy-to-read things in a L<Dancer2> route, like:
243              
244             # find all the books for an author, give me an array of
245             # their books as Row objects, with the editions prefetched.
246             #
247             my @books = author($id)->books->prefetch('edition')->all
248            
249             # send a JSON-encoded list of hashrefs of authors with first names
250             # that start with 'John' and their works to your front-end framework
251             # (Some, like DevExtreme, do not cope well with the objects.)
252             #
253             send_as JSON => [ authors->like( 'first_name', 'John%')->prefetch('books')->hri->all ];
254              
255             There are many really snazzy things to be found in L<DBIx::Class::Helpers>. Many of them can make
256             your code much more readable. Definitely worth a look-see.
257              
258             Remember: your code has two developers: you, and you six months from now.
259              
260             Remember also: You should write your code like the next developer to work on it is
261             a psychopath who knows where you live.
262              
263             =head1 SEE ALSO
264              
265             =over 4
266              
267             =item *
268              
269             L<DBIx::Class::ResultSet>
270              
271             =item *
272              
273             L<DBIx::Class::Schema::ResultSetNames>
274              
275             =item *
276              
277             L<DBIx::Class::Schema>
278              
279             =back
280              
281             =head1 CREDIT WHERE CREDIT IS DUE
282              
283             Practically all of this code is the work of L<Matt S Trout (mst)|https://metacpan.org/author/MSTROUT>.
284             I just tidied things up and wrote documentation.
285              
286             =head1 SOURCE
287              
288             L<https://gitlab.com/geekruthie/Dancer2-Plugin-DBIx-Class>
289              
290             =head1 HOMEPAGE
291              
292             L<https://metacpan.org/release/Dancer2-Plugin-DBIx-Classs>
293              
294             =head1 AUTHOR
295              
296             D Ruth Holloway <ruth@hiruthie.me>
297              
298             =head1 COPYRIGHT AND LICENSE
299              
300             This software is copyright (c) 2021 by D Ruth Holloway.
301              
302             This is free software; you can redistribute it and/or modify it under
303             the same terms as the Perl 5 programming language system itself.
304              
305             =cut
306              
307             __END__
308              
309             # ABSTRACT: syntactic sugar for DBIx::Class in Dancer2, optionally with DBIx::Class::Schema::ResultSetNames
310