package Dancer::Session::MongoDB;

# ABSTRACT: MongoDB session backend for Dancer.

our $VERSION = "0.3";
$VERSION = eval $VERSION;

use warnings;
use strict;
use vars '$VERSION';
use base 'Dancer::Session::Abstract';
use MongoDB;
use Dancer::Config 'setting';
use Dancer::ModuleLoader;
use Carp;

# singleton for the MongoDB connection
my ($DB, $COLL);

=head1 NAME

Dancer::Session::MongoDB - MongoDB session backend for Dancer.

=head1 VERSION

version 0.3

=head1 SYNOPSIS

	# in your config.yml file:
	session: "MongoDB"
	mongodb_session_db: "myapp_database"	# required
	mongodb_session_coll: "myapp_sessions"	# optional, defaults to 'sessions'

	# you can pass connection options to MongoDB::Connection::new() by
	# prefixing them with 'mongodb_' like so:
	mongodb_host: "mongo.server.com"	# defaults to 'localhost'
	mongodb_port: 27017			# this is the default
	mongodb_username: "mongodb_user"
	mongodb_password: "secret_password"

	# now you can use sessions in your app as described in L<Dancer::Session>.
	# for example:
	session user => params->{user};
	
	...
	
	if (session('user')) {
		my $msg = "Welcome back, ".session('user');
	}

=head1 DESCRIPTION

This module implements a L<MongoDB> based session engine for L<Dancer>
applications. It keeps session data in a L<MongoDB collection|MongoDB::Collection>,
providing you with a simple, easy to setup, yet powerful session backend.

If you're running your Dancer application in a L<PSGI>/L<Plack> environment,
please consider using L<Plack::Session::Store::MongoDB> with
L<Dancer::Session::PSGI> instead.

This module is a subclass of L<Dancer::Session::Abstract>.

=head1 CONFIGURATION

In order to use this session engine, you need to define a few settings in
your app's settings file (or in your app's code):

=over

=item * session

Give this the value "MongoDB" (using this I<exact> capitalization).
This is required.

=item * mongodb_session_db

Give this the name of the MongoDB database to use. This is required.

=item * mongodb_session_coll

The name of the collection in which session objects will be stored.
Optional, defaults to 'sessions'.

=back

Optionally, you can also define options for initiating the connection to
the MongoDB server, as described in L<MongoDB::Connection/"ATTRIBUTES">,
by prefixing them with 'mongodb_'. For example, if your database requires
authentication, you can pass the 'mongodb_username', 'mongodb_password'
and possibly 'mongodb_db_name' to authenticate. Or, if your database server
is not located on the same server your application resides, then you can
provide 'mongodb_host' and possibly 'mongodb_port'.

=head1 CLASS METHODS

=head2 init()

=cut

sub init {
	my $class = shift;

	# load settings to MongoDB::Connection::new() (if any)
	my %opts;
	foreach (keys %{Dancer::Config::settings()}) {
		next unless m/^mongodb_/;
		next if $_ eq 'mongodb_session_db' || $_ eq 'mongodb_session_coll';
		my $key = $_;
		$key =~ s/^mongodb_//;
		$opts{$key} = setting($_);
	}

	my $db_name = setting('mongodb_session_db')
		|| croak "You must define the name of the MongoDB database for session use in the app's settings (parameter 'mongodb_session_db)'.";
	my $coll_name = setting('mongodb_session_coll') || 'sessions';

	my $conn = MongoDB::Connection->new(%opts);
	$DB = $conn->get_database($db_name);
	$COLL = $DB->get_collection($coll_name);

	# rodrigo: relies on Mongo for a session id
	# optionally could use this: $class->SUPER::init();
	$class->id(''.$COLL->insert({}));
}

=head2 create()

Creates a new session object and returns it.

=cut

sub create {
	$_[0]->new->flush;
}

=head2 retrieve( $id )

Returns the session object whose ID is C<$id> if exists, otherwise returns
a false value.

=cut

sub retrieve($$) {
	my ($class, $id) = @_;

	my $obj = $COLL->find_one({ _id => MongoDB::OID->new(value => $id) }) || return;

	$obj->{id} = ''.delete $obj->{_id};

	return bless $obj, $class;
}

=head1 OBJECT METHODS

=head2 flush()

Writes the session object to the MongoDB database. If a database error
occurs and the object is not saved, this method will die.

=cut

sub flush {
	my $self = shift;

	my %obj = %$self;
	delete $obj{id};

	$COLL->update({ _id => MongoDB::OID->new(value => $self->id) }, \%obj, { safe => 1, upsert => 1 })
		|| croak "Failed writing session to MongoDB database: ".$DB->last_error;

	return $self;
}

=head2 destroy()

Removes the session object from the MongoDB database. If a database error
occurs and the object is not removed, this method will generate a warning.

=cut

sub destroy {
	$COLL->remove({ _id => MongoDB::OID->new(value => $_[0]->id) }, { safe => 1, just_one => 1 })
		|| carp "Failed removing session from MongoDB database: ".$DB->last_error;
}

=head1 AUTHORS

Ido Perlmuter, C<< <ido at ido50 dot net> >>

Rodrigo de Oliveira, C<< <rodrigolive at gmail dot com> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-dancer-session-mongodb at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dancer-Session-MongoDB>. 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 Dancer::Session::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=Dancer-Session-MongoDB>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Dancer-Session-MongoDB>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Dancer-Session-MongoDB>

=item * Search CPAN

L<http://search.cpan.org/dist/Dancer-Session-MongoDB/>

=back

=head1 ACKNOWLEDGEMENTS

Alexis Sukrieh, author of L<Dancer::Session::Memcached>, on which this
module is based.

=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;
