MongoDBx-Class-1.02000755001750001750 012070412022 12653 5ustar00idoido000000000000README100644001750001750 3057512070412022 13646 0ustar00idoido000000000000MongoDBx-Class-1.02NAME MongoDBx::Class - Flexible ORM for MongoDB databases VERSION version 1.02 SYNOPSIS Normal usage: use MongoDBx::Class; # create a new instance of the module and load a model schema my $dbx = MongoDBx::Class->new(namespace => 'MyApp::Model::DB'); # if MongoDBx::Class can't find your model schema (possibly because # it exists in some different location), you can do this: my $dbx = MongoDBx::Class->new(namespace => 'MyApp::Model::DB', search_dirs => ['/path/to/model/dir']); # connect to a MongoDB server my $conn = $dbx->connect(host => 'localhost', port => 27017); # be safe by default $conn->safe(1); # we could've also just passed "safe => 1" to $dbx->connect() above # get a MongoDB database my $db = $conn->get_database('people'); # insert a person my $person = $db->insert({ name => 'Some Guy', birth_date => '1984-06-12', _class => 'Person' }); print "Created person ".$person->name." (".$person->id.")\n"; $person->update({ name => 'Some Smart Guy' }); $person->delete; See MongoDBx::Class::ConnectionPool for simple connection pool usage. DESCRIPTION MongoDBx::Class is a flexible object relational mapper (ORM) for MongoDB databases. Given a schema-like collection of document classes, MongoDBx::Class expands MongoDB objects (hash-refs in Perl) from the database into objects of those document classes, and collapses such objects back to the database. MongoDBx::Class takes advantage of the fact that Perl's MongoDB driver is Moose-based to extend and tweak the driver's behavior, instead of wrapping it. This means MongoDBx::Class does not define its own syntax, so you simply use it exactly as you would the MongoDB driver directly. That said, MongoDBx::Class adds some sugar that enhances and simplifies the syntax unobtrusively (either use it or don't). Thus, it is relatively easy to convert your current MongoDB applications to MongoDBx::Class. A collection in MongoDBx::Class "isa('MongoDB::Collection')", a database in MongoDBx::Class "isa('MongoDB::Database')", etc. As opposed to other ORMs (even non-MongoDB ones), MongoDBx::Class attempts to stay as close as possible to MongoDB's non-schematic nature. While most ORMs enforce using a single collection (or table in the SQL world) for every object class, MongoDBx::Class allows you to store documents of different classes in different collections (and even databases). A collection can hold documents of many different classes. Not only that, as MongoDBx::Class is Moose based, you can easily create very flexible schemas by using concepts such as inheritance and roles. For example, say you have a collection called 'people' with documents representing, well, people, but these people can either be teachers or students. Also, students may assume the role "hall monitor". With MongoDBx::Class, you can create a common base class, say "People", and two more classes that extend it - "Teacher" and "Student" with attributes that are only relevant to each one. You also create a role called "HallMonitor", possibly with some methods of its own. You can save all these "people documents" into a single MongoDB collection, and when fetching documents from that collection, they will be properly expanded to their correct classes (though you will have to apply roles yourself - at least for now). COMPARISON WITH OTHER MongoDB ORMs As MongoDB is rather young, there aren't many options out there, though CPAN has some pretty good ones, and will probably have more as MongoDB popularity rises. The first MongoDB ORM in CPAN was Mongoose, and while it's a very good ORM, MongoDBx::Class was mainly written to overcome some limitations of Mongoose. The biggest of these limitations is that in order to provide a more comfortable syntax than MongoDB's native syntax, Mongoose makes the unfortunate decision of being implemented as a singleton, meaning only one instance of a Mongoose-based schema can be used in an application. That essentially kills multithreaded applications. Say you have a Plack-based (doesn't have to be Plack-based though) web application deployed via Starman (or any other web server for that matter), which is a pre-forking web server - you're pretty much doomed. As MongoDB's driver states, it doesn't support connection pooling, so every fork has to have its own connection to the MongoDB server. Mongoose being a singleton means your threads will not have a connection to the server, and you're screwed. MongoDBx::Class does not suffer this limitation. You can start as many connections as you like. If you're running in a pre-forking environment, you don't have to worry about it at all. Other differences from Mongoose include: * Mongoose creates its own syntax, MongoDBx::Class doesn't, you use MongoDB's syntax directly. * A document class in Mongoose is connected to a single collection only, and a collection can only have documents of that class. MongoDBx::Class doesn't have that limitation. Do what you like. * Mongoose has limited support for multiple database usage. With MongoDBx::Class, you can use as many databases as you want. * MongoDBx::Class is way faster. While I haven't performed any real benchmarks, an application converted from Mongoose to MongoDBx::Class showed an increase of speed in orders of magnitude. * In Mongoose, your document class attributes are expected to be read-write (i.e. "is => 'rw'" in Moose), otherwise expansion will fail. This is not the case with MongoDBx::Class, your attributes can safely be read-only. Another ORM for MongoDB is Mongrel, which doesn't use Moose and is thus lighter (though as MongoDB is already Moose-based, I see no benefit here). It uses Oogly for data validation (while Moose has its own type validation), and seems to define its own syntax as well. Unfortunately, documentation is currently lacking, and I haven't given it a try, so I can't draw specific comparisons here. Even before Mongoose was born, you could use MongoDB as a backend for KiokuDB, by using KiokuDB::Backend::MongoDB. However, KiokuDB is considered a database of its own and uses some conventions which doesn't fit well with MongoDB. Mongoose::Intro already gives a pretty convincing case when and why you should or shouldn't want to use KiokuDB. CONNECTION POOLING Since version 0.9, "MongoDBx::Class" provides experimental, simple connection pooling for applications. Take a look at MongoDBx::Class::ConnectionPool for more information. CAVEATS AND THINGS TO CONSIDER There are a few caveats and important facts to take note of when using MongoDBx::Class as of today: * MongoDBx::Class's flexibility is dependant on its ability to recognize which class a document in a MongoDB collection expands to. Currently, MongoDBx::Class requires every document to have an attribute called "_class" that contains the name of the document class to use. This isn't very comfortable, but works. I'm still thinking of ways to expand documents without this. This pretty much means that you will have to perform some preparations to use existing MongoDB database with MongoDBx::Class - you will have to update every document in the database with the "_class" attribute. * References (representing joins) are expected to be in the DBRef format, as defined in . If your database references aren't in this format, you'll have to convert them first. * The '_id' attribute of all your documents has to be an internally generated MongoDB::OID. This limitation may or may not be lifted in the future. TUTORIAL To start using MongoDBx::Class, please read MongoDBx::Class::Tutorial. It also contains a list of frequently asked questions. ATTRIBUTES namespace A string representing the namespace of the MongoDB schema used (e.g. "MyApp::Schema"). Your document classes, structurally speaking, should be descendants of this namespace (e.g. "MyApp::Schema::Article", "MyApp::Schema::Post"). search_dirs An array-ref of directories in which to search for the document classes. Not required, useful if for some reason MongoDBx::Class can't find your document classes. doc_classes A hash-ref of document classes found when loading the schema. CLASS METHODS new( namespace => $namespace ) Creates a new instance of this module. Requires the namespace of the database schema to use. The schema will be immediately loaded, but no connection to a MongoDB server is made yet. OBJECT METHODS connect( %options ) Initiates a new connection to a MongoDB server running on a certain host and listening to a certain port. %options is the hash of attributes that can be passed to "new()" in MongoDB::Connection, plus the 'safe' attribute from MongoDBx::Class::Connection. You're mostly expected to provide the 'host' and 'port' options. If a host is not provided, 'localhost' is used. If a port is not provided, 27017 (MongoDB's default port) is used. Returns a MongoDBx::Class::Connection object. NOTE: Since version 0.7, the created connection object isn't saved in the top MongoDBx::Class object, but only returned, in order to be more like how connection is made in MongoDB (and to allow multiple connections). This change breaks backwords compatibility. pool( [ type => $type, max_conns => $max_conns, params => \%params, ... ] ) Creates a new connection pool (see MongoDBx::Class::ConnectionPool for more info) and returns it. "type" is either 'rotated' or 'backup' (the default). "params" is a hash-ref of parameters that can be passed to "MongoDB::Connection->new()" when creating connections in the pool. See "ATTRIBUTES" in MongoDBx::Class::ConnectionPool for a complete list of attributes that can be passed. INTERNAL METHODS The following methods are only to be used internally. BUILD() Automatically called when creating a new instance of this module. This loads the schema and saves a hash-ref of document classes found in the object. Automatic loading courtesy of Module::Pluggable. TODO * Improve the tests. * Make the "isa" option in MongoDBx::Class::Moose's relationship types consistent. Either use the full package names or the short class names. * Try to find a way to not require documents to have the _class attribute. AUTHOR Ido Perlmuter, "" BUGS Please report any bugs or feature requests to "bug-mongodbx-class at rt.cpan.org", or through the web interface at . I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class You can also look for information at: * RT: CPAN's request tracker * AnnoCPAN: Annotated CPAN documentation * CPAN Ratings * Search CPAN SEE ALSO MongoDB, Mongoose, Mongrel, KiokuDB::Backend::MongoDB. ACKNOWLEDGEMENTS * Rodrigo de Oliveira, author of Mongoose, whose code greatly assisted me in writing MongoDBx::Class. * Thomas Müller, for adding support for the Transient trait. * Dan Dascalescu, for fixing typos and other problems in the documentation. LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. Changes100644001750001750 1140612070412022 14251 0ustar00idoido000000000000MongoDBx-Class-1.02Revision history for MongoDBx::Class 1.02 2013-01-01 00:38:58 Asia/Jerusalem - Fixed previous failed attempt at adapting MongoDBx::Class to the new MongoDB version 1.01 2012-12-11 22:02:29 Asia/Jerusalem - Updated to extend the correct MongoDB connection class depending on the installed version of the MongoDB driver (starting at MongoDB v0.503.1 MongoDB::Connection has been deprecated in favor of MongoDB::MongoClient) 1.00 2012-09-06 21:20:00 Asia/Jerusalem - Added a new Transient attribute trait, for document attributes that do not get saved in the database (thanks Thomas Müller) - Expanded documentation on the joins_many relationship - Small documentation fixes (thanks Dan Dascalescu) - Module no longer considered beta 0.91 2011-07-16 20:21:43 Asia/Jerusalem - Small documentation fixes 0.9 2011-07-16 17:10:26 Asia/Jerusalem [ New features ] - Created a very-simple (and experimental) connection pool under the new MongoDBx::Class::ConnectionPool role class, with two implementations called Backup Pool and Rotated Pool. See the documentation of the MongoDBx::Class::pool() method for more information. - Changed MongoDBx::Class::Cursor->next() such that it takes an option to return the document as is (i.e. as a hash-ref) without expanding it (will be used internally by MongoDBx::Class at a later version). [ Bug fixes ] - MongoDBx::Class::Document::update(), when called with an update hash-ref, will return the output of MongoDB::Collection->update() as expected (did not do that before causing a false value to be returned even if update succeeded). 0.8 2011-05-09 19:54:24 Asia/Jerusalem - Added the option to tell MongoDBx::Class where to search for document classes (see the document_dirs attribute) - Added a new question/answer to the FAQ section of the tutorial explaining when and why MongoDBx::Class might fail to expand documents to their respective classes - Fixed bug causing MongoDBx::Class to fail when a document class has attributes that hold blessed values of classes that do not use Moose. - 01-simple.t now drops the test database before and after running the test. - renamed the test schema to prevent clashing with other namespaces 0.7 2011-03-17 00:48:50 Asia/Jerusalem - Changed the way database connection is made to be more like in the original MongoDB driver (there was no reason to do it differently, this change breaks backwords compatibility though) - Added the defines_many relationship type that helps embedding documents within a hash reference. - Added the as_hashref() method to embedded documents. - Removed the -T option from the shebang line in 01-simple.t as it seems like it causes the whole module finding problems (though I could be wrong) - Status updated from alpha to beta 0.6 2011-02-01 23:28:29 Asia/Jerusalem - MongoDBx::Class's connect() method now takes all the parameters that MongoDB::Collection's new() method takes, so we can use authentication and other MongoDB options. We can also define a safe connection with the 'safe' option. 0.5 2011-02-01 21:10:25 Asia/Jerusalem - DateTime inflation/deflation in MongoDBx::Class::ParsedAttribute::DateTime is now evaled to prevent dies - The argumented version of the update() method in MongoDBx::Class::Moose now also updates the document object with the new changes - Updated test to reflect above change - Fixed bug causing 'null' values being saved in the database when using the $doc->update() method. 0.4 2011-01-11 22:25:54 Asia/Jerusalem - Added the _attributes method to document objects (standalone/embedded) - Added the ParsedAttribute role and the ParsedAttribute::DateTime class for document attributes that are automatically expanded and collapsed by MongoDBx::Class - Added the Parsed attribute trait to allow setting document attributes that are parsed by ParsedAttribute classes - Fixed some POD mistakes in the docs - Improved the documentation a bit - insert and batch_insert shouldn't enforce only inserting hash-refs - Fixed bug in MongoDBx::Class::Collection->update where the wrong function argument was collapsed - Fixed bug in MongoDBx::Class::Collection->find_one where searching by string OID didn't work - Updated test suite with new features 0.3 2010-12-26 12:15:03 Asia/Jerusalem - Fixed typos, errors and mistakes in the distribution's documentation - Added another item to the CAVEATS AND THINGS TO CONSIDER section of MongoDBx/Class.pm - Temporarily added a bypass for the test problem until I can figure out what's the problem. 0.2 2010-12-25 23:13:25 Asia/Jerusalem - Fixed bug preventing tests from loading the test schema 0.1 2010-12-25 20:40:15 Asia/Jerusalem - Initial release LICENSE100644001750001750 4365612070412022 13777 0ustar00idoido000000000000MongoDBx-Class-1.02This software is copyright (c) 2013 by Ido Perlmuter. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2013 by Ido Perlmuter. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2013 by Ido Perlmuter. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End INSTALL100644001750001750 171012070412022 13764 0ustar00idoido000000000000MongoDBx-Class-1.02 This is the Perl distribution MongoDBx-Class. Installing MongoDBx-Class is straightforward. ## Installation with cpanm If you have cpanm, you only need one line: % cpanm MongoDBx::Class If you are installing into a system-wide directory, you may need to pass the "-S" flag to cpanm, which uses sudo to install the module: % cpanm -S MongoDBx::Class ## Installing with the CPAN shell Alternatively, if your CPAN shell is set up, you should just be able to do: % cpan MongoDBx::Class ## Manual installation As a last resort, you can manually install it. Download the tarball, untar it, then build it: % perl Makefile.PL % make && make test Then install it: % make install If you are installing into a system-wide directory, you may need to run: % sudo make install ## Documentation MongoDBx-Class documentation is available as POD. You can run perldoc from a shell to read the documentation: % perldoc MongoDBx::Class dist.ini100644001750001750 20012070412022 14350 0ustar00idoido000000000000MongoDBx-Class-1.02name = MongoDBx-Class author = Ido Perlmuter license = Perl_5 copyright_holder = Ido Perlmuter [@IDOPEREL] META.yml100644001750001750 173612070412022 14214 0ustar00idoido000000000000MongoDBx-Class-1.02--- abstract: 'Flexible ORM for MongoDB databases' author: - 'Ido Perlmuter ' build_requires: Data::Dumper: 0 DateTime: 0 Test::More: 0 Time::HiRes: 0 strict: 0 warnings: 0 configure_requires: ExtUtils::MakeMaker: 6.30 dynamic_config: 0 generated_by: 'Dist::Zilla version 4.300028, CPAN::Meta::Converter version 2.120921' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: MongoDBx-Class requires: Carp: 0 DateTime::Format::W3CDTF: 0 Module::Load: 0 Module::Pluggable: 0 MongoDB: 0.40 MongoDB::Collection: 0 MongoDB::Connection: 0 MongoDB::Cursor: 0 MongoDB::Database: 0 MongoDB::MongoClient: 0 Moose: 0 Moose::Exporter: 0 Moose::Role: 0 Moose::Util::TypeConstraints: 0 Try::Tiny: 0 namespace::autoclean: 0 perl: 5.006 version: 0 resources: homepage: https://github.com/ido50/MongoDBx-Class repository: https://github.com/ido50/MongoDBx-Class.git version: 1.02 MANIFEST100644001750001750 167512070412022 14076 0ustar00idoido000000000000MongoDBx-Class-1.02Changes INSTALL LICENSE MANIFEST MANIFEST.SKIP META.json META.yml Makefile.PL README SIGNATURE dist.ini lib/MongoDBx/Class.pm lib/MongoDBx/Class/Collection.pm lib/MongoDBx/Class/Connection.pm lib/MongoDBx/Class/ConnectionPool.pm lib/MongoDBx/Class/ConnectionPool/Backup.pm lib/MongoDBx/Class/ConnectionPool/Rotated.pm lib/MongoDBx/Class/Cursor.pm lib/MongoDBx/Class/Database.pm lib/MongoDBx/Class/Document.pm lib/MongoDBx/Class/EmbeddedDocument.pm lib/MongoDBx/Class/Meta/AttributeTraits.pm lib/MongoDBx/Class/Moose.pm lib/MongoDBx/Class/ParsedAttribute.pm lib/MongoDBx/Class/ParsedAttribute/DateTime.pm lib/MongoDBx/Class/Reference.pm lib/MongoDBx/Class/Tutorial.pod t/00-load.t t/01-simple.t t/02-pool.t t/lib/MongoDBxTestSchema/Novel.pm t/lib/MongoDBxTestSchema/PersonName.pm t/lib/MongoDBxTestSchema/Review.pm t/lib/MongoDBxTestSchema/Synopsis.pm t/lib/MongoDBxTestSchema/Tag.pm t/release-dist-manifest.t t/release-pod-coverage.t t/release-pod-syntax.t META.json100644001750001750 374312070412022 14364 0ustar00idoido000000000000MongoDBx-Class-1.02{ "abstract" : "Flexible ORM for MongoDB databases", "author" : [ "Ido Perlmuter " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 4.300028, CPAN::Meta::Converter version 2.120921", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "MongoDBx-Class", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.30" } }, "develop" : { "requires" : { "Pod::Coverage::TrustPod" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08" } }, "runtime" : { "requires" : { "Carp" : "0", "DateTime::Format::W3CDTF" : "0", "Module::Load" : "0", "Module::Pluggable" : "0", "MongoDB" : "0.40", "MongoDB::Collection" : "0", "MongoDB::Connection" : "0", "MongoDB::Cursor" : "0", "MongoDB::Database" : "0", "MongoDB::MongoClient" : "0", "Moose" : "0", "Moose::Exporter" : "0", "Moose::Role" : "0", "Moose::Util::TypeConstraints" : "0", "Try::Tiny" : "0", "namespace::autoclean" : "0", "perl" : "5.006", "version" : "0" } }, "test" : { "requires" : { "Data::Dumper" : "0", "DateTime" : "0", "Test::More" : "0", "Time::HiRes" : "0", "strict" : "0", "warnings" : "0" } } }, "release_status" : "stable", "resources" : { "homepage" : "https://github.com/ido50/MongoDBx-Class", "repository" : { "type" : "git", "url" : "https://github.com/ido50/MongoDBx-Class.git", "web" : "https://github.com/ido50/MongoDBx-Class" } }, "version" : "1.02" } SIGNATURE100644001750001750 652712070412022 14232 0ustar00idoido000000000000MongoDBx-Class-1.02This file contains message digests of all files listed in MANIFEST, signed via the Module::Signature module, version 0.69. To verify the content in this distribution, first make sure you have Module::Signature installed, then type: % cpansign -v It will check each file's integrity, as well as the signature's validity. If "==> Signature verified OK! <==" is not displayed, the distribution may already have been compromised, and you should not run its Makefile.PL or Build.PL. -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 SHA1 aebd8c878873c9e53541f220440894b9a3cbb9a5 Changes SHA1 c1ecbd37c4014c98a8844b2d4ee560392833398d INSTALL SHA1 4dceae0eaef7a9ac83dea9d378c954bfce55223a LICENSE SHA1 ff663b55ed9bc68bc8676e978582449936b8e420 MANIFEST SHA1 20c1431f6ffa70f555428944c8583a2b59ad5676 MANIFEST.SKIP SHA1 addff859dd2a619cd61ab709c0d07b79c32d9834 META.json SHA1 582a992fffebe685cc7f7bce5dae860e01bdfaeb META.yml SHA1 985ebb1d390d11a6bad989e073ca827d986cb7e9 Makefile.PL SHA1 81567244d9d4800444f1acea18d45671af30eb82 README SHA1 ac705ba18654c6db427017a3321e117d2d03fddd dist.ini SHA1 c5633bc1ec2a3e48523474795b9b0e48029d8dc1 lib/MongoDBx/Class.pm SHA1 643843dfd0f12d633c2f99eb401f32db5965b3d7 lib/MongoDBx/Class/Collection.pm SHA1 9909847fffadd65ab450edc984097e408849d59a lib/MongoDBx/Class/Connection.pm SHA1 ba14ec2e78d2b097d360778c65da3c5f63a5ee8a lib/MongoDBx/Class/ConnectionPool.pm SHA1 aba5aa42982de0378d7ed8f1535364bdca8e064a lib/MongoDBx/Class/ConnectionPool/Backup.pm SHA1 442404e70bf1512b07f94c9ee24c7afd720fca1f lib/MongoDBx/Class/ConnectionPool/Rotated.pm SHA1 01175806f63d65a014c88e3f82c787b27d0e0512 lib/MongoDBx/Class/Cursor.pm SHA1 9d8790a62084e219b765ee87bb1fa1db3ef562aa lib/MongoDBx/Class/Database.pm SHA1 f0c49c138cded2f866b97ea5dc9e76ea82b03ea0 lib/MongoDBx/Class/Document.pm SHA1 6ea891b39d3d003ed04ae42316d202a8ea2a89ef lib/MongoDBx/Class/EmbeddedDocument.pm SHA1 2a59e18031f1f51fc2e7488d36311c928b566ac1 lib/MongoDBx/Class/Meta/AttributeTraits.pm SHA1 a036dac8bcf89681d058b3210368dd9fa2b5f364 lib/MongoDBx/Class/Moose.pm SHA1 9a30f5112ab4d1601b41b861e07aba666de5217e lib/MongoDBx/Class/ParsedAttribute.pm SHA1 955c4b996253fc9b08a43cbc8979ba2d9fe842b9 lib/MongoDBx/Class/ParsedAttribute/DateTime.pm SHA1 3e7c04c9d94c35dc08894fd76b78a6b00277f1fb lib/MongoDBx/Class/Reference.pm SHA1 876e06c810edb0682d343e0661b5fd73c3ca1f60 lib/MongoDBx/Class/Tutorial.pod SHA1 6075d4e8f18b5d8bcb2c59c727d8e73df3df6ad3 t/00-load.t SHA1 ab8ec5a966706d8f8914916583149e8d3f571b73 t/01-simple.t SHA1 ccf4163cc4d1b0c058937ec49b5e52185727e3fa t/02-pool.t SHA1 e397b5d8d6150e22a930c39d4262c5b8774dbd68 t/lib/MongoDBxTestSchema/Novel.pm SHA1 39f1e4936c93de752199553bfcafc4ad06d7ceb3 t/lib/MongoDBxTestSchema/PersonName.pm SHA1 adf23897541638409d88c4e4ae568b19fb19de74 t/lib/MongoDBxTestSchema/Review.pm SHA1 44f1399c9610235780ad1e9b0836f3db2f83bbad t/lib/MongoDBxTestSchema/Synopsis.pm SHA1 f9415b067d58615f9c97ebc20f0c9a22c337a367 t/lib/MongoDBxTestSchema/Tag.pm SHA1 87768d177f8c2e2df9c591cde869e7c8a245555e t/release-dist-manifest.t SHA1 9433c240fe590bc404ab68ff63984df763e347ed t/release-pod-coverage.t SHA1 b30cbdfaf935017c4568c0c91b242438cb87786e t/release-pod-syntax.t -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.19 (GNU/Linux) iF4EAREIAAYFAlDiFBIACgkQa4OHQAFM10efSAD/fyImGTQSkGvra/oNyjKA+FQJ SIOXLaOW+51RU/LgejwBAKY5q8a2s6pjOICTybuqJanbyxkxB4G4XqViEPvK0CfU =lR2g -----END PGP SIGNATURE----- t000755001750001750 012070412022 13037 5ustar00idoido000000000000MongoDBx-Class-1.0200-load.t100644001750001750 26212070412022 14500 0ustar00idoido000000000000MongoDBx-Class-1.02/t#!perl -T use Test::More tests => 1; BEGIN { use_ok( 'MongoDBx::Class' ) || print "Bail out!\n"; } diag( "Testing MongoDBx::Class $MongoDBx::Class::VERSION, Perl $], $^X" ); 02-pool.t100644001750001750 355612070412022 14565 0ustar00idoido000000000000MongoDBx-Class-1.02/t#!/perl use lib 't/lib'; use strict; use warnings; use Test::More; use MongoDBx::Class; use Time::HiRes qw/time/; use Data::Dumper; my $dbx = MongoDBx::Class->new(namespace => 'MongoDBxTestSchema'); # temporary bypass, should be removed when I figure out why tests can't find the schema if (scalar(keys %{$dbx->doc_classes}) != 5) { plan skip_all => "Temporary skip due to schema not being found"; } else { plan tests => 10; } SKIP: { is(scalar(keys %{$dbx->doc_classes}), 5, 'successfully loaded schema'); SKIP: { # make sure we can connect to MongoDB on localhost and # discard the connection my $conn; eval { $conn = $dbx->connect }; skip "Can't connect to MongoDB server", 9 if $@; #------------------------------------------------------- # BACKUP POOL #------------------------------------------------------- my $pool = $dbx->pool(max_conns => 3, type => 'backup'); my @conns = map { $pool->get_conn } (1 .. 3); ok(!$conns[0]->is_backup, 'conn 1 of pool is not backup'); ok(!$conns[1]->is_backup, 'conn 2 of pool is not backup'); ok(!$conns[2]->is_backup, 'conn 3 of pool is not backup'); $conn = $pool->get_conn; ok($conn && $conn->is_backup, 'when all conns are used the backup is returned'); ok(!$pool->return_conn($conn), 'pool does not return backup conn'); #------------------------------------------------------- # ROTATED POOL #------------------------------------------------------- $pool = $dbx->pool(max_conns => 2, type => 'rotated'); @conns = map { $pool->get_conn } (1 .. 2); is($pool->num_used, 2, 'created two connections'); $conn = $pool->get_conn; ok($conn && $pool->num_used == 1, 'rotated to pool start'); $conn = $pool->get_conn; ok($conn && $pool->num_used == 2, 'once again at end of pool'); $conn = $pool->get_conn; ok($conn && $pool->num_used == 1, 'once again at the beginning'); } } done_testing(); Makefile.PL100644001750001750 306212070412022 14707 0ustar00idoido000000000000MongoDBx-Class-1.02 use strict; use warnings; use 5.006; use ExtUtils::MakeMaker 6.30; my %WriteMakefileArgs = ( "ABSTRACT" => "Flexible ORM for MongoDB databases", "AUTHOR" => "Ido Perlmuter ", "BUILD_REQUIRES" => { "Data::Dumper" => 0, "DateTime" => 0, "Test::More" => 0, "Time::HiRes" => 0, "strict" => 0, "warnings" => 0 }, "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => "6.30" }, "DISTNAME" => "MongoDBx-Class", "EXE_FILES" => [], "LICENSE" => "perl", "NAME" => "MongoDBx::Class", "PREREQ_PM" => { "Carp" => 0, "DateTime::Format::W3CDTF" => 0, "Module::Load" => 0, "Module::Pluggable" => 0, "MongoDB" => "0.40", "MongoDB::Collection" => 0, "MongoDB::Connection" => 0, "MongoDB::Cursor" => 0, "MongoDB::Database" => 0, "MongoDB::MongoClient" => 0, "Moose" => 0, "Moose::Exporter" => 0, "Moose::Role" => 0, "Moose::Util::TypeConstraints" => 0, "Try::Tiny" => 0, "namespace::autoclean" => 0, "version" => 0 }, "VERSION" => "1.02", "test" => { "TESTS" => "t/*.t" } ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.56) } ) { my $br = delete $WriteMakefileArgs{BUILD_REQUIRES}; my $pp = $WriteMakefileArgs{PREREQ_PM}; for my $mod ( keys %$br ) { if ( exists $pp->{$mod} ) { $pp->{$mod} = $br->{$mod} if $br->{$mod} > $pp->{$mod}; } else { $pp->{$mod} = $br->{$mod}; } } } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); MANIFEST.SKIP100644001750001750 35412070412022 14614 0ustar00idoido000000000000MongoDBx-Class-1.02^\.gitignore$ ^blib/.*$ ^inc/.*$ ^Makefile$ ^Makefile\.old$ ^pm_to_blib$ ^Build$ ^Build\.bat$ ^_build\.*$ ^pm_to_blib.+$ ^.+\.tar\.gz$ ^\.lwpcookies$ ^cover_db$ ^pod2htm.*\.tmp$ ^MongoDBx-Class-.*$ ^\.build.+$ ^MYMETA\.(yml|yaml|json)$ 01-simple.t100644001750001750 1404612070412022 15120 0ustar00idoido000000000000MongoDBx-Class-1.02/t#!/perl use lib 't/lib'; use strict; use warnings; use Test::More; use MongoDBx::Class; use DateTime; my $dbx = MongoDBx::Class->new(namespace => 'MongoDBxTestSchema'); # temporary bypass, should be removed when I figure out why tests can't find the schema if (scalar(keys %{$dbx->doc_classes}) != 5) { plan skip_all => "Temporary skip due to schema not being found"; } else { plan tests => 27; } SKIP: { is(scalar(keys %{$dbx->doc_classes}), 5, 'successfully loaded schema'); SKIP: { my $conn; eval { $conn = $dbx->connect }; skip "Can't connect to MongoDB server", 26 if $@; $conn->safe(1); is($conn->safe, 1, "Using safe operations by default"); my $db = $conn->get_database('mongodbx_class_test'); $db->drop; my $novels_coll = $db->get_collection('novels'); $novels_coll->ensure_index([ title => 1, year => -1 ]); my $novel = $novels_coll->insert({ _class => 'Novel', title => 'The Valley of Fear', year => 1914, author => { first_name => 'Arthur', middle_name => 'Conan', last_name => 'Doyle', }, added => DateTime->now, tags => [ { category => 'mystery', subcategory => 'thriller' }, { category => 'mystery', subcategory => 'detective' }, { category => 'crime', subcategory => 'fiction' }, ], }); is(ref($novel->_id), 'MongoDB::OID', 'document successfully inserted'); is(ref($novel->added), 'DateTime', 'added attribute successfully parsed'); is($novel->author->name, 'Arthur Conan Doyle', 'embedded document works'); my $synopsis = $db->get_collection('synopsis')->insert({ _class => 'Synopsis', novel => $novel, text => "The Valley of Fear is the final Sherlock Holmes novel by Sir Arthur Conan Doyle. The story was first published in the Strand Magazine between September 1914 and May 1915. The first book edition was published in New York on 27 February 1915.", }); is(ref($synopsis->_id), 'MongoDB::OID', 'successfully created a synopsis'); is($synopsis->novel->_id, $novel->_id, 'reference from synopsis to novel correct'); my @reviews = $db->get_collection('reviews')->batch_insert([ { _class => 'Review', novel => $novel, reviewer => 'Some Guy', text => 'I really liked it!', score => 5, }, { _class => 'Review', novel => $novel, reviewer => 'Some Other Guy', text => 'It was okay.', score => 3, }, { _class => 'Review', novel => $novel, reviewer => 'Totally Different Guy', text => 'Man, that just sucked!', score => 1, } ]); is(scalar(@reviews), 3, 'successfully created three reviews'); my ($total_score, $avg_score) = (0, 0); foreach (@reviews) { $total_score += $_->score || 0; } $avg_score = $total_score / scalar(@reviews); is($avg_score, 3, 'average score correct'); $novel->update({ year => 1915, 'author.middle_name' => 'Xoxa' }); is($novel->year, 1915, "novel's year successfully changed"); is($novel->author->middle_name, 'Xoxa', "author's middle name successfully changed"); is_deeply([$novel->_attributes], [qw/_id added author related_novels review_count tags title year/], '_attributes okay'); is_deeply([$novel->author->_attributes], [qw/first_name last_name middle_name/], 'embedded _attributes okay'); my $found_novel = $db->get_collection('novels')->find_one($novel->id); is($found_novel->reviews->count, 3, 'joins_many works correctly'); $found_novel->set_year(1914); $found_novel->author->set_middle_name('Conan'); $found_novel->update(); is($db->get_collection('novels')->find_one($found_novel->_id)->year, 1914, "novel's year successfully changed back"); is($db->get_collection('novels')->find_one({ _id => MongoDB::OID->new(value => $found_novel->oid) })->author->middle_name, 'Conan', "author's middle name successfully changed back"); is($found_novel->added->year, DateTime->now->year, 'DateTime objects correctly parsed by MongoDBx::Class::ParsedAttribute::DateTime'); $synopsis->delete; my $syns = $db->get_collection('synopsis')->find({ 'novel.$id' => $novel->_id }); is($syns->count, 0, 'Synopsis successfully removed'); $db->get_collection('reviews')->update({ 'novel.$id' => $novel->_id }, { '$set' => { reviewer => 'John John' }, '$inc' => { score => 3 } }, { multiple => 1 }); my @scores; my $john_john = 1; foreach ($novel->reviews->sort([ score => -1 ])->all) { undef $john_john if $_->reviewer ne 'John John'; push(@scores, $_->score); } is($john_john, 1, "Successfully replaced reviewer for all reviews"); is_deeply(\@scores, [8, 6, 4], "Successfully increased all scores by three"); # Test transient Attributes is($novel->review_count, 3, 'Correct number of reviews found'); $novel->review_count(4); is($novel->review_count, 4, 'Set number of reviews is correct'); $novel->update; # read novel from db again, without expanding it, to # verify the review_count attribute was not saved my $novel_fetched = $db->get_collection('novels')->find({ _id => $novel->_id })->next(1); ok(!exists $novel_fetched->{review_count}, 'Transient value was not saved in DB'); $novel = $db->get_collection('novels')->find_one($novel->id); # refresh from DB $novel->update({ review_count => 4 }); is($novel->review_count, 3, 'Transient value not updated'); # really the correct behaviour? # refresh again $novel_fetched = $db->get_collection('novels')->find({ _id => $novel->_id })->next(1); ok(!exists $novel_fetched->{review_count}, 'Transient value was not saved in DB'); my $novel2 = $novels_coll->insert({ _class => 'Novel', title => 'Modern Perl', year => 2010, author => { first_name => '', middle_name => 'chromatic', last_name => '', }, added => DateTime->now, tags => [ { category => 'programming', subcategory => 'perl' }, ], review_count => 2, }); # refresh novel 2 without expanding my $novel2_fetched = $db->get_collection('novels')->find({ _id => $novel2->_id })->next(1); ok(!exists $novel2_fetched->{review_count}, 'Transient value was not saved in DB'); is($novel2->review_count, 0, 'Transient value is not inflated'); $db->drop; } } done_testing(); MongoDBx000755001750001750 012070412022 15017 5ustar00idoido000000000000MongoDBx-Class-1.02/libClass.pm100644001750001750 3477112070412022 16616 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBxpackage MongoDBx::Class; # ABSTRACT: Flexible ORM for MongoDB databases our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use Moose::Util::TypeConstraints; use namespace::autoclean; use MongoDB 0.40; use MongoDBx::Class::Connection; use MongoDBx::Class::ConnectionPool::Backup; use MongoDBx::Class::ConnectionPool::Rotated; use MongoDBx::Class::Database; use MongoDBx::Class::Collection; use MongoDBx::Class::Cursor; use MongoDBx::Class::Reference; use MongoDBx::Class::Meta::AttributeTraits; use Carp; # let's set some internal subtypes we can use to automatically coerce # objects when expanding documents. subtype 'MongoDBx::Class::CoercedReference' => as 'MongoDBx::Class::Reference'; subtype 'ArrayOfMongoDBx::Class::CoercedReference' => as 'ArrayRef[MongoDBx::Class::Reference]'; coerce 'MongoDBx::Class::CoercedReference' => from 'Object' => via { $_->isa('MongoDBx::Class::Reference') ? $_ : MongoDBx::Class::Reference->new(ref_coll => $_->_collection->name, ref_id => $_->_id, _collection => $_->_collection, _class => 'MongoDBx::Class::Reference') }; coerce 'ArrayOfMongoDBx::Class::CoercedReference' => from 'ArrayRef[Object]' => via { my @arr; foreach my $i (@$_) { push(@arr, $i->isa('MongoDBx::Class::Reference') ? $i : MongoDBx::Class::Reference->new(ref_coll => $i->_collection->name, ref_id => $i->_id, _collection => $i->_collection, _class => 'MongoDBx::Class::Reference')); } return \@arr; }; =head1 NAME MongoDBx::Class - Flexible ORM for MongoDB databases =head1 VERSION version 1.02 =head1 SYNOPSIS Normal usage: use MongoDBx::Class; # create a new instance of the module and load a model schema my $dbx = MongoDBx::Class->new(namespace => 'MyApp::Model::DB'); # if MongoDBx::Class can't find your model schema (possibly because # it exists in some different location), you can do this: my $dbx = MongoDBx::Class->new(namespace => 'MyApp::Model::DB', search_dirs => ['/path/to/model/dir']); # connect to a MongoDB server my $conn = $dbx->connect(host => 'localhost', port => 27017); # be safe by default $conn->safe(1); # we could've also just passed "safe => 1" to $dbx->connect() above # get a MongoDB database my $db = $conn->get_database('people'); # insert a person my $person = $db->insert({ name => 'Some Guy', birth_date => '1984-06-12', _class => 'Person' }); print "Created person ".$person->name." (".$person->id.")\n"; $person->update({ name => 'Some Smart Guy' }); $person->delete; See L for simple connection pool usage. =head1 DESCRIPTION L is a flexible object relational mapper (ORM) for L databases. Given a schema-like collection of document classes, MongoDBx::Class expands MongoDB objects (hash-refs in Perl) from the database into objects of those document classes, and collapses such objects back to the database. MongoDBx::Class takes advantage of the fact that Perl's L driver is L-based to extend and tweak the driver's behavior, instead of wrapping it. This means MongoDBx::Class does not define its own syntax, so you simply use it exactly as you would the L driver directly. That said, MongoDBx::Class adds some sugar that enhances and simplifies the syntax unobtrusively (either use it or don't). Thus, it is relatively easy to convert your current L applications to MongoDBx::Class. A collection in MongoDBx::Class C, a database in MongoDBx::Class C, etc. As opposed to other ORMs (even non-MongoDB ones), MongoDBx::Class attempts to stay as close as possible to MongoDB's non-schematic nature. While most ORMs enforce using a single collection (or table in the SQL world) for every object class, MongoDBx::Class allows you to store documents of different classes in different collections (and even databases). A collection can hold documents of many different classes. Not only that, as MongoDBx::Class is Moose based, you can easily create very flexible schemas by using concepts such as inheritance and L. For example, say you have a collection called 'people' with documents representing, well, people, but these people can either be teachers or students. Also, students may assume the role "hall monitor". With MongoDBx::Class, you can create a common base class, say "People", and two more classes that extend it - "Teacher" and "Student" with attributes that are only relevant to each one. You also create a role called "HallMonitor", possibly with some methods of its own. You can save all these "people documents" into a single MongoDB collection, and when fetching documents from that collection, they will be properly expanded to their correct classes (though you will have to apply roles yourself - at least for now). =head2 COMPARISON WITH OTHER MongoDB ORMs As MongoDB is rather young, there aren't many options out there, though CPAN has some pretty good ones, and will probably have more as MongoDB popularity rises. The first MongoDB ORM in CPAN was L, and while it's a very good ORM, MongoDBx::Class was mainly written to overcome some limitations of Mongoose. The biggest of these limitations is that in order to provide a more comfortable syntax than MongoDB's native syntax, Mongoose makes the unfortunate decision of being implemented as a L, meaning only one instance of a Mongoose-based schema can be used in an application. That essentially kills multithreaded applications. Say you have a L-based (doesn't have to be Plack-based though) web application deployed via L (or any other web server for that matter), which is a pre-forking web server - you're pretty much doomed. As L states, it doesn't support connection pooling, so every fork has to have its own connection to the MongoDB server. Mongoose being a singleton means your threads will not have a connection to the server, and you're screwed. MongoDBx::Class does not suffer this limitation. You can start as many connections as you like. If you're running in a pre-forking environment, you don't have to worry about it at all. Other differences from Mongoose include: =over =item * Mongoose creates its own syntax, MongoDBx::Class doesn't, you use L's syntax directly. =item * A document class in Mongoose is connected to a single collection only, and a collection can only have documents of that class. MongoDBx::Class doesn't have that limitation. Do what you like. =item * Mongoose has limited support for multiple database usage. With MongoDBx::Class, you can use as many databases as you want. =item * MongoDBx::Class is way faster. While I haven't performed any real benchmarks, an application converted from Mongoose to MongoDBx::Class showed an increase of speed in orders of magnitude. =item * In Mongoose, your document class attributes are expected to be read-write (i.e. C<< is => 'rw' >> in Moose), otherwise expansion will fail. This is not the case with MongoDBx::Class, your attributes can safely be read-only. =back Another ORM for MongoDB is L, which doesn't use Moose and is thus lighter (though as L is already Moose-based, I see no benefit here). It uses L for data validation (while Moose has its own type validation), and seems to define its own syntax as well. Unfortunately, documentation is currently lacking, and I haven't given it a try, so I can't draw specific comparisons here. Even before Mongoose was born, you could use MongoDB as a backend for L, by using L. However, KiokuDB is considered a database of its own and uses some conventions which doesn't fit well with MongoDB. L already gives a pretty convincing case when and why you should or shouldn't want to use KiokuDB. =head2 CONNECTION POOLING Since version 0.9, C provides experimental, simple connection pooling for applications. Take a look at L for more information. =head2 CAVEATS AND THINGS TO CONSIDER There are a few caveats and important facts to take note of when using MongoDBx::Class as of today: =over =item * MongoDBx::Class's flexibility is dependant on its ability to recognize which class a document in a MongoDB collection expands to. Currently, MongoDBx::Class requires every document to have an attribute called "_class" that contains the name of the document class to use. This isn't very comfortable, but works. I'm still thinking of ways to expand documents without this. This pretty much means that you will have to perform some preparations to use existing MongoDB database with MongoDBx::Class - you will have to update every document in the database with the "_class" attribute. =item * References (representing joins) are expected to be in the DBRef format, as defined in L. If your database references aren't in this format, you'll have to convert them first. =item * The '_id' attribute of all your documents has to be an internally generated L. This limitation may or may not be lifted in the future. =back =head2 TUTORIAL To start using MongoDBx::Class, please read L. It also contains a list of frequently asked questions. =head1 ATTRIBUTES =cut has 'namespace' => (is => 'ro', isa => 'Str', required => 1); has 'search_dirs' => (is => 'ro', isa => 'ArrayRef[Str]', default => sub { [] }); has 'doc_classes' => (is => 'ro', isa => 'HashRef', default => sub { {} }); =head2 namespace A string representing the namespace of the MongoDB schema used (e.g. C). Your document classes, structurally speaking, should be descendants of this namespace (e.g. C, C). =head2 search_dirs An array-ref of directories in which to search for the document classes. Not required, useful if for some reason MongoDBx::Class can't find your document classes. =head2 doc_classes A hash-ref of document classes found when loading the schema. =head1 CLASS METHODS =head2 new( namespace => $namespace ) Creates a new instance of this module. Requires the namespace of the database schema to use. The schema will be immediately loaded, but no connection to a MongoDB server is made yet. =head1 OBJECT METHODS =head2 connect( %options ) Initiates a new connection to a MongoDB server running on a certain host and listening to a certain port. C<%options> is the hash of attributes that can be passed to C in L, plus the 'safe' attribute from L. You're mostly expected to provide the 'host' and 'port' options. If a host is not provided, 'localhost' is used. If a port is not provided, 27017 (MongoDB's default port) is used. Returns a L object. NOTE: Since version 0.7, the created connection object isn't saved in the top MongoDBx::Class object, but only returned, in order to be more like how connection is made in L (and to allow multiple connections). This change breaks backwords compatibility. =cut sub connect { my ($self, %opts) = @_; $opts{namespace} = $self->namespace; $opts{doc_classes} = $self->doc_classes; return MongoDBx::Class::Connection->new(%opts); } =head2 pool( [ type => $type, max_conns => $max_conns, params => \%params, ... ] ) Creates a new connection pool (see L for more info) and returns it. C is either 'rotated' or 'backup' (the default). C is a hash-ref of parameters that can be passed to C<< MongoDB::Connection->new() >> when creating connections in the pool. See L for a complete list of attributes that can be passed. =cut sub pool { my ($self, %opts) = @_; $opts{params} ||= {}; $opts{params}->{namespace} = $self->namespace; $opts{params}->{doc_classes} = $self->doc_classes; if ($opts{type} && $opts{type} eq 'rotated') { return MongoDBx::Class::ConnectionPool::Rotated->new(%opts); } else { return MongoDBx::Class::ConnectionPool::Backup->new(%opts); } } =head1 INTERNAL METHODS The following methods are only to be used internally. =head2 BUILD() Automatically called when creating a new instance of this module. This loads the schema and saves a hash-ref of document classes found in the object. Automatic loading courtesy of L. =cut sub BUILD { my $self = shift; # load the classes require Module::Pluggable; Module::Pluggable->import(search_path => [$self->namespace], search_dirs => $self->search_dirs, require => 1, sub_name => '_doc_classes'); foreach ($self->_doc_classes) { my $name = $_; $name =~ s/$self->{namespace}:://; $self->doc_classes->{$name} = $_; } } =head1 TODO =over 6 =item * Improve the tests. =item * Make the C option in L's relationship types consistent. Either use the full package names or the short class names. =item * Try to find a way to not require documents to have the _class attribute. =back =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L, L, L. =head1 ACKNOWLEDGEMENTS =over =item * Rodrigo de Oliveira, author of L, whose code greatly assisted me in writing MongoDBx::Class. =item * Thomas Müller, for adding support for the Transient trait. =item * Dan Dascalescu, for fixing typos and other problems in the documentation. =back =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable; release-pod-syntax.t100644001750001750 45012070412022 17067 0ustar00idoido000000000000MongoDBx-Class-1.02/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Pod 1.41"; plan skip_all => "Test::Pod 1.41 required for testing POD" if $@; all_pod_files_ok(); release-pod-coverage.t100644001750001750 76512070412022 17345 0ustar00idoido000000000000MongoDBx-Class-1.02/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Pod::Coverage 1.08"; plan skip_all => "Test::Pod::Coverage 1.08 required for testing POD coverage" if $@; eval "use Pod::Coverage::TrustPod"; plan skip_all => "Pod::Coverage::TrustPod required for testing POD coverage" if $@; all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); release-dist-manifest.t100644001750001750 46612070412022 17537 0ustar00idoido000000000000MongoDBx-Class-1.02/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::DistManifest"; plan skip_all => "Test::DistManifest required for testing the manifest" if $@; manifest_ok(); Class000755001750001750 012070412022 16064 5ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBxMoose.pm100644001750001750 3127512070412022 17674 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::Moose; # ABSTRACT: Extends Moose with common relationships for MongoDBx::Class documents our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose (); use Moose::Exporter; =head1 NAME MongoDBx::Class::Moose - Extends Moose with common relationships for MongoDBx::Class documents =head1 VERSION version 1.02 =head1 PROVIDES L =head1 SYNOPSIS # create a document class package MyApp::Schema::Novel; use MongoDBx::Class::Moose; # use this instead of Moose; use namespace::autoclean; with 'MongoDBx::Class::Document'; has 'title' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_title'); holds_one 'author' => (is => 'ro', isa => 'MyApp::Schema::PersonName', required => 1, writer => 'set_author'); has 'year' => (is => 'ro', isa => 'Int', predicate => 'has_year', writer => 'set_year'); has 'added' => (is => 'ro', isa => 'DateTime', traits => ['Parsed'], required => 1); holds_many 'tags' => (is => 'ro', isa => 'MyApp::Schema::Tag', predicate => 'has_tags'); joins_one 'synopsis' => (is => 'ro', isa => 'Synopsis', coll => 'synopsis', ref => 'novel'); has_many 'related_novels' => (is => 'ro', isa => 'Novel', predicate => 'has_related_novels', writer => 'set_related_novels', clearer => 'clear_related_novels'); joins_many 'reviews' => (is => 'ro', isa => 'Review', coll => 'reviews', ref => 'novel'); sub print_related_novels { my $self = shift; foreach my $other_novel ($self->related_novels) { print $other_novel->title, ', ', $other_novel->year, ', ', $other_novel->author->name, "\n"; } } around 'reviews' => sub { my ($orig, $self) = (shift, shift); my $cursor = $self->$orig; return $cursor->sort([ year => -1, title => 1, 'author.last_name' => 1 ]); }; __PACKAGE__->meta->make_immutable; =head1 DESCRIPTION This module provides some relationship types (i.e. database references) for L and L, in the form of L attributes. It also provides everything Moose provides, and so is to replace C when creating document classes. =cut Moose::Exporter->setup_import_methods( with_meta => [ 'belongs_to', 'has_one', 'has_many', 'holds_one', 'holds_many', 'defines_many', 'joins_one', 'joins_many' ], also => 'Moose', ); =head1 RELATIONSHIP TYPES This module provides the following relationship types. The differences between different relationships stem from the different ways in which references can be represented in the database. =head2 belongs_to Specifies that the document has an attribute which references another, supposedly parent, document. The reference is in the form documented by L. belongs_to 'parent' => (is => 'ro', isa => 'Article', required => 1) In the database, this relationship is represented in the referencing document like this: { ... parent => { '$ref' => 'coll_name', '$id' => $mongo_oid } ... } Calling C on the referencing document returns the parent document after expansion: $doc->parent->title; =cut sub belongs_to { my ($meta, $name, %opts) = @_; $opts{isa} = 'MongoDBx::Class::CoercedReference'; $opts{coerce} = 1; $meta->add_attribute('_'.$name => %opts); $meta->add_method($name => sub { my $self = shift; my $attr = '_'.$name; return unless $self->$attr; return $self->$attr->load; }); } =head2 has_one Specifies that the document has an attribute which references another document. The reference is in the form documented by L. This is entirely equivalent to L, the two are provided merely for convenience, the difference is purely semantic. =cut sub has_one { belongs_to(@_); } =head2 has_many Specifies that the document has an attribute which holds a list (array) of references to other documents. These references are in the form documented by L. has_many 'related_articles' => (is => 'ro', isa => 'Article', predicate => 'has_related_articles') In the database, this relationship is represented in the referencing document like this: { ... related_articles => [{ '$ref' => 'coll_name', '$id' => $mongo_oid_1 }, { '$ref' => 'other_coll_name', '$id' => $mongo_oid_2 }] ... } Calling C on the referencing document returns an array of all referenced documents, after expansion: foreach ($doc->related_articles) { print $_->title, "\n"; } =cut sub has_many { my ($meta, $name, %opts) = @_; $opts{isa} = "ArrayOfMongoDBx::Class::CoercedReference"; $opts{coerce} = 1; $meta->add_attribute('_'.$name => %opts); $meta->add_method($name => sub { my $self = shift; my $attr = '_'.$name; my @docs; foreach (@{$self->$attr || []}) { push(@docs, $_->load); } return @docs; }); } =head2 holds_one Specifies that the document has an attribute which holds an embedded document (a.k.a sub-document) in its entirety. The embedded document is represented by a class that C L. holds_one 'author' => (is => 'ro', isa => 'MyApp::Schema::PersonName', required => 1) Note that the C relationship has the unfortunate constraint of having to pass the full package name of the foreign document (e.g. MyApp::Schema::PersonName above), whereas other relationship types (except C which has the same constraint) require the class name only (e.g. Novel). In the database, this relationship is represented in the referencing (i.e. holding) document like this: { ... author => { first_name => 'Arthur', middle_name => 'Conan', last_name => 'Doyle' } ... } Calling C on the referencing document returns the embedded document, after expansion: $doc->author->first_name; # returns 'Arthur' =cut sub holds_one { my ($meta, $name, %opts) = @_; $opts{documentation} = 'MongoDBx::Class::EmbeddedDocument'; $meta->add_attribute($name => %opts); } =head2 holds_many Specifies that the document has an attribute which holds a list (array) of embedded documents (a.k.a sub-documents) in their entirety. These embedded documents are represented by a class that C L. holds_many 'tags' => (is => 'ro', isa => 'MyApp::Schema::Tag', predicate => 'has_tags') Note that the C relationship has the unfortunate constraint of having to pass the full package name of the foreign document (e.g. MyApp::Schema::Tag above), whereas other relationship types (except C which has the same constraint) require the class name only (e.g. Novel). In the database, this relationship is represented in the referencing (i.e. holding) document like this: { ... tags => [ { category => 'mystery', subcategory => 'thriller' }, { category => 'mystery', subcategory => 'detective' } ] ... } =cut sub holds_many { my ($meta, $name, %opts) = @_; $opts{isa} = "ArrayRef[$opts{isa}]"; $opts{documentation} = 'MongoDBx::Class::EmbeddedDocument'; $meta->add_attribute('_'.$name => %opts); $meta->add_method($name => sub { my $self = shift; my $attr = '_'.$name; return @{$self->$attr || []}; }); } =head2 defines_many Specifies that the document has an attribute which holds a hash (a.k.a associative array or dictionary) of embedded documents in their entirety. These embedded documents are represented by a class that C L. defines_many 'things' => (is => 'ro', isa => 'MyApp::Schema::Thing', predicate => 'has_things'); When calling C on a document, a hash-ref is returned (not a hash!). Like C and C, this relationship has the unfortunate constraint of having to pass the full package name of the foreign document (e.g. MyApp::Schema::Thing above), whereas other relationship types require the class name only (e.g. Novel). In the database, this relationship is represented in the referencing (i.e. holding) document like this: { ... things => { "mine" => { _class => 'MyApp::Schema::Thing', ... }, "his" => { _class => 'MyApp::Schema::Thing', ... }, "hers" => { _class => 'MyApp::Schema::Thing', ... }, } ... } =cut sub defines_many { my ($meta, $name, %opts) = @_; $opts{isa} = "HashRef[$opts{isa}]"; $opts{documentation} = 'MongoDBx::Class::EmbeddedDocument'; $meta->add_attribute('_'.$name => %opts); $meta->add_method($name => sub { my $self = shift; my $attr = '_'.$name; return $self->$attr || {}; }); } =head2 joins_one Specifies that the document is referenced by one other document. The reference in the other document to this document is in the form documented by L. This "pseudo-attribute" requires two new options: 'coll', with the name of the collection in which the referencing document is located, and 'ref', with the name of the attribute which is referencing the document. If 'coll' isn't provided, the referencing document is searched in the same collection. joins_one 'synopsis' => (is => 'ro', isa => 'Synopsis', coll => 'synopsis', ref => 'novel') In the database, this relationship is represented in the referencing document (located in the 'synopsis' collection) like this: { ... novel => { '$ref' => 'novels', '$id' => $mongo_oid } ... } When calling C on a Novel document, a C query is performed like so: $db->get_collection('synopsis')->find_one({ 'novel.$id' => $doc->_id }) Note that passing a 'required' option to C has no effect at all, the existence of the referencing document is never enforced, so C can be returned. =cut sub joins_one { my ($meta, $name, %opts) = @_; $opts{coll} ||= ''; $opts{isa} = 'MongoDBx::Class::Reference'; my $ref = delete $opts{ref}; my $coll = delete $opts{coll}; $meta->add_method($name => sub { my $self = shift; my $coll_name = $coll eq '' ? $self->_collection->name : $coll; return $self->_collection->_database->get_collection($coll_name)->find_one({ $ref.'.$id' => $self->_id }); }); } =head2 joins_many Specifies that the document is referenced by other documents. The references in the other document to this document are in the form documented by L. This "pseudo-attribute" requires two new options: 'coll', with the name of the collection in which the referencing documents are located, and 'ref', with the name of the attribute which is referncing the document. If 'coll' isn't provided, the referencing documents are searched in the same collection. joins_many 'reviews' => (is => 'ro', isa => 'Review', coll => 'reviews', ref => 'novel') In the database, this relationship is represented in the referencing documents (located in the 'reviews' collection) like this: { ... novel => { '$ref' => 'novels', '$id' => $mongo_oid } ... } When calling C on a Novel document, a C query is performed like so: $db->get_collection('reviews')->find({ 'novel.$id' => $doc->_id }) And thus a L is returned. Note that passing the 'required' option to C has no effect at all, and the existance of referncing documents is never enforced, so the cursor can have a count of zero. =cut sub joins_many { my ($meta, $name, %opts) = @_; $opts{coll} ||= ''; $opts{isa} = 'MongoDBx::Class::Reference'; my $ref = delete $opts{ref}; my $coll = delete $opts{coll}; $meta->add_method($name => sub { my $self = shift; my $coll_name = $coll eq '' ? $self->_collection->name : $coll; return $self->_collection->_database->get_collection($coll_name)->find({ $ref.'.$id' => $self->_id }); }); } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::Moose You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1; Cursor.pm100644001750001750 641612070412022 20046 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::Cursor; # ABSTRACT: A MongoDBx::Class cursor/iterator object for query results our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use namespace::autoclean; use version; extends 'MongoDB::Cursor'; =head1 NAME MongoDBx::Class::Cursor - A MongoDBx::Class cursor/iterator object for query results =head1 VERSION version 1.02 =head1 EXTENDS L =head1 SYNOPSIS my $cursor = $coll->find({ author => 'Conan Doyle' }); print "Novels by Arthur Conan Doyle:\n"; foreach ($cursor->sort({ year => 1 })->all) { print $_->title, '( ', $_->year, ")\n"; } =head1 DESCRIPTION MongoDBx::Class::Cursor extends L. At its basis, it adds automatic document expansion when traversing cursor results. =head1 ATTRIBUTES No special attributes are added. =head1 OBJECT METHODS Aside from methods provided by L, the following method modifications are performed: =head2 next( [ $do_not_expand ] ) Returns the next document in the cursor, if any. Automatically expands that document to the appropriate class (if '_class' attribute exists, otherwise document is returned as is). If C<$do_not_expand> is true, the document will not be expanded and simply returned as is (i.e. as a hash-ref). =cut around 'next' => sub { my ($orig, $self, $do_not_expand) = (shift, shift); my $doc = $self->$orig || return; return $do_not_expand ? $doc : $self->_connection->expand($self->_ns, $doc); }; =head2 sort( $rules ) Adds a sort to the cursor and returns the cursor itself for chaining. C<$rules> can either be an unordered hash-ref, an ordered L object, or an ordered array reference such as this: $cursor->sort([ date => -1, time => -1, subject => 1 ]) =cut around 'sort' => sub { my ($orig, $self, $rules) = @_; if (ref $rules eq 'ARRAY') { return $self->$orig(Tie::IxHash->new(@$rules)); } else { return $self->$orig($rules); } }; sub _connection { version->parse($MongoDB::VERSION) < v0.502.0 ? $_[0]->SUPER : $_[0]->_client; } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::Cursor You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable; Database.pm100644001750001750 454512070412022 20276 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::Database; # ABSTRACT: A MongoDBx::Class database object our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use namespace::autoclean; use version; extends 'MongoDB::Database'; =head1 NAME MongoDBx::Class::Database - A MongoDBx::Class database object =head1 VERSION version 1.02 =head1 EXTENDS L =head1 SYNOPSIS # get a database object from your connection object my $db = $conn->get_database($db_name); # or simply $conn->$db_name =head1 DESCRIPTION MongoDBx::Class::Database extends L. All it actually does is override the C method such that it returns a L object instead of a L object. =head1 ATTRIBUTES No special attributes are added. =head1 OBJECT METHODS Only the C method is modified as described above. =cut override 'get_collection' => sub { MongoDBx::Class::Collection->new(_database => shift, name => shift); }; sub _connection { version->parse($MongoDB::VERSION) < v0.502.0 ? $_[0]->SUPER : $_[0]->_client; } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::Database You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable; Document.pm100644001750001750 2101512070412022 20357 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::Document; # ABSTRACT: A MongoDBx::Class document role our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose::Role; use namespace::autoclean; use Carp; =head1 NAME MongoDBx::Class::Document - A MongoDBx::Class document role =head1 VERSION version 1.02 =head1 SYNOPSIS # create a document class package MyApp::Schema::Novel; use MongoDBx::Class::Moose; # use this instead of Moose; use namespace::autoclean; with 'MongoDBx::Class::Document'; has 'title' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_title'); holds_one 'author' => (is => 'ro', isa => 'MyApp::Schema::PersonName', required => 1, writer => 'set_author'); has 'year' => (is => 'ro', isa => 'Int', predicate => 'has_year', writer => 'set_year'); has 'added' => (is => 'ro', isa => 'DateTime', traits => ['Parsed'], required => 1); has 'review_count' => (is => 'rw', isa => 'Int', traits => ['Transient'], builder => '_build_review_count'); holds_many 'tags' => (is => 'ro', isa => 'MyApp::Schema::Tag', predicate => 'has_tags'); joins_one 'synopsis' => (is => 'ro', isa => 'Synopsis', coll => 'synopsis', ref => 'novel'); has_many 'related_novels' => (is => 'ro', isa => 'Novel', predicate => 'has_related_novels', writer => 'set_related_novels', clearer => 'clear_related_novels'); joins_many 'reviews' => (is => 'ro', isa => 'Review', coll => 'reviews', ref => 'novel'); sub _build_review_count { shift->reviews->count } sub print_related_novels { my $self = shift; foreach my $other_novel ($self->related_novels) { print $other_novel->title, ', ', $other_novel->year, ', ', $other_novel->author->name, "\n"; } } around 'reviews' => sub { my ($orig, $self) = (shift, shift); my $cursor = $self->$orig; return $cursor->sort([ year => -1, title => 1, 'author.last_name' => 1 ]); }; __PACKAGE__->meta->make_immutable; =head1 DESCRIPTION MongoDBx::Class::Document is a L meant to be consumed by document classes. It provides expanded MongoDB documents with some common attributes, and needed methods for easy updating and deleting of documents. =head1 ATTRIBUTES The following attributes are provided: =head2 _id The document's internal ID, represented as a L object. This is a required attribute. =head2 _collection The L object representing the MongoDB collection in which the document is stored. This is a required attribute. =head2 _class A string. The name of the document class of this document. This is a required attribute. =cut has '_id' => (is => 'ro', isa => 'MongoDB::OID', required => 1); has '_collection' => (is => 'ro', isa => 'MongoDBx::Class::Collection', required => 1); has '_class' => (is => 'ro', isa => 'Str', required => 1); =head1 OBJECT METHODS The following object methods are provided: =head2 id() =head2 oid() Both methods are equivalent. They are convenience methods that return the documents internal MongoDB OID in string format. =cut sub id { shift->_id->to_string; } sub oid { shift->id; } =head2 update( [ \%object, [ \%options ] ] ) Saves a new version of the document to the database. The behavior of this method is dependant on the existance or non-existance of an object hash-ref: If an object hash-ref is provided, all of its key-value pairs are collapsed, and a C<$set> update is performed on them. For example: $doc->update({ author => 'Sir Arthur Conan Doyle', year => 1895 }, $options) Will effectively result in something like this being performed: $coll->update({ _id => $doc->_id }, { '$set' => { author => 'Sir Arthur Conan Doyle', year => 1895 } }, $options) If an object hash-ref isn't provided, the entire document object is collapsed and an aggresive update is performed (i.e. an entirely new version of the document, representing the current state of the document's attributes, is saved to the database. For example: my $doc = find_one($id); $doc->set_author('Sir Arthur Conan Doyle'); $doc->set_year(1895); $doc->update; Will effectively result in something like this being performed: $coll->update({ _id => $doc->_id }, $collapsed_doc, $options) You can pass an options hash-ref just like with the C method of L, but only if you pass an update object also. Returns the output of L's original C method. =cut sub update { my $self = shift; if (scalar @_ && ref $_[0] eq 'HASH') { $_[0]->{_class} = $self->_class; my $doc = $self->_connection->collapse($_[0]); delete $doc->{_class}; # update the database entry my $ret = $self->_collection->update({ _id => $self->_id }, { '$set' => $doc }, $_[1]); # update the object itself $self->_update_self; return $ret; } else { my $doc = { _class => $self->_class }; foreach ($self->meta->get_all_attributes) { my $name = $_->name; next if $name eq '_collection' || $name eq '_class'; my $val = $self->$name; next unless defined $val; $name =~ s/^_// if ($_->{isa} eq 'MongoDBx::Class::CoercedReference' || $_->{isa} eq 'ArrayOfMongoDBx::Class::CoercedReference' || ($_->documentation && $_->documentation eq 'MongoDBx::Class::EmbeddedDocument') ); $doc->{$name} = $val; } return $self->_collection->update({ _id => $self->_id }, $self->_connection->collapse($doc), $_[1]); } } =head2 delete() =head2 remove() Both methods are equivalent. They are shortcut methods for invoking the collection's C method on this document only. So, umm, they remove the document. But note that this operation does not cascade, so documents which are considered dependant on this document (such as those that reference it with C) will not be removed too. =cut sub delete { my $self = shift; $self->_collection->remove({ _id => $self->_id }); } sub remove { shift->delete } =head1 INTERNAL METHODS The following methods are only to be used internally. =head2 _database() Convenience method, shortcut for C<< $doc->_collection->_database >>. =cut sub _database { shift->_collection->_database; } =head2 _connection() Convenience method, shortcut for C<< $doc->_database->_connection >>. =cut sub _connection { shift->_database->_connection; } =head2 _attributes() Returns a list of names of all attributes the document object has, minus '_collection' and '_class', sorted alphabetically. =cut sub _attributes { my @names; foreach (shift->meta->get_all_attributes) { next if $_->name =~ m/^_(class|collection)$/; if ($_->{isa} =~ m/MongoDBx::Class::CoercedReference/ || ($_->documentation && $_->documentation eq 'MongoDBx::Class::EmbeddedDocument')) { my $name = $_->name; $name =~ s/^_//; push(@names, $name); } else { push(@names, $_->name); } } return sort @names; } =head2 _update_self() Used by the C method to update the object instance with the new values. =cut sub _update_self { my $self = shift; my $new_version = $self->_collection->find_one({ _id => $self->_id }); unless ($new_version) { # huh?! we didn't find the document?!@? lets warn but not die carp "Can't find document after update, object instance will remain unchanged."; return; } foreach ($self->meta->get_all_attributes) { my $new_val = $_->get_value($new_version); $_->set_value($self, $new_val) if defined $new_val; } } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::Document You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1; Reference.pm100644001750001750 606312070412022 20465 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::Reference; # ABSTRACT: An embedded document representing a reference to a different document (thus establishing a relationship) our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use namespace::autoclean; use Carp; =head1 NAME MongoDBx::Class::Reference - An embedded document representing a reference to a different document (thus establishing a relationship) =head1 VERSION version 1.02 =head1 CONSUMES L =head1 DESCRIPTION This class represents a reference (or "join") to a MongoDB document. In L, references are expected to be in the DBRef format, as defined in L, for example (this is a JSON example): { "$ref": "collection_name", "$id": ObjectId("4cbca90d3a41e35916720100") } =cut with 'MongoDBx::Class::EmbeddedDocument'; =head1 ATTRIBUTES Aside from attributes provided by L, the following attributes are provided: =head2 ref_coll A string representing the collection in which the reference document is stored (translates to the '$ref' hash key above). =head2 ref_id A L object with the internal ID of the referenced document (translates to the '$id' hash key above). =cut has 'ref_coll' => (is => 'ro', isa => 'Str', required => 1); has 'ref_id' => (is => 'ro', isa => 'MongoDB::OID', required => 1); =head1 METHODS Aside from methods provided by L, the following methods are provided: =head2 load() Returns the document referenced by this object, after expansion. This is mostly used internally, you don't have to worry about it. =cut sub load { my $self = shift; return $self->_collection->_database->get_collection($self->ref_coll)->find_one($self->ref_id); } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::Reference You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable; Tutorial.pod100644001750001750 7070412070412022 20563 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Class=head1 NAME MongoDBx::Class::Tutorial - Tutorial for using MongoDBx::Class =head1 VERSION version 1.02 =head1 INTRODUCTION L is a flexible object relational mapper (ORM) for L databases. Before reading this tutorial, please read L. =head1 CREATING DOCUMENT CLASSES The first step towards using MongoDBx::Class is creating document classes. Let's say your application is called MyApp, and that it reviews novels by popular authors. Every novel has a title (its name), the full name of its author, its release year, a synopsis, the date the novel was added to the database, a list of tags categorizing the novel, a list of related novels, and zero or more reviews. MongoDBx::Class provides you with a flexible foundation for building classes that represent our proposed database schema and the relationships (or "joins") between these classes. This foundation is provided by three modules: =over =item * L - intended to be used by document classes in place of L. It provides Moose, and all needed relationship types, in the form of Moose attributes. =item * L - a L intended to be consumed by document classes. When documents are expanded from the database, this role is applied to them and thus provides them some common attributes and methods. =item * L - a L intended to be consumed by embedded document classes. When embedded documents are found inside other documents and expanded, this role is applied to them and thus provides them some common attributes and methods. =back Before actually creating document classes, we need to design our schema. The most common question to tackle when designing a schema is when and how to use references, and when to simply embed documents. Refer to L for some information about schema design in MongoDB. Our database schema will be like this: =over =item * Every novel will be represented by a document in the 'novels' collection. =item * An author's name will be stored in the novel's document as an embedded document that has first and last names, and possibly a middle name. =item * The synopsis of the novel will be stored as a standalone document in the 'synopsis' collection, and will simply reference the novel with a 'novel' attribute. =item * The date and time the novel was added to the database will be stored as a W3C formatted string which will automatically be converted to a L object (see L or read further for the rationale). =item * The list of tags will be stored in the novel document as an array of embedded documents. =item * The list of related novels will be stored in the novel document as an array of database references to other novels. =item * The reviews for every novel are stored as standalone documents in the 'reviews' collection, and will simply reference the novel with a 'novel' attribute. =back Let's look at a possible JSON representation of a novel - "The Valley of Fear" by Sir Arthur Conan Doyle: # in the 'novels' collection { "_id": ObjectId("4cbca90d576fad5916790100"), "title": "The Valley of Fear", "year": 1914, "author": { "first_name": "Arthur", "middle_name": "Conan", "last_name": "Doyle" }, "added": "2010-01-11T22:12:44+02:00", "tags": [ { "category": "mystery", "subcategory": "thriller" }, { "category": "mystery", "subcategory": "detective" }, { "category": "crime", "subcategory": "fiction" } ], "related_novels": [ { "$ref": "novels", "$id": ObjectId("4cbca90d3a41e35916720100") }, { "$ref": "novels", "$id": ObjectId("4cbca90d44d8c959167a0100") }, { "$ref": "novels", "$id": ObjectId("4cbca90d486bbf59166f0100") } ] } # in the 'synopsis' collection { "_id": ObjectId("4cbca90d699e9a5916670100"), "novel": { "$ref": "novels", "$id": ObjectId("4cbca90d576fad5916790100") }, "text": "The Valley of Fear is the final Sherlock Holmes novel by Sir Arthur Conan Doyle. The story was first published in the Strand Magazine between September 1914 and May 1915. The first book edition was published in New York on 27 February 1915." } # in the 'reviews' collection { "_id": ObjectId("4cbca90dfbb2945916740100"), "novel": { "$ref": "novels", "$id": ObjectId("4cbca90d576fad5916790100") }, "reviewer": "Some Guy", "text": "I really liked it!", "score": 5 }, { "_id": ObjectId("4cbca90e0ad57b5916f50100"), "novel": { "$ref": "novels", "$id": ObjectId("4cbca90d576fad5916790100") }, "reviewer": "Some Other Guy", "text": "It was okay.", "score": 3 }, { "_id": ObjectId("4cbca90e0b9c175916c60100"), "novel": { "$ref": "novels", "$id": ObjectId("4cbca90d576fad5916790100") }, "reviewer": "Totally Different Guy", "text": "Man, that just sucked!", "score": 1 } We now need to translate this structure to MongoDBx::Class document classes. As mentioned before, document classes are L classes, but instead of using Moose, they use L. We'll start with the document class representing novels: package MyApp::Schema::Novel; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::Document'; has 'title' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_title'); holds_one 'author' => (is => 'ro', isa => 'MyApp::Schema::PersonName', required => 1, writer => 'set_author'); has 'year' => (is => 'ro', isa => 'Int', predicate => 'has_year', writer => 'set_year'); has 'added' => (is => 'ro', isa => 'DateTime', traits => ['Parsed'], required => 1); has 'review_count' => (is => 'rw', isa => 'Int', traits => ['Transient'], builder => '_build_review_count'); holds_many 'tags' => (is => 'ro', isa => 'MyApp::Schema::Tag', predicate => 'has_tags'); joins_one 'synopsis' => (is => 'ro', isa => 'Synopsis', coll => 'synopsis', ref => 'novel'); has_many 'related_novels' => (is => 'ro', isa => 'Novel', predicate => 'has_related_novels', writer => 'set_related_novels', clearer => 'clear_related_novels'); joins_many 'reviews' => (is => 'ro', isa => 'Review', coll => 'reviews', ref => 'novel'); sub _build_review_count { shift->reviews->count } sub print_related_novels { my $self = shift; foreach my $other_novel ($self->related_novels) { print $other_novel->title, ', ', $other_novel->year, ', ', $other_novel->author->name, "\n"; } } around 'reviews' => sub { my ($orig, $self) = (shift, shift); my $cursor = $self->$orig; return $cursor->sort([ year => -1, title => 1, 'author.last_name' => 1 ]); }; __PACKAGE__->meta->make_immutable; Aside from standard Moose usage, we've used some of MongoDBx::Class' built-in relationship types. Head over to L for a list of provided relationship types and how they translate to database entries. In this example, we've used the C relationship to denote that a Novel document entirely holds one embedded PersonName sub-document. We've used C to have Novel documents entirely hold Tag documents. We've also used C to easily find a novel's synopsis, located in the synopsis collection. C was used to easily find all Review documents located in the reviews collection. Finally, we've used C for the list of references to related novels. You will notice that when using the C and C relationship types, we've given the full package names to the C option (such as MyApp::Schema::PersonName), while in other relationship types, we've given the class names only (such as 'Synopsis'). This inconsistency is only temporary, and will change in future versions, so keep your finger on the pulse. Of particular interest is the 'added' attribute, for which we've added the attribute trait (automatically provided with MongoDBx::Class). When adding this trait to an attribute, MongoDBx::Class looks for a module implementing the L role named like the 'isa' option of the attribute. In our example, MongoDBx::Class will look for L. However, if you're creating your own ParsedAttribute classes, you need to also pass the 'parser' option. For example: has 'added' => (is => 'ro', isa => 'DateTime', traits => ['Parsed'], parser => 'MyApp::ParsedAttribute::MyCoolDateTimeParser', required => 1); The 'Parsed' trait means that the parser is responsible for expanding this attribute from the database when loading a document, and for collapsing the attribute when saving to the database. This is similar to L' L family of classes from the SQL world. You might ask yourself why we're using a special DateTime parser here, even though L natively supports DateTime objects. The following paragraph is taken from L's documentation): While the Perl L driver already supports L objects natively, due to a bug with MongoDB, you can't save dates earlier than the UNIX epoch. This module overcomes this limitation by simply saving dates as strings and automatically turning them into DateTime objects (and vica-versa). The DateTime strings are formatted by the L module, which parses dates in the format recommended by the W3C. This is good for web apps, and also makes it easier to edit dates from the MongoDB shell. But most importantly, it also allows sorting by date. Also note the 'review_count' attribute, which has the 'Transient' trait. This trait means the attribute is not to be saved in the database, even though it is one of the document's attributes. This is useful for calculated attributes, or any supporting attributes you add to a document class whose state should not be saved. You should note that if for some reason a document in the database I have an attribute marked as transient in the document's class (for example if you added it to the document manually), the value of this attribute will also be ignored when the document is read from the database. As you can see, the C relationship creates a one-to-many relationship between one document to one or more other documents which can be considered "child documents". In this example, there is a one-to-many relationship between a 'novel' document and one or more 'review' documents. A C relationship is implemented with a cursor. For example, calling the C method on a C object will generally return a L object for a C query that searches for documents in the 'reviews' collection, whose 'novel' attribute references the C document. In the C class above, you will notice the C method is also modified with Moose's C method modifier. This example illustrates the fact that C is implemented with a cursor. Suppose you know you will always want to call C and get the child documents sorted by date (or other fields). The above modification simply sorts the cursor before returning it to the caller. Of course, this is merely an example of the things you can do with the C relationship. Continuing on, lets create our two embedded document classes. We'll start with PersonName: package MyApp::Schema::PersonName; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::EmbeddedDocument'; has 'first_name' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_first_name'); has 'middle_name' => (is => 'ro', isa => 'Str', predicate => 'has_middle_name', writer => 'set_middle_name'); has 'last_name' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_last_name'); sub name { my $self = shift; my $name = $self->first_name; $name .= ' '.$self->middle_name if $self->has_middle_name; $name .= ' '.$self->last_name; return $name; } __PACKAGE__->meta->make_immutable; This is a very simple class, with no relationships. We use the C method to easily print a person's full name. On to the Tag document class: package MyApp::Schema::Tag; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::EmbeddedDocument'; has 'category' => (is => 'ro', isa => 'Str', required => 1); has 'subcategory' => (is => 'ro', isa => 'Str', required => 1); __PACKAGE__->meta->make_immutable; Again, this is a very simple example. Embedded documents will often be as simple as that. We have two document classes left. The first is the Synopsis class: package MyApp::Schema::Synopsis; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::Document'; belongs_to 'novel' => (is => 'ro', isa => 'Novel', required => 1); has 'text' => (is => 'ro', isa => 'Str', writer => 'set_text', required => 1); __PACKAGE__->meta->make_immutable; Here, we've used C to signify a Synopsis document belongs to a Novel document. Every Synopsis document has a 'novel' attribute which references the parent Novel document. Only the Review class is left: package MyApp::Schema::Review; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::Document'; belongs_to 'novel' => (is => 'ro', isa => 'Novel', required => 1); has 'reviewer' => (is => 'ro', isa => 'Str', required => 1); has 'text' => (is => 'ro', isa => 'Str', required => 1); has 'score' => (is => 'ro', isa => 'Int', predicate => 'has_score'); __PACKAGE__->meta->make_immutable; That wraps up our document classes. Before we continue, it is important to note that MongoDBx::Class' ability to identify the class of a document is currently reliant on the existance of a '_class' attribute in every document in the database (as described in L). So, looking at the JSON representations from before, we need to modify the representations like so: # in the 'novels' collection { "_id": ObjectId("4cbca90d576fad5916790100"), "_class": "Novel", "title": "The Valley of Fear", "year": 1914, "author": { "first_name": "Arthur", "middle_name": "Conan", "last_name": "Doyle" }, "added": "2010-01-11T22:12:44+02:00", "tags": [ { "category": "mystery", "subcategory": "thriller" }, { "category": "mystery", "subcategory": "detective" }, { "category": "crime", "subcategory": "fiction" } ], "related_novels": [ { "$ref": "novels", "$id": ObjectId("4cbca90d3a41e35916720100") }, { "$ref": "novels", "$id": ObjectId("4cbca90d44d8c959167a0100") }, { "$ref": "novels", "$id": ObjectId("4cbca90d486bbf59166f0100") } ] } # in the 'synopsis' collection { "_id": ObjectId("4cbca90d699e9a5916670100"), "_class": "Synopsis", "novel": { "$ref": "novels", "$id": ObjectId("4cbca90d576fad5916790100") }, "text": "The Valley of Fear is the final Sherlock Holmes novel by Sir Arthur Conan Doyle. The story was first published in the Strand Magazine between September 1914 and May 1915. The first book edition was published in New York on 27 February 1915." } # in the 'reviews' collection { "_id": ObjectId("4cbca90dfbb2945916740100"), "_class": "Review", "novel": { "$ref": "novels", "$id": ObjectId("4cbca90d576fad5916790100") }, "reviewer": "Some Guy", "text": "I really liked it!", "score": 5 }, { "_id": ObjectId("4cbca90e0ad57b5916f50100"), "_class": "Review", "novel": { "$ref": "novels", "$id": ObjectId("4cbca90d576fad5916790100") }, "reviewer": "Some Other Guy", "text": "It was okay.", "score": 3 }, { "_id": ObjectId("4cbca90e0b9c175916c60100"), "_class": "Review", "novel": { "$ref": "novels", "$id": ObjectId("4cbca90d576fad5916790100") }, "reviewer": "Totally Different Guy", "text": "Man, that just sucked!", "score": 1 } You will notice that it is not required to add the '_class' attribute to embedded documents, only to standalone documents. The reason for the '_class' requirement is the fact that MongoDBx::Class doesn't enforce one collection for every document class. Every collection can have documents of one or more classes, and documents of the same class can be stored in one or more collections, even databases. =head1 LOADING MongoDBx::Class AND CONNECTING TO A MongoDB SERVER The next step is loading the schema we've just created, and connecting to a MongoDB server: my $dbx = MongoDBx::Class->new(namespace => 'MyApp::Schema'); We need to pass the namespace of our schema to MongoDBx::Class. It will attempt to automatically load every document class under that namespace, and will return a L object back. We then initiate a connection to a server: my $conn = $dbx->connect(); We don't pass anything to the C method, so it attempts to connect to a MongoDB server running on 'localhost', on the default 27017 port. We can connect to a specific server like so: my $conn = $dbx->connect(host => $host, port => $port); The connect method accepts any of the options that the C method in L accepts, plus the new 'safe' boolean attribute. Passing a true value for this attribute causes MongoDBx::Class to automatically enable the 'pass' option to all insert/update/delete operations performed on the database, so we don't need to pass C<< { safe => 1 } >> to the C, C methods, etc. The safe option is actually required in order to autoamtically expand/collapse documents, so you'd probably want to enable it, but it is kept disabled by default for compatibility with the original MongoDB module. The C method returns a L object. NOTE: versions prior to 0.7 stored the returned connection object as the 'conn' attribute of the C<$dbx> variable. This behavior has been dropped in version 0.7 in order to be consistent with L and allowing multiple connections. Now that we have our connection object, we can get a database object: $conn->get_database('whatever'); # also simply $conn->whatever =head1 INSERTING DOCUMENTS Now the we've loaded our schema and connected to a server, we can start using MongoDBx::Class. Basically, our usage will not differ greatly from direct L usage, as MonogDBx::Class simply extends MongoDB. The biggest difference between directly using MongoDB and using MongoDBx::Class, is the automatic expanding and collapsing of documents. Documents are automatically expanded when inserting documents only if the C option is on, as mentioned in the previous section. Let's create a novel document: my $db = $conn->get_database('myapp'); my $novels_coll = $db->get_collection('novels'); my $novel = $novels_coll->insert({ _class => 'Novel', title => 'The Valley of Fear', year => 1914, author => { first_name => 'Arthur', middle_name => 'Conan', last_name => 'Doyle', }, added => DateTime->now(time_zone => 'Asia/Jerusalem'), tags => [ { category => 'mystery', subcategory => 'thriller' }, { category => 'mystery', subcategory => 'detective' }, { category => 'crime', subcategory => 'fiction' }, ], }); Notice that when inserting the novel document, we've directly inserted the PersonName embedded document and the Tags embedded documents as hash-refs. This insert, which was safe (since the 'safe' attribute of our connection object had a true value), returns the novel document after expansion: $novel->author->name; # prints 'Arthur Conan Doyle' If the insert was unsafe, we'd just get the L of the document back. But note that you can't get the OID object and immediately attempt to load the document with it, as you can't predict the order in which MongoDB will perform asynchronous operations. Lets insert our synopsis now: my $synopsis = $db->synopsis->insert({ _class => 'Synopsis', novel => $novel, text => "The Valley of Fear is the final Sherlock Holmes novel by Sir Arthur Conan Doyle. The story was first published in the Strand Magazine between September 1914 and May 1915. The first book edition was published in New York on 27 February 1915.", }); Notice how we've passed the C<$novel> object directly as the 'novel' attribute. When inserting, MongoDBx::Class will automatically save it as a DBRef object for us. Now for our reviews: my @reviews = $conn->get_collection('reviews')->batch_insert([ { _class => 'Review', novel => $novel, reviewer => 'Some Guy', text => 'I really liked it!', score => 5, }, { _class => 'Review', novel => $novel, reviewer => 'Some Other Guy', text => 'It was okay.', score => 3, }, { _class => 'Review', novel => $novel, reviewer => 'Totally Different Guy', text => 'Man, that just sucked!', score => 1, } ]); my ($total_score, $avg_score) = (0, 0); foreach (@reviews) { $total_score += $_->score || 0; } $avg_score = $total_score / scalar(@reviews); print $avg_score; # prints 3 If we now run C<< $novel->reviews() >>, we'd get a L back. And since we've created a method modification in the Novel class on this method, this cursor will also be sorted. foreach ($novel->reviews) { # $_ is now a MyApp::Schema::Review object print $_->score; } =head1 SEARCHING DOCUMENTS MongoDBx::Class adds automatic expansion to document searching, plus some convenient shortcuts. Say we've received the ID of a novel document as input, and we want to load it. With MongoDB, we'd probably do: $db->novels->find_one({ _id => MongoDB::OID->new(value => $oid) }) With MongoDBx::Class, we can just do: $db->novels->find_one($oid); And C<$oid> can either be the string ID, or a L object. If the document is found, and has the '_class' attribute, then it will be automatically expanded. my $novel = $db->novels->find_one($oid) || die "Oh my god I can't find this, kill me, kill me now"; print $novel->author->name; # again, prints 'Arthur Conan Doyle' Let's search for reviews written by a certain reviewer: my $cursor = $db->reviews->find({ reviewer => 'Some Guy' })->sort({ score => -1 }); # the `find` method also has two synonyms: `query` and `search` This gives a L back: print $cursor->count; # prints 1 while (my $review = $cursor->next) { print $review->novel->title, "\n"; } Sorting documents is easier. We can sort by a list of ordered attributes like so: $cursor->sort([ attr1 => 1, attr2 => 1, attr3 => -1 ]); =head1 UPDATING DOCUMENTS Updating documents is much easier with MongoDBx::Class. There are two ways to update documents in MongoDBx::Class: =over 2 =item * The older, L way of using the C method in L. This is now mostly used to update multiple documents at once. =item * The new, L way of using the C method provided to document classes by L. This is used to update a specific document. =back Let's take a look at the first way. Suppose we want to cheat and update all reviews for "The Valley of Fear" with a score of five: $db->reviews->update({ 'novel.$id' => $novel->_id }, { '$set' => { score => 5 } }, { multiple => 1 }); # updates are by default singular, so we add the 'multiple' option This is exactly like using L directly. If we're updating a specific document we've already loaded, however, MongoDBx::Class provides a much more comfortable way. Instead of doing: $db->novels->update({ _id => $novel->_id }, { ... }) We can do: $novel->update({ year => 1915 }); This will effectively invoke a '$set' update like this: $db->novels->update({ _id => $novel->_id }, { '$set' => { year => 1915 } }); But this isn't really the Moose way of doing things, so MongoDBx::Class gives us yet another way of updating a document: $novel->set_year(1915); # we can do this because we've added a 'writer' option to the 'year' attribute $novel->update; When invoking C on a document object with no arguments, a "snapshot" of the document is taken, and the following update is effectively performed: $db->novels->update({ _id => $novel->_id }, { title => "The Valley of Fear", year => 1915, ... }); When we pass arguments to the C method (there are two arguments we can pass, the hash-ref of updates to perform, and the standard options hash-ref we know from the original C method in L), MongoDBx::Class simply performs a '$set' update on the passed hash-ref attributes only. So doing this: $novel->set_year(1915); $novel->update({ title => 'The Valley of Fearrrrrr' }); Will only result in the 'title' attribute being updated, not the year attribute. Updating embedded documents is similarly easy: $novel->author->set_first_name('Sir Arthur'); $novel->update; =head1 REMOVING DOCUMENTS Removing documents with MongoDBx::Class is very easy. Having the document object, simply call: $novel->remove; Or: $novel->delete; And this novel document will be removed from the database. Note, however, that the delete operation does not cascade, so only the novel document is deleted. The synopsis and reviews are not deleted, and you have to do so manually. You can still use the original C method on collections, now mostly to remove multiple documents: $db->get_collection('reviews')->remove({ 'novel.$id' => $novel->_id }); # removes are by default multiple =head1 FAQ =head2 Can I use more than one database? Yes, you can use as many databases as you like and use the same document classes across them: my $data_db = $conn->get_database('myapp_data'); my $user_db = $conn->get_database('myapp_users'); =head2 Can I define different document classes to different databases? There currently isn't a way to define individual schemas for different databases. You can, however, "split" your schema. For example, if your application has a data DB and a user DB, you can put all the document classes of the data DB under MyApp::Schema::Data, and all the document classes of the user DB under MyApp::Schema::User. =head2 What if I want to use the asynchronous L driver instead? Currently, MongoDBx::Class only supports the official L driver, but support for L is planned. =head2 What if I have attributes I don't want to save in the database? MongoDBx::Class does not provide an option like that yet, but will probably do so in upcoming versions. =head2 I'm not getting document objects but just the document hash-refs, what gives? If, either when searching for documents or creating/updating documents, you are not receiving expanded document objects back but only the document hash-refs when searching/updating, or the documents' Ls when inserting, there might be a few reasons for this: =over =item 1. You are not using safe operations: MongoDBx::Class cannot expand documents when not using safe operations. Either enable the 'safe' option when inserting/updating, or enable the safe option globally when connecting using L's C method. =item 2. The document hash-ref does not define the '_class' attribute: MongoDBx::Class cannot expand document without knowing to which document class the document belongs. Therefore, documents in the MongoDB database must have the '_class' attribute. =item 3. The '_class' attribute is wrong or does not exist: If the document has the '_class' attribute and you're still not getting document objects back, then MongoDBx::Class probably can't find the document class. This might be because the class name is wrong, or the class was not found when loading MongoDBx::Class. If that is the case, you might wanna take a look at the L attribute of MongoDBx::Class. =back =head2 Who framed Roger Rabbit? I framed Roger Rabbit! =head1 WRAP UP You now know how you can use L in your applications, and make MongoDB act like a relational database, without sacrificing its NoSQL nature and special features. Before using MongoDBx::Class, please take into account that its alpha software, and is not ready yet for production use. If you find bugs, please report them in the usual channels (see L). =head1 AUTHOR Ido Perlmuter, C<< >> =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::Tutorial =head1 SEE ALSO L, L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut MongoDBxTestSchema000755001750001750 012070412022 17243 5ustar00idoido000000000000MongoDBx-Class-1.02/t/libTag.pm100644001750001750 44612070412022 20440 0ustar00idoido000000000000MongoDBx-Class-1.02/t/lib/MongoDBxTestSchemapackage MongoDBxTestSchema::Tag; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::EmbeddedDocument'; has 'category' => (is => 'ro', isa => 'Str', required => 1); has 'subcategory' => (is => 'ro', isa => 'Str', required => 1); __PACKAGE__->meta->make_immutable; Collection.pm100644001750001750 2327712070412022 20710 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::Collection; # ABSTRACT: A MongoDBx::Class collection object our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use namespace::autoclean; use Carp; use version; extends 'MongoDB::Collection'; =head1 NAME MongoDBx::Class::Collection - A MongoDBx::Class collection object =head1 VERSION version 1.02 =head1 EXTENDS L =head1 SYNOPSIS # get a collection from a L object my $coll = $db->get_collection($coll_name); # or $db->$coll_name # insert a document my $doc = $coll->insert({ title => 'The Valley of Fear', year => 1914, author => 'Conan Doyle', _class => 'Novel' }, { safe => 1 }); # find some documents my @docs = $coll->find({ author => 'Conan Doyle' })->sort({ year => 1 })->all; =head1 DESCRIPTION MongoDBx::Class::Collection extends L. It adds some convenient options to the syntax and a few method modifications to allow automatic document expansion (when finding) and collapsing (when inserting). If you're not familiar with L, please read it first. =head1 ATTRIBUTES No special attributes are added. =head1 OBJECT METHODS The following methods are available along with all methods defined in L. However, most (or all) of those are modifications of MongoDB::Collection methods. =head2 find( \%query, [ \%attrs ] ) =head2 query( \%query, [ \%attrs ] ) =head2 search( \%query, [ \%attrs ] ) All three methods are equivalent (the last two aliases of the first). These methods perform a search for documents in the collection for documents matching a certain query and return a L object. Refer to L for more information about queries. These methods are modified in the following way: 1. They return a L object instead of a plain L object. 2. The C attribute in the C<$attr> hash-ref can contain an array-ref instead of a L object, such as: $coll->find({ some => 'thing' }, { sort_by => [ title => 1, some => -1 ] }) This array-ref will be converted into a Tie::IxHash object automatically. =cut override 'find' => sub { my ($self, $query, $attrs) = @_; # old school options - these should be set with MongoDB::Cursor methods my ($limit, $skip, $sort_by) = @{ $attrs || {} }{qw/limit skip sort_by/}; $limit ||= 0; $skip ||= 0; my $q = {}; if ($sort_by) { $sort_by = Tie::IxHash->new(@$sort_by) if ref $sort_by eq 'ARRAY'; $q->{'query'} = $query; $q->{'orderby'} = $sort_by; } else { $q = $query ? $query : {}; } my $conn_key = version->parse($MongoDB::VERSION) < v0.502.0 ? '_connection' : '_client'; my $cursor = MongoDBx::Class::Cursor->new( $conn_key => $self->_database->_connection, _ns => $self->full_name, _query => $q, _limit => $limit, _skip => $skip ); $cursor->_init; return $cursor; }; sub search { shift->find(@_); } =head2 find_one( $query, [ \%fields ] ) Performs a query on the collection and returns the first matching document. This method is modified in the following way: 1. In L, C<$query> can either be a hash-ref, a L object or an array-ref. Here, however, C<$query> can also be a MongoDB::OID, or a MongoDB::OID's string representation (see L). Searching by internal ID is thus much more convenient: my $doc = $coll->find_one("4cbca90d3a41e35916720100"); 2. The matching document is automatically expanded to the appropriate document class, but only if it has the '_class' attribute (as described in L). If it doesn't, or if expansion is impossible due to other reasons, it will be returned as is (i.e. as a hash-ref). 3. In MongoDB::Collection, passing a C<\%fields> hash-ref will result in the document being returned with those fields only (and the _id field). Behavior of this when documents are expanded is currently undefined. =cut around 'find_one' => sub { my ($orig, $self, $orig_query, $fields) = @_; my $query = {}; if ($orig_query && !ref $orig_query && length($orig_query) == 24) { $query->{_id} = MongoDB::OID->new(value => $orig_query); } elsif ($orig_query && ref $orig_query eq 'MongoDB::OID') { $query->{_id} = $orig_query; } elsif ($orig_query) { $query = $orig_query; } return $self->$orig($query, $fields); }; =head2 insert( $doc, [ \%opts ] ) Inserts the given document into the database, automatically collapsing it before insertion. An optional options hash-ref can be passed. If this hash-ref holds a safe key with a true value, insert will be safe (refer to L for more information). When performing a safe insert, the newly created document is returned (after expansions). If unsafe, its L is returned. If your L has a true value for the safe attribute, insert will be safe by default. If that is the case, and you want the specific insert to be unsafe, pass a false value for C in the C<\%opts> hash-ref. Document to insert can either be a hash-ref, a L object or an even-numbered array-ref, but currently only hash-refs are automatically collapsed. =head2 batch_insert( \@docs, [ \%opts ] ) Receives an array-ref of documents and an optional hash-ref of options, and inserts all the documents to the collection one after the other. C<\%opts> can have a "safe" key that should hold a boolean value. If true (and if your L has a true value for the safe attribute), inserts will be safe, and an array of all the documents inserted (after expansion) will be returned. If false, an array with all the document IDs is returned. Documents to insert can either be hash-refs, L objects or even-numbered array references, but currently only hash-refs are automatically collapsed. =cut around 'batch_insert' => sub { my ($orig, $self, $docs, $opts) = @_; $opts ||= {}; $opts->{safe} = 1 if $self->_database->_connection->safe && !defined $opts->{safe}; foreach (@$docs) { next unless ref $_ eq 'HASH' && $_->{_class}; $_ = $self->_database->_connection->collapse($_); } if ($opts->{safe}) { return map { $self->find_one($_) } $self->$orig($docs, $opts); } else { return $self->$orig($docs, $opts); } }; =head2 update( \%criteria, \%object, [ \%options ] ) Searches for documents matching C<\%criteria> and updates them according to C<\%object> (refer to L for more info). As opposed to the original method, this method will automatically collapse the C<\%object> hash-ref. It will croak if criteria and/or object aren't hash references. Do not use this method to update a specific document that you already have (i.e. after expansion). L has its own update method which is more convenient. Notice that this method doesn't collapse attributes with the L trait. Only the L update method performs that. =cut around 'update' => sub { my ($orig, $self, $criteria, $object, $opts) = @_; croak 'Criteria for update must be a hash reference (received '.ref($criteria).').' unless ref $criteria eq 'HASH'; croak 'Object for update must be a hash reference (received '.ref($object).').' unless ref $object eq 'HASH'; $self->_collapse_hash($object); return $self->$orig($criteria, $object, $opts); }; =head2 ensure_index( $keys, [ \%options ] ) Makes sure the given keys of this collection are indexed. C<$keys> is either an unordered hash-ref, an ordered L object, or an ordered, even-numbered array reference like this: $coll->ensure_index([ title => 1, date => -1 ]) =cut around 'ensure_index' => sub { my ($orig, $self, $keys, $options) = @_; if ($keys && ref $keys eq 'ARRAY') { $keys = Tie::IxHash->new(@$keys); } return $self->$orig($keys, $options); }; =head1 INTERNAL METHODS The following methods are only to be used internally: =head2 _collapse_hash( \%object ) Collapses an entire hash-ref for proper database insertions. =cut sub _collapse_hash { my ($self, $object) = @_; foreach (keys %$object) { if (m/^\$/ && ref $object->{$_} eq 'HASH') { # this is something like '$set' or '$inc', we need to collapse the values in it $self->_collapse_hash($object->{$_}); } else { $object->{$_} = $self->_database->_connection->_collapse_val($object->{$_}); } } } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::Collection You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable; Connection.pm100644001750001750 2672612070412022 20716 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::Connection; # ABSTARCT: A connection to a MongoDB server our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use namespace::autoclean; use Module::Load; use version; if (version->parse($MongoDB::VERSION) < v0.502.0) { extends 'MongoDB::Connection'; } else { extends 'MongoDB::MongoClient'; } =head1 NAME MongoDBx::Class::Connection - A connection to a MongoDB server =head1 VERSION version 1.02 =head1 EXTENDS L =head1 SYNOPSIS # connect to a MongoDB server my $conn = $mongodbx->connect(host => '10.10.10.10', port => 27017); # the connection object is automatically saved to the 'conn' # attribute of the L object (C<$mongodbx> above) $conn->get_database('people'); =head1 DESCRIPTION MongoDBx::Class::Connection extends L. This class provides the document expansion and collapsing methods that are used internally by other MongoDBx::Class classes. Note that a L object can only have one connection at a time. Connection is only made via the C method in MongoDBx::Class. =head1 ATTRIBUTES Aside for attributes provided by L, the following special attributes are added: =head2 namespace A string representing the namespace of document classes to load (e.g. MyApp::Schema). This is a required attribute (automatically received from the MongoDBx::Class object). =head2 doc_classes A hash-ref of document classes loaded. This is a required attribute (automatically received from the MongoDBx::Class object). =head2 safe A boolean value indicating whether to use safe operations (e.g. inserts and updates) by default - without the need to pass C<< { safe => 1 } >> to relevant methods - or not. False by default. =head2 is_backup This boolean attribute is used by L objects that use a backup connection. =cut has 'namespace' => (is => 'ro', isa => 'Str', required => 1); has 'doc_classes' => (is => 'ro', isa => 'HashRef', required => 1); has 'safe' => (is => 'rw', isa => 'Bool', default => 0); has 'is_backup' => (is => 'ro', isa => 'Bool', default => 0); =head1 OBJECT METHODS Aside from the methods provided by L, the following methods and modifications are added: =head2 get_database( $name ) Returns a L object representing the MongoDB database named C<$name>. =cut override 'get_database' => sub { my $conn_key = version->parse($MongoDB::VERSION) < v0.502.0 ? '_connection' : '_client'; MongoDBx::Class::Database->new($conn_key => shift, name => shift); }; =head2 safe( $boolean ) Overrides the current value of the safe attribute with a new boolean value. =head2 expand( $coll_ns, \%doc ) Receives the full name (a.k.a namespace) of a collection (that is the database name, followed by a dot, and the collection name), and a document hash-ref, and attempts to expand it according to the '_class' attribute that should exist in the document. If it doesn't exist, the document is returned as is. This is mostly used internally and you don't have to worry about expansion, it's done automatically. =cut sub expand { my ($self, $coll_ns, $doc) = @_; # make sure we've received the namespace and a hash-ref document return unless $coll_ns && $doc && ref $doc eq 'HASH'; # extract the database name and the collection name from the namespace my ($db_name, $coll_name) = ($coll_ns =~ m/^([^.]+)\.(.+)$/); # get the collection my $coll = $self->get_database($db_name)->get_collection($coll_name); # return the document as is if it doesn't have a _class attribute return $doc unless $doc->{_class}; # remove the schema namespace from the document class (we do not # use the full package name internally) and attempt to find that # document class. return the document as is if class isn't found my $dc_name = $doc->{_class}; my $ns = $self->namespace; $dc_name =~ s/^${ns}:://; my $dc = $self->doc_classes->{$dc_name}; return $doc unless $dc; # start building the document object my %attrs = ( _collection => $coll, _class => $doc->{_class}, ); foreach ($dc->meta->get_all_attributes) { # is this a MongoDBx::Class::Reference? if ($_->{isa} eq 'MongoDBx::Class::CoercedReference') { my $name = $_->name; $name =~ s!^_!!; next unless exists $doc->{$name} && defined $doc->{$name} && ref $doc->{$name} eq 'HASH' && exists $doc->{$name}->{'$ref'} && exists $doc->{$name}->{'$id'}; $attrs{$_->name} = MongoDBx::Class::Reference->new( _collection => $coll, _class => 'MongoDBx::Class::Reference', ref_coll => $doc->{$name}->{'$ref'}, ref_id => $doc->{$name}->{'$id'}, ); # is this an array-ref of MongoDBx::Class::References? } elsif ($_->{isa} eq 'ArrayOfMongoDBx::Class::CoercedReference') { my $name = $_->name; $name =~ s!^_!!; next unless exists $doc->{$name} && defined $doc->{$name} && ref $doc->{$name} eq 'ARRAY'; foreach my $ref (@{$doc->{$name}}) { push(@{$attrs{$_->name}}, MongoDBx::Class::Reference->new( _collection => $coll, _class => 'MongoDBx::Class::Reference', ref_coll => $ref->{'$ref'}, ref_id => $ref->{'$id'}, )); } # is this an embedded document (or array-ref of embedded documents)? } elsif ($_->documentation && $_->documentation eq 'MongoDBx::Class::EmbeddedDocument') { my $edc_name = $_->{isa}; $edc_name =~ s/^${ns}:://; if ($_->{isa} =~ m/^ArrayRef/) { my $name = $_->name; $name =~ s!^_!!; $edc_name =~ s/^ArrayRef\[//; $edc_name =~ s/\]$//; next unless exists $doc->{$name} && defined $doc->{$name} && ref $doc->{$name} eq 'ARRAY'; $attrs{$_->name} = []; foreach my $a (@{$doc->{$name}}) { $a->{_class} = $edc_name; push(@{$attrs{$_->name}}, $self->expand($coll_ns, $a)); } } elsif ($_->{isa} =~ m/^HashRef/) { my $name = $_->name; $name =~ s!^_!!; $edc_name =~ s/^HashRef\[//; $edc_name =~ s/\]$//; next unless exists $doc->{$name} && defined $doc->{$name} && ref $doc->{$name} eq 'HASH'; $attrs{$_->name} = {}; foreach my $key (keys %{$doc->{$name}}) { $doc->{$name}->{$key}->{_class} = $edc_name; $attrs{$_->name}->{$key} = $self->expand($coll_ns, $doc->{$name}->{$key}); } } else { next unless exists $doc->{$_->name} && defined $doc->{$_->name}; $doc->{$_->name}->{_class} = $edc_name; $attrs{$_->name} = $self->expand($coll_ns, $doc->{$_->name}); } # is this an expanded attribute? } elsif ($_->can('does') && $_->does('Parsed') && $_->parser) { next unless exists $doc->{$_->name} && defined $doc->{$_->name}; load $_->parser; my $val = $_->parser->new->expand($doc->{$_->name}); $attrs{$_->name} = $val if defined $val; # is this a transient attribute? } elsif ($_->can('does') && $_->does('Transient')) { next; # just pass the value as is } else { next unless exists $doc->{$_->name} && defined $doc->{$_->name}; $attrs{$_->name} = $doc->{$_->name}; } } return $dc->new(%attrs); } =head2 collapse( \%doc ) Receives a document hash-ref and returns a collapsed version of it such that it can be safely inserted to the database. For example, you can't save an embedded document directly to the database, you need to convert it to a hash-ref first. This method is mostly used internally and you don't have to worry about collapsing, it's done automatically. =cut sub collapse { my ($self, $doc) = @_; # return the document as is if it doesn't have a _class attribute return $doc unless $doc->{_class}; # remove the schema namespace from the document class (we do not # use the full package name internally) and attempt to find that # document class. return the document as is if class isn't found my $dc_name = $doc->{_class}; my $ns = $self->namespace; $dc_name =~ s/^${ns}:://; my $dc = $self->doc_classes->{$dc_name}; my $new_doc = { _class => $doc->{_class} }; foreach (keys %$doc) { next if $_ eq '_class'; my $attr = $dc->meta->get_attribute($_); if ($attr && $attr->can('does') && $attr->does('Parsed') && $attr->parser) { load $attr->parser; my $parser = $attr->parser->new; if (ref $doc->{$_} eq 'ARRAY') { my @arr; foreach my $val (@{$doc->{$_}}) { push(@arr, $parser->collapse($val)); } $new_doc->{$_} = \@arr; } else { $new_doc->{$_} = $parser->collapse($doc->{$_}); } } elsif ($attr && $attr->can('does') && $attr->does('Transient')) { next; } else { $new_doc->{$_} = $self->_collapse_val($doc->{$_}); } } return $new_doc; } =head1 INTERNAL METHODS =head2 _collapse_val( $val ) =cut sub _collapse_val { my ($self, $val) = @_; if (blessed $val && $val->isa('MongoDBx::Class::Reference')) { return { '$ref' => $val->ref_coll, '$id' => $val->ref_id }; } elsif (blessed $val && $val->can('does') && $val->does('MongoDBx::Class::Document')) { return { '$ref' => $val->_collection->name, '$id' => $val->_id }; } elsif (blessed $val && $val->can('does') && $val->does('MongoDBx::Class::EmbeddedDocument')) { return $val->as_hashref; } elsif (ref $val eq 'ARRAY') { my @arr; foreach (@$val) { if (blessed $_ && $_->isa('MongoDBx::Class::Reference')) { push(@arr, { '$ref' => $_->ref_coll, '$id' => $_->ref_id }); } elsif (blessed $_ && $_->can('does') && $_->does('MongoDBx::Class::Document')) { push(@arr, { '$ref' => $_->_collection->name, '$id' => $_->_id }); } elsif (blessed $_ && $_->can('does') && $_->does('MongoDBx::Class::EmbeddedDocument')) { push(@arr, $_->as_hashref); } else { push(@arr, $_); } } return \@arr; } elsif (ref $val eq 'HASH') { my $h = {}; foreach (keys %$val) { if (blessed $val->{$_} && $val->{$_}->isa('MongoDBx::Class::Reference')) { $h->{$_} = { '$ref' => $val->{$_}->ref_coll, '$id' => $val->{$_}->ref_id }; } elsif (blessed $val->{$_} && $val->{$_}->can('does') && $val->{$_}->does('MongoDBx::Class::Document')) { $h->{$_} = { '$ref' => $val->{$_}->_collection->name, '$id' => $val->{$_}->_id }; } elsif (blessed $val->{$_} && $val->{$_}->can('does') && $val->{$_}->does('MongoDBx::Class::EmbeddedDocument')) { $h->{$_} = $val->{$_}->as_hashref; } else { $h->{$_} = $val->{$_}; } } return $h; } return $val; } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::Connection You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable; Novel.pm100644001750001750 300312070412022 21020 0ustar00idoido000000000000MongoDBx-Class-1.02/t/lib/MongoDBxTestSchemapackage MongoDBxTestSchema::Novel; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::Document'; has 'title' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_title'); holds_one 'author' => (is => 'ro', isa => 'MongoDBxTestSchema::PersonName', required => 1, writer => 'set_author'); has 'year' => (is => 'ro', isa => 'Int', predicate => 'has_year', writer => 'set_year'); has 'added' => (is => 'ro', isa => 'DateTime', traits => ['Parsed'], required => 1); has 'review_count' => (is => 'rw', isa => 'Int', traits => ['Transient'], lazy => 1, builder => '_build_review_count'); holds_many 'tags' => (is => 'ro', isa => 'MongoDBxTestSchema::Tag', predicate => 'has_tags'); joins_one 'synopsis' => (is => 'ro', isa => 'Synopsis', coll => 'synopsis', ref => 'novel'); has_many 'related_novels' => (is => 'ro', isa => 'Novel', predicate => 'has_related_novels', writer => 'set_related_novels', clearer => 'clear_related_novels'); joins_many 'reviews' => (is => 'ro', isa => 'Review', coll => 'reviews', ref => 'novel'); sub _build_review_count { shift->reviews->count } sub print_related_novels { my $self = shift; foreach my $other_novel ($self->related_novels) { print $other_novel->title, ', ', $other_novel->year, ', ', $other_novel->author->name, "\n"; } } around 'reviews' => sub { my ($orig, $self) = (shift, shift); my $cursor = $self->$orig; return $cursor->sort([ year => -1, title => 1, 'author.last_name' => 1 ]); }; __PACKAGE__->meta->make_immutable; Review.pm100644001750001750 64412070412022 21166 0ustar00idoido000000000000MongoDBx-Class-1.02/t/lib/MongoDBxTestSchemapackage MongoDBxTestSchema::Review; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::Document'; belongs_to 'novel' => (is => 'ro', isa => 'Novel', required => 1); has 'reviewer' => (is => 'ro', isa => 'Str', required => 1); has 'text' => (is => 'ro', isa => 'Str', required => 1); has 'score' => (is => 'ro', isa => 'Int', predicate => 'has_score'); __PACKAGE__->meta->make_immutable; ConnectionPool.pm100644001750001750 1626012070412022 21540 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::ConnectionPool; # ABSTARCT: A simple connection pool for MongoDBx::Class our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose::Role; use namespace::autoclean; use Carp; =head1 NAME MongoDBx::Class::ConnectionPool - A simple connection pool for MongoDBx::Class =head1 VERSION version 1.02 =head1 SYNOPSIS # create a MongoDBx::Class object normally: use MongoDBx::Class; my $dbx = MongoDBx::Class->new(namespace => 'MyApp::Model::DB'); # instead of connection, create a rotated pool my $pool = $dbx->pool(max_conns => 200, type => 'rotated'); # max_conns defaults to 100 # or, if you need to pass attributes to MongoDB::Connection->new(): my $pool = $dbx->pool(max_conns => 200, type => 'rotated', params => { host => $host, username => $username, password => $password, ... }); # get a connection from the pool on a per-request basis my $conn = $pool->get_conn; # ... do stuff with $conn and return it when done ... $pool->return_conn($conn); # only relevant on backup pools but a good practice anyway =head1 DESCRIPTION WARNING: connection pooling via MongoDBx::Class is experimental. It is a quick, simple implementation that may or may not work as expected. MongoDBx::Class::ConnectionPool is a very simple interface for creating MongoDB connection pools. The basic idea is: create a pool with a maximum number of connections as a setting. Give connections from the pool on a per-request basis. The pool is empty at first, and connections are created for each request, until the maximum is reached. The behaviour of the pool when this maximum is reached is dependant on the implementation. There are currently two implementations: =over =item * Rotated pools (L) - these pools hold at most the number of maximum connections defined. An index is held, initially starting at zero. When a request for a connection is made, the connection located at the current index is returned (if exists, otherwise a new one is created), and the index is incremented. When the index reaches the end of the pool, it returns to the beginning (i.e. zero), and the next request will receive the first connection in the pool, and so on. This means that every connection in the pool can be shared by an unlimited number of requesters. =item * Backup pools (L) - these pools expect the receiver of a connection to return it when they're done using it. If no connections are available when a request is made (i.e. all connections are being used), a backup connection is returned (there can be only one backup connection). This means that every connection in the pool can be used by one requester, except for the backup connection which can be shared. =back The rotated pool makes more sense for pools with a relatively low number of connections, while the backup pool is more fit for a larger number of connections. The selection should be based, among other factors, on your application's metrics: how many end-users (e.g. website visitors) use your application concurrently? does your application experience larger loads and usage numbers at certain points of the day/week? does it make more sense for you to balance work between a predefined number of connections (rotated pool) or do you prefer each end-user to get their own connection (backup pool)? At any rate, every end-user will receive a connection, shared or not. =head1 ATTRIBUTES =head2 max_conns An integer defining the maximum number of connections a pool can hold. Defaults to 100. =head2 pool An array-reference of L objects, this is the actual pool. Mostly used and populated internally. =head2 num_used For backup pools, this will be an integer indicating the number of connections from the pool currently being used. For rotated pools, this will be the index of the connection to be given to the next end-user. =head2 params A hash-ref of parameters to pass to C<< MongoDB::Connection->new() >> when creating a new connection. See L for more information. =cut has 'max_conns' => ( is => 'ro', isa => 'Int', default => 100, ); has 'pool' => ( is => 'ro', isa => 'ArrayRef[MongoDBx::Class::Connection]', writer => '_set_pool', default => sub { [] }, ); has 'num_used' => ( is => 'ro', isa => 'Int', writer => '_set_used', default => 0, ); has 'params' => ( is => 'ro', isa => 'HashRef', required => 1, ); =head1 REQUIRED METHODS This L requires consuming classes to implement the following methods: =over =item * get_conn() Returns a connection from the pool to a requester, possibly creating a new one in the process if no connections are available and the maximum has not been reached yet. =item * return_conn( $conn ) Returns a connection (receievd via C) to the pool, meant to be called by the end-user after being done with the connection. Only relevant when C actually takes the connection out of the pool (so it is not shared), like with backup pools. Otherwise this method may do nothing. =back =cut requires 'get_conn'; requires 'return_conn'; =head1 PROVIDED METHODS Meant to be used by consuming classes: =head2 _get_new_conn() Creates a new L object, increments the C attribute, and returns the new connection. Should be used by C. =cut sub _get_new_conn { my $self = shift; my $conn = MongoDBx::Class::Connection->new(%{$self->params}); $self->_inc_used; return $conn; } =head2 _inc_used( [ $int ] ) Increases the C attribute by C<$int> (which can be negative), or by 1 if C<$int> is not supplied. =cut sub _inc_used { my ($self, $int) = @_; $int ||= 1; $self->_set_used($self->num_used + $int); } =head2 _add_to_pool( $conn ) Adds a connection object to the end of the pool (the C attribute). =cut sub _add_to_pool { my ($self, $conn) = @_; my $pool = $self->pool; push(@$pool, $conn); $self->_set_pool($pool); } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::ConnectionPool You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1; Synopsis.pm100644001750001750 47012070412022 21551 0ustar00idoido000000000000MongoDBx-Class-1.02/t/lib/MongoDBxTestSchemapackage MongoDBxTestSchema::Synopsis; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::Document'; belongs_to 'novel' => (is => 'ro', isa => 'Novel', required => 1); has 'text' => (is => 'ro', isa => 'Str', writer => 'set_text', required => 1); __PACKAGE__->meta->make_immutable; ParsedAttribute.pm100644001750001750 673512070412022 21677 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::ParsedAttribute; # ABSTRACT: A Moose role for automatically expanded and collapsed document attributes. our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose::Role; use namespace::autoclean; requires 'expand'; requires 'collapse'; =head1 NAME MongoDBx::Class::ParsedAttribute - A Moose role for automatically expanded and collapsed document attributes. =head1 VERSION version 1.02 =head1 SYNOPSIS # in MyApp/ParsedAttribute/URI.pm package MyApp::ParsedAttribute::URI; use Moose; use namespace::autoclean; use URI; with 'MongoDBx::Class::ParsedAttribute'; sub expand { my ($self, $uri_text) = @_; return URI->new($uri_text); } sub collapse { my ($self, $uri_obj) = @_; return $uri_obj->as_string; } 1; # in MyApp/Schema/SomeDocumentClass.pm has 'url' => (is => 'ro', isa => 'URI', traits => ['Parsed'], parser => 'MyApp::ParsedAttribute::URI', required => 1); =head1 DESCRIPTION This module is a L meant to be consumed by classes that automatically expand (from a MongoDB database) and collapse (to a MongoDB database) attributes of a certain type. This is similar to L' L family of modules that do pretty much the same thing for the SQL world. A class implementing this role with a name such as 'URI' (full package name MongoDBx::Class::ParsedAttribute::URI or MyApp::ParsedAttribute::URI) is expected to expand and collapse L objects. Similarly, a class named 'NetAddr::IP' is expected to handle L objects. Currently, a L parser is provided with the L distribution. =head1 REQUIRES Consuming classes must implement the following methods: =head2 expand( $value ) Receives a raw attribute's value from a MongoDB document and returns the appropriate object representing it. For example, supposing the value is an epoch integer, the expand method might return a L object. =head2 collapse( $object ) Receives an object representing a parsed attribute, and returns that objects value in a form that can be saved in the database. For example, if the object is a L object, this method might return the date's epoch integer. =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::ParsedAttribute You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1; EmbeddedDocument.pm100644001750001750 1043412070412022 21774 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Classpackage MongoDBx::Class::EmbeddedDocument; # ABSTRACT: A MongoDBx::Class embedded (sub-)document role our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose::Role; use namespace::autoclean; =head1 NAME MongoDBx::Class::EmbeddedDocument - A MongoDBx::Class embedded (sub-)document role =head1 VERSION version 1.02 =head1 SYNOPSIS # create an embedded document class package MyApp::Schema::PersonName; use MongoDBx::Class::Moose; # use this instead of Moose use namespace::autoclean; with 'MongoDBx::Class::EmbeddedDocument'; has 'first_name' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_first_name'); has 'middle_name' => (is => 'ro', isa => 'Str', predicate => 'has_middle_name', writer => 'set_middle_name'); has 'last_name' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_last_name'); sub name { my $self = shift; my $name = $self->first_name; $name .= ' '.$self->middle_name.' ' if $self->has_middle_name; $name .= $self->last_name; return $name; } __PACKAGE__->meta->make_immutable; =head1 DESCRIPTION MongoDBx::Class::EmbeddedDocument is a L meant to be consumed by document classes representing embedded documents. These are documents that are entirely contained within parent MongoDB documents. The role provides expanded embedded documents with some common attributes and useful methods. =head1 ATTRIBUTES =head2 _collection The L object representing the MongoDB collection in which this embedded document is stored (more correctly, the collection in which the L holding this embedded document is stored). This is a required attribute. =cut has '_collection' => (is => 'ro', isa => 'MongoDBx::Class::Collection', required => 1); =head2 _class A string. The name of the document class of this embedded document. A required attribute. =cut has '_class' => (is => 'ro', isa => 'Str', required => 1); =head1 METHODS The following methods are provided: =head2 as_hashref() Returns the embedded document as a hash reference, without the _collection and _class attributes (if they exist). =cut sub as_hashref { my ($self, $hash) = (shift, {}); foreach my $ha (keys %$self) { next if $ha eq '_collection' || $ha eq '_class'; $hash->{$ha} = $self->{$ha}; } return $hash; } =head1 INTERNAL METHODS =head2 _database() Convenience shortcut for running C<< $embd_doc->_collection->_database >>. =cut sub _database { shift->_collection->_database; } =head2 _attributes() Returns a list of names of all attributes the embedded document object has, minus '_collection' and '_class', sorted alphabetically. =cut sub _attributes { my @names; foreach (shift->meta->get_all_attributes) { next if $_->name =~ m/^_(class|collection)$/; if ($_->{isa} =~ m/MongoDBx::Class::CoercedReference/ || ($_->documentation && $_->documentation eq 'MongoDBx::Class::EmbeddedDocument')) { my $name = $_->name; $name =~ s/^_//; push(@names, $name); } else { push(@names, $_->name); } } return sort @names; } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::EmbeddedDocument You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1; PersonName.pm100644001750001750 120012070412022 22001 0ustar00idoido000000000000MongoDBx-Class-1.02/t/lib/MongoDBxTestSchemapackage MongoDBxTestSchema::PersonName; use MongoDBx::Class::Moose; use namespace::autoclean; with 'MongoDBx::Class::EmbeddedDocument'; has 'first_name' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_first_name'); has 'middle_name' => (is => 'ro', isa => 'Str', predicate => 'has_middle_name', writer => 'set_middle_name'); has 'last_name' => (is => 'ro', isa => 'Str', required => 1, writer => 'set_last_name'); sub name { my $self = shift; my $name = $self->first_name; $name .= ' '.$self->middle_name.' ' if $self->has_middle_name; $name .= $self->last_name; return $name; } __PACKAGE__->meta->make_immutable; Meta000755001750001750 012070412022 16752 5ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/ClassAttributeTraits.pm100644001750001750 333012070412022 22601 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Class/Metapackage MongoDBx::Class::Meta::AttributeTraits; # ABSTRACT: Attribute traits provided by MongoDBx::Class our $VERSION = "1.02"; $VERSION = eval $VERSION; =head1 NAME MongoDBx::Class::Meta::AttributeTraits - Attribute traits provided by MongoDBx::Class =head1 VERSION version 1.02 =cut package MongoDBx::Class::Meta::AttributeTraits::Parsed; # ABSTRACT: An attribute trait for attributes automatically expanded and collapsed by a parser class our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose::Role; use namespace::autoclean; =head1 NAME MongoDBx::Class::Meta::AttributeTraits::Parsed - An attribute trait for attributes automatically expanded and collapsed by a parser class. =cut has 'parser' => (is => 'ro', isa => 'Str', lazy_build => 1); sub _build_parser { 'MongoDBx::Class::ParsedAttribute::'.shift->{isa}; } { package Moose::Meta::Attribute::Custom::Trait::Parsed; sub register_implementation { 'MongoDBx::Class::Meta::AttributeTraits::Parsed' } } package MongoDBx::Class::Meta::AttributeTraits::Transient; # ABSTRACT: An attribute trait for attributes not saved in the database our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose::Role; use namespace::autoclean; { package Moose::Meta::Attribute::Custom::Trait::Transient; sub register_implementation { 'MongoDBx::Class::Meta::AttributeTraits::Transient' } } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1; ConnectionPool000755001750001750 012070412022 21015 5ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/ClassBackup.pm100644001750001750 1220012070412022 22733 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Class/ConnectionPoolpackage MongoDBx::Class::ConnectionPool::Backup; # ABSTARCT: A simple connection pool with a backup connection our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use namespace::autoclean; use Carp; with 'MongoDBx::Class::ConnectionPool'; =head1 NAME MongoDBx::Class::ConnectionPool::Backup - A simple connection pool with a backup connection =head1 VERSION version 1.02 =head1 SYNOPSIS # create a MongoDBx::Class object normally: use MongoDBx::Class; my $dbx = MongoDBx::Class->new(namespace => 'MyApp::Model::DB'); # instead of connection, create a pool my $pool = $dbx->pool(max_conns => 200, type => 'backup'); # max_conns defaults to 100 # or, if you need to pass attributes to MongoDB::Connection->new(): my $pool = $dbx->pool(max_conns => 200, type => 'backup', params => { host => $host, username => $username, password => $password, }); # get a connection from the pool on a per-request basis my $conn = $pool->get_conn; # ... do stuff with $conn and return it when done ... $pool->return_conn($conn); =head1 DESCRIPTION MongoDBx::Class::ConnectionPool::Backup is an implementation of the L L. In this implementation, the pool has a maximum number of connections. Whenever someone makes a request for a connection, an existing connection is taken out of the pool and returned (or created if none are available and the maximum has not been reached). When the requester is done with the connection, they are expected to return the connection to the pool. If a connection is not available for the requester (i.e. the maximum has been reached and all connections are used), a backup connection is returned. This backup connection can be shared by multiple requesters, but the pool's main connections cannot. This pool is most appropriate for larger pools where you do not wish to share connections between clients, but want to ensure that on the rare occasions that all connections are used, requests will still be honored. =head1 CONSUMES L =head1 ATTRIBUTES The following attributes are added: =head2 backup_conn The backup L object. =cut has 'backup_conn' => ( is => 'ro', isa => 'MongoDBx::Class::Connection', writer => '_set_backup', ); =head1 METHODS =head2 get_conn() Returns a connection from the pool to a requester. If a connection is not available but the maximum has not been reached, a new connection is made, otherwise the backup connection is returned. =cut sub get_conn { my $self = shift; # are there available connections in the pool? if (scalar @{$self->pool}) { return $self->_take_from_pool; } # there aren't any, can we create a new connection? if ($self->num_used < $self->max_conns) { # yes we can, let's create it return $self->_get_new_conn; } # no more connections, return backup conn return $self->backup_conn; } =head2 return_conn( $conn ) Returns a connection to the pool. If a client attempts to return the backup connection, nothing will happen (the backup connection is always saved). =cut sub return_conn { my ($self, $conn) = @_; # do not return the backup connection return if $conn->is_backup; # only add connection if pool isn't full, otherwise discard it if (scalar @{$self->pool} + $self->num_used - 1 < $self->max_conns) { $self->_add_to_pool($conn); $self->_inc_used(-1); } } =head1 INTERNAL METHODS =head2 BUILD() Called by Moose after object initiation. =cut sub BUILD { my $self = shift; my %params = %{$self->params}; $params{is_backup} = 1; $self->_set_backup(MongoDBx::Class::Connection->new(%params)); } sub _take_from_pool { my $self = shift; my $pool = $self->pool; my $conn = shift @$pool; $self->_set_pool($pool); $self->_inc_used; return $conn; } around 'get_conn' => sub { my ($orig, $self) = @_; return $self->$orig || $self->backup_conn; }; =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::ConnectionPool::Backup You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable; Rotated.pm100644001750001750 1130212070412022 23132 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Class/ConnectionPoolpackage MongoDBx::Class::ConnectionPool::Rotated; # ABSTARCT: A simple connection pool with rotated connections our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use namespace::autoclean; use Carp; use Try::Tiny; with 'MongoDBx::Class::ConnectionPool'; =head1 NAME MongoDBx::Class::ConnectionPool::Rotated - A simple connection pool with rotated connections =head1 VERSION version 1.02 =head1 SYNOPSIS # create a MongoDBx::Class object normally: use MongoDBx::Class; my $dbx = MongoDBx::Class->new(namespace => 'MyApp::Model::DB'); # instead of connection, create a pool my $pool = $dbx->pool(max_conns => 200, type => 'rotated'); # max_conns defaults to 100 # or, if you need to pass attributes to MongoDB::Connection->new(): my $pool = $dbx->pool(max_conns => 200, type => 'rotated', params => { host => $host, username => $username, password => $password, }); # get a connection from the pool on a per-request basis my $conn = $pool->get_conn; # ... do stuff with $conn and return it when done ... $pool->return_conn($conn); # not really needed, but good practice for future proofing and quick pool type switching =head1 DESCRIPTION MongoDBx::Class::ConnectionPool::Rotated is an implementation of the L L. In this implementation, the pool has a maximum number of connections. An index is kept, and whenever someone makes a request for a connection, the connection at the current index is returned (but not taken out of the pool, as opposed to L), and the index is raised. If a connection does not exist yet at the current index and the maximum has not been reached, a new connections is created, added to the pool and returned. If the maximum has been reached and the index is at the end, it is rotated to the beginning, and the first connection in the pool is returned. Therefore, every connection in the pool can be shared by an unlimited number of requesters. This pool is most appropriate for smaller pools where you want to distribute the workload between a set of connections and you don't mind sharing. =head1 CONSUMES L =head1 METHODS =head2 get_conn() Returns the connection at the current index and raises the index. If no connection is available at that index and the maximum has not been reached yet, a new connection will be created. If the index is at the end, it is returned to the beginning and the first connection from the pool is returned. =cut sub get_conn { my $self = shift; # are there available connections in the pool? if (scalar @{$self->pool} == $self->max_conns && $self->num_used < $self->max_conns) { return $self->_take_from_pool; } # there aren't any, can we create a new connection? if ($self->num_used < $self->max_conns) { # yes we can, let's create it return $self->_get_new_conn; } # no more connections, rotate to the beginning $self->_set_used(0); return $self->_take_from_pool; } =head2 return_conn() Doesn't do anything in this implementation but required by L. =cut sub return_conn { return } sub _take_from_pool { my $self = shift; my $conn = $self->pool->[$self->num_used]; $self->_inc_used; return $conn; } around '_get_new_conn' => sub { my ($orig, $self) = @_; my $conn = $self->$orig; my $pool = $self->pool; push(@$pool, $conn); $self->_set_pool($pool); return $conn; }; =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::ConnectionPool::Rotated You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L, L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable; ParsedAttribute000755001750001750 012070412022 21166 5ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/ClassDateTime.pm100644001750001750 663212070412022 23367 0ustar00idoido000000000000MongoDBx-Class-1.02/lib/MongoDBx/Class/ParsedAttributepackage MongoDBx::Class::ParsedAttribute::DateTime; # ABSTRACT: An automatic DateTime parser for MongoDBx::Class document classes our $VERSION = "1.02"; $VERSION = eval $VERSION; use Moose; use namespace::autoclean; use DateTime::Format::W3CDTF; with 'MongoDBx::Class::ParsedAttribute'; =head1 NAME MongoDBx::Class::ParsedAttribute::DateTime - An automatic DateTime parser for MongoDBx::Class document classes =head1 VERSION version 1.02 =head1 SYNOPSIS # in one of your document classes has 'datetime' => (is => 'ro', isa => 'DateTime', traits => ['Parsed'], required => 1); =head1 DESCRIPTION This class implements the L role. It provides document classes with the ability to automatically expand and collapse L values. While the Perl L driver already supports L objects natively, due to a bug with MongoDB, you can't save dates earlier than the UNIX epoch. This module overcomes this limitation by simply saving dates as strings and automatically turning them into DateTime objects (and vica-versa). The DateTime strings are formatted by the L module, which parses dates in the format recommended by the W3C. This is good for web apps, and also makes it easier to edit dates from the MongoDB shell. But most importantly, it also allows sorting by date. Note that if you already have date attributes in your database, you can't just start using this parser, you will first have to convert them to the W3C format. =head1 ATTRIBUTES =head2 f A L object used for expanding/collapsing. Automatically created. =cut has 'f' => (is => 'ro', isa => 'DateTime::Format::W3CDTF', default => sub { DateTime::Format::W3CDTF->new }); =head1 CLASS METHODS =head2 new() Creates a new instance of this module. =head1 OBJECT METHODS =head2 expand( $str ) Converts a W3C datetime string to DateTime object. =cut sub expand { return eval { $_[0]->f->parse_datetime($_[1]) } || undef; } =head2 collapse( $dt ) Converts a DateTime object to a W3C datetime string. =cut sub collapse { return eval { $_[0]->f->format_datetime($_[1]) } || undef; } =head1 AUTHOR Ido Perlmuter, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc MongoDBx::Class::ParsedAttribute::DateTime You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 SEE ALSO L. =head1 LICENSE AND COPYRIGHT Copyright 2010-2012 Ido Perlmuter. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut __PACKAGE__->meta->make_immutable;