blib/lib/Dancer/Plugin/SimpleCRUD.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 318 | 537 | 59.2 |
branch | 105 | 234 | 44.8 |
condition | 42 | 114 | 36.8 |
subroutine | 28 | 34 | 82.3 |
pod | n/a | ||
total | 493 | 919 | 53.6 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | # First, a dead simple object that can be fed a hashref of params from the | ||||||
2 | # Dancer params() keyword, and returns then when its param() method is called, | ||||||
3 | # so that we can feed it to CGI::FormBuilder: | ||||||
4 | package Dancer::Plugin::SimpleCRUD::ParamsObject; | ||||||
5 | |||||||
6 | sub new { | ||||||
7 | 0 | 0 | 0 | my ($class, $params) = @_; | |||
8 | 0 | 0 | return bless { params => $params }, $class; | ||||
9 | } | ||||||
10 | |||||||
11 | sub param { | ||||||
12 | 0 | 0 | 0 | my ($self, @args) = @_; | |||
13 | |||||||
14 | # If called with no args, return all param names | ||||||
15 | 0 | 0 | 0 | if (!@args) { | |||
0 | |||||||
0 | |||||||
16 | 0 | 0 | 0 | return $self->{params} if !$paramname; | |||
17 | |||||||
18 | # With one arg, act as an accessor | ||||||
19 | } elsif (@args == 1) { | ||||||
20 | 0 | 0 | return $self->{params}{ $args[0] }; | ||||
21 | |||||||
22 | # With two args, act as a mutator | ||||||
23 | } elsif ($args == 2) { | ||||||
24 | 0 | 0 | return $self->{params}{ $args[0] } = $args[1]; | ||||
25 | } | ||||||
26 | } | ||||||
27 | |||||||
28 | # Now, on to the real stuff | ||||||
29 | package Dancer::Plugin::SimpleCRUD; | ||||||
30 | |||||||
31 | 3 | 3 | 382747 | use warnings; | |||
3 | 12 | ||||||
3 | 88 | ||||||
32 | 3 | 3 | 13 | use strict; | |||
3 | 5 | ||||||
3 | 51 | ||||||
33 | 3 | 3 | 809 | use Dancer::Plugin; | |||
3 | 132699 | ||||||
3 | 173 | ||||||
34 | 3 | 3 | 1013 | use Dancer qw(:syntax); | |||
3 | 275942 | ||||||
3 | 14 | ||||||
35 | 3 | 3 | 1782 | use Dancer::Plugin::Database; | |||
3 | 45184 | ||||||
3 | 130 | ||||||
36 | 3 | 3 | 1374 | use HTML::Table::FromDatabase; | |||
3 | 60573 | ||||||
3 | 104 | ||||||
37 | 3 | 3 | 1807 | use CGI::FormBuilder; | |||
3 | 61065 | ||||||
3 | 141 | ||||||
38 | 3 | 3 | 1368 | use HTML::Entities; | |||
3 | 14843 | ||||||
3 | 260 | ||||||
39 | 3 | 3 | 30 | use URI::Escape; | |||
3 | 6 | ||||||
3 | 139 | ||||||
40 | 3 | 3 | 1492 | use List::MoreUtils qw( first_index uniq ); | |||
3 | 32948 | ||||||
3 | 20 | ||||||
41 | |||||||
42 | our $VERSION = '1.15'; | ||||||
43 | |||||||
44 | =encoding utf8 | ||||||
45 | |||||||
46 | =head1 NAME | ||||||
47 | |||||||
48 | Dancer::Plugin::SimpleCRUD - very simple CRUD (create/read/update/delete) | ||||||
49 | |||||||
50 | |||||||
51 | =head1 DESCRIPTION | ||||||
52 | |||||||
53 | A plugin for Dancer web applications, to use a few lines of code to create | ||||||
54 | appropriate routes to support creating/editing/deleting/viewing records within a | ||||||
55 | database table. Uses L |
||||||
56 | L |
||||||
57 | L |
||||||
58 | |||||||
59 | Setting up forms and code to display and edit database records is a very common | ||||||
60 | requirement in web apps; this plugin tries to make something basic trivially | ||||||
61 | easy to set up and use. | ||||||
62 | |||||||
63 | |||||||
64 | =head1 SYNOPSIS | ||||||
65 | |||||||
66 | The following assumes that you already have a working L |
||||||
67 | put your database connection details in your C |
||||||
68 | L |
||||||
69 | connection. | ||||||
70 | |||||||
71 | # In your Dancer app, | ||||||
72 | use Dancer::Plugin::SimpleCRUD; | ||||||
73 | |||||||
74 | # Simple example: | ||||||
75 | simple_crud( | ||||||
76 | record_title => 'Widget', | ||||||
77 | prefix => '/widgets', | ||||||
78 | db_table => 'widgets', | ||||||
79 | editable => 1, | ||||||
80 | ); | ||||||
81 | |||||||
82 | # The above would create a route to handle C, listing all widgets, | ||||||
83 | # with options to add/edit entries (linking to C and | ||||||
84 | # C respectively) where a form to add a new entry or edit | ||||||
85 | # an existing entry will be created. | ||||||
86 | # All fields in the database table would be editable. | ||||||
87 | # | ||||||
88 | # There is also a view route, C, which shows all the values | ||||||
89 | # for the fields of a single database entry. | ||||||
90 | |||||||
91 | # A more in-depth synopsis, using all options (of course, usually you'd only | ||||||
92 | # need to use a few of the options where you need to change the default | ||||||
93 | # behaviour): | ||||||
94 | |||||||
95 | simple_crud( | ||||||
96 | record_title => 'Team', | ||||||
97 | prefix => '/teams', | ||||||
98 | db_table => 'team', | ||||||
99 | labels => { # More human-friendly labels for some columns | ||||||
100 | venue_id => 'Home Venue', | ||||||
101 | name => 'Team Name', | ||||||
102 | }, | ||||||
103 | validation => { # validate values entered for some columns | ||||||
104 | division => qr/\d+/, | ||||||
105 | }, | ||||||
106 | input_types => { # overriding form input type for some columns | ||||||
107 | supersecret => 'password', | ||||||
108 | lotsoftext' => 'textarea', | ||||||
109 | }, | ||||||
110 | key_column => 'id', # id is default anyway | ||||||
111 | editable_columns => [ qw( venue_id name division ) ], | ||||||
112 | display_columns => [ qw( id venue_id name division ) ], | ||||||
113 | deleteable => 1, | ||||||
114 | editable => 1, | ||||||
115 | addable => 0, # does not allow adding rows | ||||||
116 | sortable => 1, | ||||||
117 | paginate => 300, | ||||||
118 | template => 'simple_crud.tt', | ||||||
119 | query_auto_focus => 1, | ||||||
120 | downloadable => 1, | ||||||
121 | foreign_keys => { | ||||||
122 | columnname => { | ||||||
123 | table => 'venues', | ||||||
124 | key_column => 'id', | ||||||
125 | label_column => 'name', | ||||||
126 | }, | ||||||
127 | }, | ||||||
128 | table_class => 'table table-bordered', | ||||||
129 | paginate_table_class => 'table table-borderless', | ||||||
130 | custom_columns => [ | ||||||
131 | { | ||||||
132 | name => "division_news", | ||||||
133 | raw_column => "division", | ||||||
134 | transform => sub { | ||||||
135 | my $division_name = shift; | ||||||
136 | my $label = "News about $division_name"; | ||||||
137 | $division_name =~ s/([^-_.~A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; | ||||||
138 | my $search = qq{http://news.google.com/news?q="$division_name"}; | ||||||
139 | return "$label"; | ||||||
140 | }, | ||||||
141 | column_class => "column-class", | ||||||
142 | }, | ||||||
143 | ], | ||||||
144 | auth => { | ||||||
145 | view => { | ||||||
146 | require_login => 1, | ||||||
147 | }, | ||||||
148 | edit => { | ||||||
149 | require_role => 'Admin', | ||||||
150 | }, | ||||||
151 | }, | ||||||
152 | ); | ||||||
153 | |||||||
154 | |||||||
155 | |||||||
156 | =head1 USAGE | ||||||
157 | |||||||
158 | This plugin provides a C |
||||||
159 | described below, and sets up the appropriate routes to present add/edit/delete | ||||||
160 | options. | ||||||
161 | |||||||
162 | =head1 OPTIONS | ||||||
163 | |||||||
164 | The options you can pass to simple_crud are: | ||||||
165 | |||||||
166 | =over 4 | ||||||
167 | |||||||
168 | =item C |
||||||
169 | |||||||
170 | What we're editing, for instance, if you're editing widgets, use 'Widget'. Will | ||||||
171 | be used in form titles (for instance "Add a ...", "Edit ..."), and button | ||||||
172 | labels. | ||||||
173 | |||||||
174 | =item C |
||||||
175 | |||||||
176 | The prefix for the routes which will be created. Given a prefix of C, | ||||||
177 | then you can go to C to create a new Widget, and C to | ||||||
178 | edit the widget with the ID (see key_column) 42. | ||||||
179 | |||||||
180 | Don't confuse this with Dancer's C |
||||||
181 | before the prefix you pass to this plugin. For example, if you used: | ||||||
182 | |||||||
183 | prefix '/foo'; | ||||||
184 | simple_crud( | ||||||
185 | prefix => 'bar', | ||||||
186 | ... | ||||||
187 | ); | ||||||
188 | |||||||
189 | ... then you'd end up with e.g. C as the record listing page. | ||||||
190 | |||||||
191 | =item C |
||||||
192 | |||||||
193 | The name of the database table. | ||||||
194 | |||||||
195 | =item C |
||||||
196 | |||||||
197 | Specify which column in the table is the primary key. If not given, defaults to | ||||||
198 | id. | ||||||
199 | |||||||
200 | =item C |
||||||
201 | |||||||
202 | Specify one or more 'where' clauses to use to filter the table. For example: | ||||||
203 | |||||||
204 | simple_crud( | ||||||
205 | prefix => 'bar', | ||||||
206 | where_filter => {user_id => 1000}, | ||||||
207 | ... | ||||||
208 | ); | ||||||
209 | |||||||
210 | This would cause only rows with an user_id of 1000 to be displayed in listings | ||||||
211 | and search results, viewed, edited etc. | ||||||
212 | |||||||
213 | The C |
||||||
214 | used by L |
||||||
215 | example - see the | ||||||
216 | L |
||||||
217 | |||||||
218 | Alternatively, if the filter condition needs to be calculated at runtime (for | ||||||
219 | example, based on the logged in user calling it), then you can provide a coderef | ||||||
220 | which returns the WHERE clause hashref - for instance: | ||||||
221 | |||||||
222 | where_filter => sub { { customer_id => logged_in_user()->{customer_id} } }, | ||||||
223 | |||||||
224 | =item C |
||||||
225 | |||||||
226 | We use L |
||||||
227 | allows you to specify the name of a connection defined in the config file to | ||||||
228 | use. See the documentation for L |
||||||
229 | database configurations work. If this is not supplied or is empty, the default | ||||||
230 | database connection details in your config file will be used - this is often | ||||||
231 | what you want, so unless your app is dealing with multiple DBs, you probably | ||||||
232 | won't need to worry about this option. | ||||||
233 | |||||||
234 | =item C |
||||||
235 | |||||||
236 | A hashref of field_name => 'Label', if you want to provide more user-friendly | ||||||
237 | labels for some or all fields. As we're using CGI::FormBuilder, it will do a | ||||||
238 | reasonable job of figuring these out for itself usually anyway - for instance, a | ||||||
239 | field named C |
||||||
240 | |||||||
241 | =item C |
||||||
242 | |||||||
243 | A hashref of field_name => input type, if you want to override the default type | ||||||
244 | of input which would be selected by L |
||||||
245 | default, password fields will be used for field names like 'password', 'passwd' | ||||||
246 | etc, and text area inputs will be used for columns with type 'TEXT'). | ||||||
247 | |||||||
248 | Valid values include anything allowed by HTML, e.g. C |
||||||
249 | C |