Patch for situations when you broke DBIC, to get clearer error reporting behaviour
[catagits/Catalyst-Authentication-Store-DBIx-Class.git] / lib / Catalyst / Authentication / Store / DBIx / Class / User.pm
index 8a7936c..4e5319c 100644 (file)
@@ -1,21 +1,22 @@
 package Catalyst::Authentication::Store::DBIx::Class::User;
 
-use strict;
-use warnings;
-use List::MoreUtils qw(all);
-use base qw/Catalyst::Authentication::User/;
-use base qw/Class::Accessor::Fast/;
-
-BEGIN {
-    __PACKAGE__->mk_accessors(qw/config resultset _user _roles/);
-}
+use Moose;
+use namespace::autoclean;
+extends 'Catalyst::Authentication::User';
+
+use List::MoreUtils 'all';
+use Try::Tiny;
+
+has 'config'    => (is => 'rw');
+has 'resultset' => (is => 'rw');
+has '_user'     => (is => 'rw');
+has '_roles'    => (is => 'rw');
 
 sub new {
     my ( $class, $config, $c) = @_;
 
-       if (!defined($config->{'user_model'})) {
-               $config->{'user_model'} = $config->{'user_class'};
-       }
+       $config->{user_model} = $config->{user_class}
+        unless defined $config->{user_model};
 
     my $self = {
         resultset => $c->model($config->{'user_model'}),
@@ -26,11 +27,10 @@ sub new {
 
     bless $self, $class;
 
-
-
-    if (not $self->{'resultset'}) {
-        Catalyst::Exception->throw("\$c->model('${ \$self->config->{user_model} }') did not return a resultset. Did you set user_model correctly?");
-    }
+    Catalyst::Exception->throw(
+        "\$c->model('${ \$self->config->{user_model} }') did not return a resultset."
+          . " Did you set user_model correctly?"
+    ) unless $self->{resultset};
 
     $self->config->{'id_field'} = [$self->{'resultset'}->result_source->primary_columns]
         unless exists $self->config->{'id_field'};
@@ -38,8 +38,11 @@ sub new {
     $self->config->{'id_field'} = [$self->config->{'id_field'}]
         unless ref $self->config->{'id_field'} eq 'ARRAY';
 
-    Catalyst::Exception->throw("id_field set to " . join(q{,} => @{ $self->config->{'id_field'} }) . " but user table has no column by that name!")
-        unless all { $self->{'resultset'}->result_source->has_column($_) } @{ $self->config->{'id_field'} };
+    Catalyst::Exception->throw(
+        "id_field set to "
+          . join(q{,} => @{ $self->config->{'id_field'} })
+          . " but user table has no column by that name!"
+    ) unless all { $self->{'resultset'}->result_source->has_column($_) } @{ $self->config->{'id_field'} };
 
     ## if we have lazyloading turned on - we should not query the DB unless something gets read.
     ## that's the idea anyway - still have to work out how to manage that - so for now we always force
@@ -71,7 +74,9 @@ sub load {
     ## User can provide an arrayref containing the arguments to search on the user class.
     ## or even provide a prepared resultset, allowing maximum flexibility for user retreival.
     ## these options are only available when using the dbix_class authinfo hash.
-    if ($dbix_class_config && exists($authinfo->{'resultset'})) {
+    if ($dbix_class_config && exists($authinfo->{'result'})) {
+       $self->_user($authinfo->{'result'});
+    } elsif ($dbix_class_config && exists($authinfo->{'resultset'})) {
         $self->_user($authinfo->{'resultset'}->first);
     } elsif ($dbix_class_config && exists($authinfo->{'searchargs'})) {
         $self->_user($self->resultset->search(@{$authinfo->{'searchargs'}})->first);
@@ -89,7 +94,12 @@ sub load {
         if (keys %{$searchargs}) {
             $self->_user($self->resultset->search($searchargs)->first);
         } else {
-            Catalyst::Exception->throw("Failed to load user data.  You passed [" . join(',', keys %{$authinfo}) . "] to authenticate() but your user source (" .  $self->config->{'user_model'} . ") only has these columns: [" . join( ",", $self->resultset->result_source->columns ) . "]   Check your authenticate() call.");
+            Catalyst::Exception->throw(
+                "Failed to load user data.  You passed [" . join(',', keys %{$authinfo}) . "]"
+                  . " to authenticate() but your user source (" .  $self->config->{'user_model'} . ")"
+                  . " only has these columns: [" . join( ",", $self->resultset->result_source->columns ) . "]"
+                  . "   Check your authenticate() call."
+            );
         }
     }
 
@@ -130,7 +140,11 @@ sub roles {
     } elsif (exists($self->config->{'role_relation'})) {
         my $relation = $self->config->{'role_relation'};
         if ($self->_user->$relation->result_source->has_column($self->config->{'role_field'})) {
-            @roles = map { $_->get_column($self->config->{'role_field'}) } $self->_user->$relation->search(undef, { columns => [ $self->config->{'role_field'}]})->all();
+            @roles = map {
+                $_->get_column($self->config->{role_field})
+            } $self->_user->$relation->search(undef, {
+                columns => [ $self->config->{role_field} ]
+            })->all;
             $self->_roles(\@roles);
         } else {
             Catalyst::Exception->throw("role table does not have a column called " . $self->config->{'role_field'});
@@ -151,7 +165,14 @@ sub for_session {
     #return $frozenuser;
 
     my %userdata = $self->_user->get_columns();
-    return \%userdata;
+
+    # If use_userdata_from_session is set, then store all of the columns of the user obj in the session
+    if (exists($self->config->{'use_userdata_from_session'}) && $self->config->{'use_userdata_from_session'} != 0) {
+        return \%userdata;
+    } else { # Otherwise, we just need the PKs for load() to use.
+        my %pk_fields = map { ($_ => $userdata{$_}) } @{ $self->config->{id_field} };
+        return \%pk_fields;
+    }
 }
 
 sub from_session {
@@ -168,7 +189,17 @@ sub from_session {
 #
 ## if use_userdata_from_session is defined in the config, we fill in the user data from the session.
     if (exists($self->config->{'use_userdata_from_session'}) && $self->config->{'use_userdata_from_session'} != 0) {
-        my $obj = $self->resultset->new_result({ %$frozenuser });
+
+        # We need to use inflate_result here since we -are- inflating a
+        # result object from cached data, not creating a fresh one.
+        # Components such as EncodedColumn wrap new() to ensure that a
+        # provided password is hashed on the way in, and re-running the
+        # hash function on data being restored is expensive and incorrect.
+
+        my $class = $self->resultset->result_class;
+        my $source = $self->resultset->result_source;
+        my $obj = $class->inflate_result($source, { %$frozenuser });
+
         $obj->in_storage(1);
         $self->_user($obj);
         return $self;
@@ -178,7 +209,7 @@ sub from_session {
         return $self->load({
             map { ($_ => $frozenuser->{$_}) }
             @{ $self->config->{id_field} }
-        });
+        }, $c);
     }
 
     return $self->load( { $self->config->{'id_field'} => $frozenuser }, $c);
@@ -187,9 +218,14 @@ sub from_session {
 sub get {
     my ($self, $field) = @_;
 
-    if ($self->_user->can($field)) {
-        return $self->_user->$field;
+    if (my $code = $self->_user->can($field)) {
+        return $self->_user->$code;
+    }
+    elsif (my $accessor =
+         try { $self->_user->result_source->column_info($field)->{accessor} }) {
+        return $self->_user->$accessor;
     } else {
+        # XXX this should probably throw
         return undef;
     }
 }
@@ -221,14 +257,42 @@ sub auto_update {
     $self->_user->auto_update( @_ );
 }
 
+sub can {
+    my $self = shift;
+    return $self->SUPER::can(@_) || do {
+        my ($method) = @_;
+        if (not $self->_user) {
+            undef;
+        } elsif (my $code = $self->_user->can($method)) {
+            sub { shift->_user->$code(@_) }
+        } elsif (my $accessor =
+            try { $self->_user->result_source->column_info($method)->{accessor} }) {
+            sub { shift->_user->$accessor }
+        } else {
+            undef;
+        }
+    };
+}
+
 sub AUTOLOAD {
     my $self = shift;
     (my $method) = (our $AUTOLOAD =~ /([^:]+)$/);
     return if $method eq "DESTROY";
 
-    $self->_user->$method(@_);
+    if (my $code = $self->_user->can($method)) {
+        return $self->_user->$code(@_);
+    }
+    elsif (my $accessor =
+         try { $self->_user->result_source->column_info($method)->{accessor} }) {
+        return $self->_user->$accessor(@_);
+    } else {
+        # XXX this should also throw
+        return undef;
+    }
 }
 
+__PACKAGE__->meta->make_immutable(inline_constructor => 0);
+
 1;
 __END__
 
@@ -240,7 +304,7 @@ module.
 
 =head1 VERSION
 
-This documentation refers to version 0.10.
+This documentation refers to version 0.1503.
 
 =head1 SYNOPSIS
 
@@ -318,6 +382,14 @@ By default, auto_update will call the C<auto_update()> method of the
 DBIx::Class::Row object associated with the user. It is up to you to implement
 that method (probably in your schema file)
 
+=head2 AUTOLOAD
+
+Delegates method calls to the underlieing user row.
+
+=head2 can
+
+Delegates handling of the C<< can >> method to the underlieing user row.
+
 =head1 BUGS AND LIMITATIONS
 
 None known currently, please email the author if you find any.
@@ -326,9 +398,15 @@ None known currently, please email the author if you find any.
 
 Jason Kuri (jayk@cpan.org)
 
+=head1 CONTRIBUTORS
+
+Matt S Trout (mst) <mst@shadowcat.co.uk>
+
+(fixes wrt can/AUTOLOAD sponsored by L<http://reask.com/>)
+
 =head1 LICENSE
 
-Copyright (c) 2007 the aforementioned authors. All rights
+Copyright (c) 2007-2010 the aforementioned authors. All rights
 reserved. This program is free software; you can redistribute
 it and/or modify it under the same terms as Perl itself.