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