package Entities::Backend::MongoDB;
BEGIN {
$Entities::Backend::MongoDB::VERSION = '0.2';
}
use Moose;
use namespace::autoclean;
use MongoDB;
use Carp;
use DateTime::Format::ISO8601;
with 'Entities::Backend';
# ABSTRACT: Stores all Entities data in a MongoDB database.
=head1 NAME
Entities::Backend::MongoDB - Stores all Entities data in a MongoDB database.
=head1 VERSION
version 0.2
=head1 SYNOPSIS
use Entities;
use Entities::Backend::MongoDB;
# see synopsis at L<Entities>
=head1 DESCRIPTION
This L<backend|Entities::Backend> for the L<Entities> user management
and authorization system stores all entities and relations between them
in a MongoDB database, using the L<MongoDB> module. This is a powerful,
fast backend that gives you all the features of MongoDB. This is the only
backend right now that can be used in production environments.
A big advantage of using this backend is that there is no setup work
necessary. Just make sure your MongoDB daemon is running, and this
backend will automatically created the database and necessary collections.
=head1 UNIQUE METHODS
The following method are unique to this backend only.
=head2 new( [host => 'localhost', port => 27017, db_name => 'entities'] )
Creates a new instance of this module. Can receive the hostname of the server
running the MongoDB daemon, the port on thiat host where the daemon is
listening, and the name of the database to use. None of these parameters
is required, host will default to 'localhost', port will default to 27017
(the default MongoDB port) and db_name will default to 'entities'.
=head2 host()
Returns the host name or IP of the MongoDB server.
=cut
has 'host' => (is => 'ro', isa => 'Str', default => 'localhost');
=head2 port()
Returns the port number on the host where the MongoDB server listens.
=cut
has 'port' => (is => 'ro', isa => 'Int', default => 27017);
=head2 db_name()
Returns the name of the database into which all data is saved.
=cut
has 'db_name' => (is => 'ro', isa => 'Str', default => 'entities');
=head2 db( [$db_obj] )
Returns the L<MongoDB::Database> object used for actually storing and
retrieving data. If a MongoDB::Database object is provided, it will replace
the current object.
=cut
has 'db' => (is => 'rw', isa => 'MongoDB::Database');
=head2 to_hash( $obj )
Receives an entity object (either user, action, role, feature, plan or
customer) and turns it into a hash-ref that can be saved in the database.
=cut
sub to_hash {
my ($self, $obj) = @_;
my $hash;
if ($obj->isa('Entities::User')) {
return {
username => $obj->username,
passphrase => $obj->passphrase,
realname => $obj->realname,
is_super => $obj->is_super ? 1 : 0,
created => $obj->created->datetime,
modified => $obj->modified->datetime,
actions => [$obj->_actions],
roles => [$obj->_roles],
emails => [$obj->emails],
customer => $obj->customer ? $obj->customer->name : undef,
};
} elsif ($obj->isa('Entities::Role')) {
return {
name => $obj->name,
desription => $obj->description,
is_super => $obj->is_super ? 1 : 0,
created => $obj->created->datetime,
modified => $obj->modified->datetime,
actions => [$obj->_actions],
roles => [$obj->_roles],
};
} elsif ($obj->isa('Entities::Action') || $obj->isa('Entities::Feature')) {
return {
name => $obj->name,
desription => $obj->description,
created => $obj->created->datetime,
modified => $obj->modified->datetime,
};
} elsif ($obj->isa('Entities::Customer')) {
return {
name => $obj->name,
email_address => $obj->email_address,
created => $obj->created->datetime,
modified => $obj->modified->datetime,
features => [$obj->_features],
plans => [$obj->_plans],
};
} elsif ($obj->isa('Entities::Plan')) {
return {
name => $obj->name,
description => $obj->description,
created => $obj->created->datetime,
modified => $obj->modified->datetime,
features => [$obj->_features],
plans => [$obj->_plans],
};
} else {
croak "Received an object that doesn't belong to the Entities family.";
}
}
=head1 METHODS IMPLEMENTED
The following methods implement the methods that the L<Entities::Backend>
Moose role requires backend classes to implement. See the documentation
of that role for more information on these methods.
=head2 get_user_from_id( $user_id )
=cut
sub get_user_from_id {
my ($self, $id) = @_;
my $user = $self->db->get_collection('users')->find_one({ _id => $id });
return unless $user;
# turn this into an object
return Entities::User->new(id => $user->{_id}, username => $user->{username}, realname => $user->{realname}, customer => $user->{customer} ? $self->get_customer($user->{customer}) : undef, passphrase => $user->{passphrase}, is_super => $user->{is_super}, _roles => $user->{roles}, _actions => $user->{actions}, emails => $user->{emails}, created => DateTime::Format::ISO8601->parse_datetime($user->{created}), modified => DateTime::Format::ISO8601->parse_datetime($user->{modified}), parent => $self);
}
=head2 get_user_from_name( $username )
=cut
sub get_user_from_name {
my ($self, $username) = @_;
my $user = $self->db->get_collection('users')->find_one({ username => $username });
return unless $user;
# turn this into an object
return Entities::User->new(id => $user->{_id}, username => $user->{username}, realname => $user->{realname}, customer => $user->{customer} ? $self->get_customer($user->{customer}) : undef, passphrase => $user->{passphrase}, is_super => $user->{is_super}, _roles => $user->{roles}, _actions => $user->{actions}, emails => $user->{emails}, created => DateTime::Format::ISO8601->parse_datetime($user->{created}), modified => DateTime::Format::ISO8601->parse_datetime($user->{modified}), parent => $self);
}
=head2 get_role( $role_name )
=cut
sub get_role {
my ($self, $name) = @_;
my $role = $self->db->get_collection('roles')->find_one({ name => $name });
return unless $role;
# turn this into an object
$role->{description} ||= '';
return Entities::Role->new(id => $role->{_id}, name => $role->{name}, description => $role->{description}, is_super => $role->{is_super}, _roles => $role->{roles}, _actions => $role->{actions}, created => DateTime::Format::ISO8601->parse_datetime($role->{created}), modified => DateTime::Format::ISO8601->parse_datetime($role->{modified}), parent => $self);
}
=head2 get_customer( $customer_name )
=cut
sub get_customer {
my ($self, $name) = @_;
my $customer = $self->db->get_collection('customers')->find_one({ name => $name });
return unless $customer;
# turn this into an object
return Entities::Customer->new(id => $customer->{_id}, name => $customer->{name}, email_address => $customer->{email_address}, _features => $customer->{features}, _plans => $customer->{plans}, created => DateTime::Format::ISO8601->parse_datetime($customer->{created}), modified => DateTime::Format::ISO8601->parse_datetime($customer->{modified}), parent => $self);
}
=head2 get_plan( $plan_name )
=cut
sub get_plan {
my ($self, $name) = @_;
my $plan = $self->db->get_collection('plans')->find_one({ name => $name });
return unless $plan;
# turn this into an object
$plan->{description} ||= '';
return Entities::Plan->new(id => $plan->{_id}, name => $plan->{name}, description => $plan->{description}, _features => $plan->{features}, _plans => $plan->{plans}, created => DateTime::Format::ISO8601->parse_datetime($plan->{created}), modified => DateTime::Format::ISO8601->parse_datetime($plan->{modified}), parent => $self);
}
=head2 get_feature( $feature_name )
=cut
sub get_feature {
my ($self, $name) = @_;
my $feature = $self->db->get_collection('features')->find_one({ name => $name });
return unless $feature;
$feature->{description} ||= '';
# turn this into an object
return Entities::Feature->new(id => $feature->{_id}, name => $feature->{name}, description => $feature->{description}, created => DateTime::Format::ISO8601->parse_datetime($feature->{created}), modified => DateTime::Format::ISO8601->parse_datetime($feature->{modified}), parent => $self);
}
=head2 get_action( $action_name )
=cut
sub get_action {
my ($self, $name) = @_;
my $action = $self->db->get_collection('actions')->find_one({ name => $name });
return unless $action;
$action->{description} ||= '';
# turn this into an object
return Entities::Action->new(id => $action->{_id}, name => $action->{name}, description => $action->{description}, created => DateTime::Format::ISO8601->parse_datetime($action->{created}), modified => DateTime::Format::ISO8601->parse_datetime($action->{modified}), parent => $self);
}
=head2 save( $obj )
=cut
sub save {
my ($self, $obj) = @_;
my $coll_name = $obj->isa('Entities::User') ? 'users' :
$obj->isa('Entities::Role') ? 'roles' :
$obj->isa('Entities::Action') ? 'actions' :
$obj->isa('Entities::Feature') ? 'features' :
$obj->isa('Entities::Plan') ? 'plans' :
$obj->isa('Entities::Customer') ? 'customers' :
'unknown';
croak "Can't find out the type of object received, it is not a valid Entity"
if $coll_name eq 'unknown';
if ($obj->has_id) {
# we're updating an existing object
croak "Failed updating the object in MongoDB collection $coll_name: ".$self->db->last_error
unless $self->db->get_collection($coll_name)->update({ _id => $obj->id }, $self->to_hash($obj), { safe => 1 });
} else {
# we're storing a new object
my $coll = $self->db->get_collection($coll_name);
if ($coll_name eq 'users') {
$coll->ensure_index({ username => 1 }, { unique => 1 });
$coll->ensure_index({ customer => 1 });
} else {
$coll->ensure_index({ name => 1 }, { unique => 1 });
}
my $id = $coll->insert($self->to_hash($obj), { safe => 1 });
croak "Failed creating the object in MongoDB collection $coll: ".$self->db->last_error
unless $id;
$obj->_set_id($id);
}
return 1;
}
=head1 METHOD MODIFIERS
The following list documents any method modifications performed through
the magic of L<Moose>.
=head2 BUILD()
This method is automatically invoked immediately after the C<new()> method
is invoked. It is used to initiate the connection to the MongoDB database
and store it in the object.
=cut
sub BUILD {
my $self = shift;
my $connection = MongoDB::Connection->new(host => $self->host, port => $self->port);
$self->db($connection->get_database($self->db_name));
}
=head1 SEE ALSO
L<Entities>, L<Entities::Backend>, L<Entities::Backend::Memory>, L<MongoDB>.
=head1 AUTHOR
Ido Perlmuter, C<< <ido at ido50 dot net> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-entities at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Entities>. 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 Entities::Backend::MongoDB
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Entities>
=item * AnnoCPAN: Annotated CPAN documentation
L<http://annocpan.org/dist/Entities>
=item * CPAN Ratings
L<http://cpanratings.perl.org/d/Entities>
=item * Search CPAN
L<http://search.cpan.org/dist/Entities/>
=back
=head1 LICENSE AND COPYRIGHT
Copyright 2010 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;
1;