Commit | Line | Data |
9ba33dac |
1 | =head1 NAME |
2 | |
3 | DBIx::Class::Manual::Features - A boatload of DBIx::Class features with links to respective documentation |
4 | |
5 | =head1 META |
6 | |
7 | =head2 Large Community |
8 | |
9 | Currently there are 88 people listed as contributors to DBIC. That ranges |
10 | from documentation help, to test help, to added features, to entire database |
11 | support. |
12 | |
13 | =head2 Active Community |
14 | |
f165eda8 |
15 | Currently (June 9, 2010) 6 active branches (committed to |
9ba33dac |
16 | in the last two weeks) in git. Last release (0.08122) |
17 | had 14 new features, and 16 bug fixes. Of course that |
18 | L<ebbs and flows|http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits/DBIx-Class.git;a=blob;f=Changes>.) |
19 | |
20 | =head2 Responsive Community |
21 | |
22 | =over 1 |
23 | |
24 | =item I needed MSSQL order-by support; the community helped me add support |
25 | |
26 | =item generally very welcoming of people willing to help |
27 | |
28 | =back |
29 | |
30 | =head1 General ORM |
31 | |
32 | These are things that are in most other ORMs, but are still reasons to use |
33 | DBIC over raw SQL. |
34 | |
35 | =head2 Cross DB |
36 | |
37 | The vast majority of code should run on all databases without needing tweaking |
38 | |
39 | =head2 Basic CRUD |
40 | |
41 | =over 1 |
42 | |
43 | =item C - Create |
44 | |
45 | =item R - Retrieve |
46 | |
47 | =item U - Update |
48 | |
49 | =item D - Delete |
50 | |
51 | =back |
52 | |
53 | =head2 SQL: Create |
54 | |
55 | my $sth = $dbh->prepare(' |
56 | INSERT INTO books |
57 | (title, author_id) |
58 | values (?,?) |
59 | '); |
60 | |
61 | $sth->execute( 'A book title', $author_id ); |
62 | |
63 | =head2 DBIC: Create |
64 | |
65 | my $book = $book_rs->create({ |
66 | title => 'A book title', |
67 | author_id => $author_id, |
68 | }); |
69 | |
70 | See L<DBIx::Class::ResultSet/create> |
71 | |
72 | =over 1 |
73 | |
74 | =item No need to pair placeholders and values |
75 | |
76 | =item Automatically gets autoincremented id for you |
77 | |
78 | =item Transparently uses INSERT ... RETURNING for databases that support it |
79 | |
80 | =back |
81 | |
82 | =head2 SQL: Read |
83 | |
84 | my $sth = $dbh->prepare(' |
85 | SELECT title, |
86 | authors.name as author_name |
87 | FROM books, authors |
88 | WHERE books.author = authors.id |
89 | '); |
90 | |
91 | while ( my $book = $sth->fetchrow_hashref ) { |
92 | say "Author of $book->{title} is $book->{author_name}"; |
93 | } |
94 | |
95 | =head2 DBIC: Read |
96 | |
97 | my $book = $book_rs->find($book_id); |
98 | |
99 | or |
100 | |
101 | my $book = $book_rs->search({ title => 'A book title' }, { rows => 1 })->next; |
102 | |
103 | or |
104 | |
105 | my @books = $book_rs->search({ author => $author_id })->all; |
106 | |
107 | or |
108 | |
109 | while( my $book = $books_rs->next ) { |
110 | printf "Author of %s is %s\n", $book->title, $book->author->name; |
111 | } |
112 | |
113 | See L<DBIx::Class::ResultSet/find>, L<DBIx::Class::ResultSet/search>, L<DBIx::Class::ResultSet/next>, and L<DBIx::Class::ResultSet/all> |
114 | |
115 | B<TMTOWTDI!> |
116 | |
117 | =head2 SQL: Update |
118 | |
119 | my $update = $dbh->prepare(' |
120 | UPDATE books |
121 | SET title = ? |
122 | WHERE id = ? |
123 | '); |
124 | |
125 | $update->execute( 'New title', $book_id ); |
126 | |
127 | =head2 DBIC: Update |
128 | |
129 | $book->update({ title => 'New title' }); |
130 | |
131 | See L<DBIx::Class::Row/update> |
132 | |
133 | Will not update unless value changes |
134 | |
135 | =head2 SQL: Delete |
136 | |
137 | my $delete = $dbh->prepare('DELETE FROM books WHERE id = ?'); |
138 | |
139 | $delete->execute($book_id); |
140 | |
141 | =head2 DBIC: Delete |
142 | |
143 | $book->delete |
144 | |
145 | See L<DBIx::Class::Row/delete> |
146 | |
147 | =head2 SQL: Search |
148 | |
149 | my $sth = $dbh->prepare(' |
150 | SELECT title, |
151 | authors.name as author_name |
152 | FROM books |
153 | WHERE books.name LIKE "%monte cristo%" AND |
154 | books.topic = "jailbreak" |
155 | '); |
156 | |
157 | =head2 DBIC: Search |
158 | |
159 | my $book = $book_rs->search({ |
160 | 'me.name' => { -like => '%monte cristo%' }, |
161 | 'me.topic' => 'jailbreak', |
162 | })->next; |
163 | |
164 | =over 1 |
165 | |
166 | =item See L<SQL::Abstract>, L<DBIx::Class::ResultSet/next>, and L<DBIx::Class::ResultSet/search> |
167 | |
168 | =item (kinda) introspectible |
169 | |
170 | =item Prettier than SQL |
171 | |
172 | =back |
173 | |
4d1e63f4 |
174 | =head2 OO Overridability |
9ba33dac |
175 | |
176 | =over 1 |
177 | |
178 | =item Override new if you want to do validation |
179 | |
180 | =item Override delete if you want to disable deletion |
181 | |
182 | =item and on and on |
183 | |
184 | =back |
185 | |
186 | =head2 Convenience Methods |
187 | |
188 | =over 1 |
189 | |
190 | =item L<DBIx::Class::ResultSet/find_or_create> |
191 | |
192 | =item L<DBIx::Class::ResultSet/update_or_create> |
193 | |
194 | =back |
195 | |
196 | =head2 Non-column methods |
197 | |
198 | Need a method to get a user's gravatar URL? Add a C<gravatar_url> method to the |
199 | Result class |
200 | |
201 | =head2 RELATIONSHIPS |
202 | |
203 | =over 1 |
204 | |
205 | =item L<DBIx::Class::Relationship/belongs_to> |
206 | |
207 | =item L<DBIx::Class::Relationship/has_many> |
208 | |
209 | =item L<DBIx::Class::Relationship/might_have> |
210 | |
211 | =item L<DBIx::Class::Relationship/has_one> |
212 | |
213 | =item L<DBIx::Class::Relationship/many_to_many> |
214 | |
215 | =item SET AND FORGET |
216 | |
217 | =back |
218 | |
219 | =head1 DBIx::Class Specific Features |
220 | |
221 | These things may be in other ORM's, but they are very specific, so doubtful |
222 | |
223 | =head2 ->deploy |
224 | |
225 | Create a database from your DBIx::Class schema. |
226 | |
227 | my $schema = Frew::Schema->connect( $dsn, $user, $pass ); |
228 | |
229 | $schema->deploy |
230 | |
231 | See L<DBIx::Class::Schema/deploy>. |
232 | |
233 | See also: L<DBIx::Class::DeploymentHandler> |
234 | |
235 | =head2 Schema::Loader |
236 | |
237 | Create a DBIx::Class schema from your database. |
238 | |
239 | package Frew::Schema; |
240 | |
241 | use strict; |
242 | use warnings; |
243 | |
244 | use base 'DBIx::Class::Schema::Loader'; |
245 | |
246 | __PACKAGE__->loader_options({ |
247 | naming => 'v7', |
248 | debug => $ENV{DBIC_TRACE}, |
249 | }); |
250 | |
251 | 1; |
252 | |
253 | # elsewhere... |
254 | |
255 | my $schema = Frew::Schema->connect( $dsn, $user, $pass ); |
256 | |
257 | See L<DBIx::Class::Schema::Loader> and L<DBIx::Class::Schema::Loader::Base/CONSTRUCTOR_OPTIONS>. |
258 | |
259 | =head2 Populate |
260 | |
261 | Made for inserting lots of rows very quicky into database |
262 | |
263 | $schema->populate([ Users => |
264 | [qw( username password )], |
265 | [qw( frew >=4char$ )], |
266 | [qw( ... )], |
267 | [qw( ... )], |
268 | ); |
269 | |
270 | See L<DBIx::Class::Schema/populate> |
271 | |
272 | I use populate L<here|http://blog.afoolishmanifesto.com/archives/1255> to export our whole |
273 | (200M~) db to SQLite |
274 | |
275 | =head2 Multicreate |
276 | |
277 | Create an object and it's related objects all at once |
278 | |
279 | $schema->resultset('Author')->create({ |
280 | name => 'Stephen King', |
281 | books => [{ title => 'The Dark Tower' }], |
282 | address => { |
283 | street => '123 Turtle Back Lane', |
284 | state => { abbreviation => 'ME' }, |
285 | city => { name => 'Lowell' }, |
286 | }, |
287 | }); |
288 | |
289 | See L<DBIx::Class::ResultSet/create> |
290 | |
291 | =over 1 |
292 | |
293 | =item books is a has_many |
294 | |
295 | =item address is a belongs_to which in turn belongs to state and city each |
296 | |
297 | =item for this to work right state and city must mark abbreviation and name as unique |
298 | |
299 | =back |
300 | |
301 | =head2 Extensible |
302 | |
303 | DBIx::Class helped pioneer fast MI in Perl 5 with Class::C3, so it is made to |
304 | allow extensions to nearly every part of it. |
305 | |
306 | =head2 Extensibility example: DBIx::Class::Helpers |
307 | |
308 | =over 1 |
309 | |
310 | =item L<DBIx::Class::Helper::ResultSet::IgnoreWantarray> |
311 | |
312 | =item L<DBIx::Class::Helper::ResultSet::Random> |
313 | |
314 | =item L<DBIx::Class::Helper::ResultSet::SetOperations> |
315 | |
316 | =item L<DBIx::Class::Helper::Row::JoinTable> |
317 | |
318 | =item L<DBIx::Class::Helper::Row::NumifyGet> |
319 | |
320 | =item L<DBIx::Class::Helper::Row::SubClass> |
321 | |
322 | =item L<DBIx::Class::Helper::Row::ToJSON> |
323 | |
324 | =item L<DBIx::Class::Helper::Row::StorageValues> |
325 | |
326 | =item L<DBIx::Class::Helper::Row::OnColumnChange> |
327 | |
328 | =back |
329 | |
330 | =head2 Extensibility example: DBIx::Class::TimeStamp |
331 | |
332 | =over 1 |
333 | |
334 | =item See L<DBIx::Class::TimeStamp> |
335 | |
336 | =item Cross DB |
337 | |
338 | =item set_on_create |
339 | |
340 | =item set_on_update |
341 | |
342 | =back |
343 | |
344 | =head2 Extensibility example: Kioku |
345 | |
346 | =over 1 |
347 | |
348 | =item See L<DBIx::Class::Schema::KiokuDB> |
349 | |
350 | =item Kioku is the new hotness |
351 | |
352 | =item Mix RDBMS with Object DB |
353 | |
354 | =back |
355 | |
356 | =head2 Result vs ResultSet |
357 | |
358 | =over 1 |
359 | |
360 | =item Result == Row |
361 | |
362 | =item ResultSet == Query Plan |
363 | |
364 | =over 1 |
365 | |
366 | =item Internal Join Optimizer for all DB's (!!!) |
367 | |
368 | =back |
369 | |
370 | =item (less important but...) |
371 | |
372 | =item ResultSource == Queryable collection of rows (Table, View, etc) |
373 | |
374 | =item Storage == Database |
375 | |
376 | =item Schema == associates a set of ResultSources with a Storage |
377 | |
378 | =back |
379 | |
380 | =head2 ResultSet methods |
381 | |
382 | package MyApp::Schema::ResultSet::Book; |
383 | |
384 | use strict; |
385 | use warnings; |
386 | |
387 | use base 'DBIx::Class::ResultSet'; |
388 | |
389 | sub good { |
390 | my $self = shift; |
391 | $self->search({ |
392 | $self->current_source_alias . '.rating' => { '>=' => 4 } |
393 | }) |
394 | }; |
395 | |
396 | sub cheap { |
397 | my $self = shift; |
398 | $self->search({ |
399 | $self->current_source_alias . '.price' => { '<=' => 5} |
400 | }) |
401 | }; |
402 | |
403 | # ... |
404 | |
405 | 1; |
406 | |
407 | See L<DBIx::Class::Manual::Cookbook/Predefined searches> |
408 | |
409 | =over 1 |
410 | |
411 | =item All searches should be ResultSet methods |
412 | |
413 | =item Name has obvious meaning |
414 | |
415 | =item L<DBIx::Class::ResultSet/current_source_alias> helps things to work no matter what |
416 | |
417 | =back |
418 | |
419 | =head2 ResultSet method in Action |
420 | |
421 | $schema->resultset('Book')->good |
422 | |
423 | =head2 ResultSet Chaining |
424 | |
425 | $schema->resultset('Book') |
426 | ->good |
427 | ->cheap |
428 | ->recent |
429 | |
430 | =head2 search_related |
431 | |
432 | my $score = $schema->resultset('User') |
433 | ->search({'me.userid' => 'frew'}) |
434 | ->related_resultset('access') |
435 | ->related_resultset('mgmt') |
436 | ->related_resultset('orders') |
437 | ->telephone |
438 | ->search_related( shops => { |
439 | 'shops.datecompleted' => { |
440 | -between => ['2009-10-01','2009-10-08'] |
441 | } |
442 | })->completed |
443 | ->related_resultset('rpt_score') |
444 | ->search(undef, { rows => 1}) |
445 | ->get_column('raw_scores') |
446 | ->next; |
447 | |
448 | The SQL that this produces (with placeholders filled in for clarity's sake) |
449 | on our system (Microsoft SQL) is: |
450 | |
451 | SELECT raw_scores |
452 | FROM ( |
453 | SELECT raw_scores, ROW_NUMBER() OVER ( |
454 | ORDER BY ( |
455 | SELECT (1) |
456 | ) |
457 | ) AS rno__row__index |
458 | FROM ( |
459 | SELECT rpt_score.raw_scores |
460 | FROM users me |
461 | JOIN access access |
462 | ON access.userid = me.userid |
463 | JOIN mgmt mgmt |
464 | ON mgmt.mgmtid = access.mgmtid |
465 | JOIN [order] orders |
466 | ON orders.mgmtid = mgmt.mgmtid |
467 | JOIN shop shops |
468 | ON shops.orderno = orders.orderno |
469 | JOIN rpt_scores rpt_score |
470 | ON rpt_score.shopno = shops.shopno |
471 | WHERE ( |
472 | datecompleted IS NOT NULL AND |
473 | ( |
474 | (shops.datecompleted BETWEEN '2009-10-01' AND '2009-10-08') AND |
475 | (type = '1' AND me.userid = 'frew') |
476 | ) |
477 | ) |
478 | ) rpt_score |
479 | ) rpt_score |
480 | WHERE rno__row__index BETWEEN 1 AND 1 |
481 | |
482 | See: L<DBIx::Class::ResultSet/related_resultset>, L<DBIx::ClassResultSet/search_related>, and L<DBIx::Class::ResultSet/get_column>. |
483 | |
484 | =head2 bonus rel methods |
485 | |
486 | my $book = $author->create_related( |
487 | books => { |
488 | title => 'Another Discworld book', |
489 | } |
490 | ); |
491 | |
492 | my $book2 = $pratchett->add_to_books({ |
493 | title => 'MOAR Discworld book', |
494 | }); |
495 | |
496 | See L<DBIx::Class::Relationship::Base/create_related> and L<DBIx::Class::Relationship::Base/add_to_$rel> |
497 | |
f165eda8 |
498 | Note that it automatically fills in foreign key for you |
9ba33dac |
499 | |
500 | =head2 Excellent Transaction Support |
501 | |
502 | $schema->txn_do(sub { |
503 | ... |
504 | }); |
505 | |
506 | $schema->txn_begin; # <-- low level |
507 | # ... |
508 | $schema->txn_commit; |
509 | |
510 | See L<DBIx::Class::Schema/txn_do>, L<DBIx::Class::Schema/txn_begin>, |
511 | and L<DBIx::Class::Schema/txn_commit>. |
512 | |
513 | =head2 InflateColumn |
514 | |
515 | package Frew::Schema::Result::Book; |
516 | |
517 | use strict; |
518 | use warnings; |
519 | |
520 | use base 'DBIx::Class::Core'; |
521 | |
522 | use DateTime::Format::MySQL; |
523 | |
524 | # Result code here |
525 | |
526 | __PACKAGE__->load_components('InflateColumn'); |
527 | |
528 | __PACKAGE__->inflate_column( |
529 | date_published => { |
530 | inflate => sub { DateTime::Format::MySQL->parse_date( shift ) }, |
531 | deflate => sub { shift->ymd }, |
532 | }, |
533 | ); |
534 | |
535 | See L<DBIx::Class::InflateColumn>, L<DBIx::Class::InflateColumn/inflate_column>, and |
536 | L<DBIx::Class::InflateColumn::DateTime>. |
537 | |
538 | =head2 InflateColumn: deflation |
539 | |
540 | $book->date_published(DateTime->now); |
541 | $book->update; |
542 | |
543 | =head2 InflateColumn: inflation |
544 | |
545 | say $book->date_published->month_abbr; # Nov |
546 | |
547 | =head2 FilterColumn |
548 | |
549 | package Frew::Schema::Result::Book; |
550 | |
551 | use strict; |
552 | use warnings; |
553 | |
554 | use base 'DBIx::Class::Core'; |
555 | |
556 | # Result code here |
557 | |
558 | __PACKAGE__->load_components('FilterColumn'); |
559 | |
560 | __PACKAGE__->filter_column( |
561 | length => { |
562 | to_storage => 'to_metric', |
563 | from_storage => 'to_imperial', |
564 | }, |
565 | ); |
566 | |
567 | sub to_metric { $_[1] * .305 } |
568 | sub to_imperial { $_[1] * 3.28 } |
569 | |
570 | See L<DBIx::Class::FilterColumn> and L<DBIx::Class::FilterColumn/filter_column> |
571 | |
572 | =head2 ResultSetColumn |
573 | |
574 | my $rsc = $schema->resultset('Book')->get_column('price'); |
575 | $rsc->first; |
576 | $rsc->all; |
577 | $rsc->min; |
578 | $rsc->max; |
579 | $rsc->sum; |
580 | |
581 | See L<DBIx::Class::ResultSetColumn> |
582 | |
583 | =head2 Aggregates |
584 | |
585 | my @res = $rs->search(undef, { |
586 | select => [ |
587 | 'price', |
588 | 'genre', |
589 | { max => price }, |
590 | { avg => price }, |
591 | ], |
592 | as => [ |
593 | qw(price genre max_price avg_price) |
594 | ], |
595 | group_by => [qw(price genre)], |
596 | }); |
597 | for (@res) { |
598 | say $_->price . ' ' . $_->genre; |
599 | say $_->get_column('max_price'); |
a9e8284f |
600 | say $_->get_column('avg_price'); |
9ba33dac |
601 | } |
602 | |
603 | See L<DBIx::Class::ResultSet/select>, L<DBIx::Class::ResultSet/as>, and |
604 | L<DBIx::Class::ResultSet/group_by> |
605 | |
606 | =over 1 |
607 | |
608 | =item Careful, get_column can basicaly mean B<three> things |
609 | |
610 | =item private in which case you should use an accessor |
611 | |
612 | =item public for what there is no accessor for |
613 | |
614 | =item public for get resultset column (prev example) |
615 | |
616 | =back |
617 | |
618 | =head2 HRI |
619 | |
620 | $rs->search(undef, { |
621 | result_class => 'DBIx::Class::ResultClass::HashRefInflator', |
622 | }); |
623 | |
624 | See L<DBIx::Class::ResultSet/result_class> and L<DBIx::Class::ResultClass::HashRefInflator>. |
625 | |
626 | =over 1 |
627 | |
628 | =item Easy on memory |
629 | |
630 | =item Mega fast |
631 | |
632 | =item Great for quick debugging |
633 | |
634 | =item Great for performance tuning (we went from 2m to < 3s) |
635 | |
636 | =back |
637 | |
638 | =head2 Subquery Support |
639 | |
640 | my $inner_query = $schema->resultset('Artist') |
641 | ->search({ |
642 | name => [ 'Billy Joel', 'Brittany Spears' ], |
643 | })->get_column('id')->as_query; |
644 | |
645 | my $rs = $schema->resultset('CD')->search({ |
646 | artist_id => { -in => $inner_query }, |
647 | }); |
648 | |
f165eda8 |
649 | See L<DBIx::Class::Manual::Cookbook/Subqueries> |
9ba33dac |
650 | |
651 | =head2 Bare SQL w/ Placeholders |
652 | |
653 | $rs->update({ |
654 | # !!! SQL INJECTION VECTOR |
655 | price => \"price + $inc", # DON'T DO THIS |
656 | }); |
657 | |
658 | Better: |
659 | |
660 | $rs->update({ |
661 | price => \['price + ?', [inc => $inc]], |
662 | }); |
663 | |
f165eda8 |
664 | See L<SQL::Abstract/Literal_SQL_with_placeholders_and_bind_values_(subqueries)> |
9ba33dac |
665 | |