Hi,
I am working through the chapter in the book to learn about many to many
relationship bridges.
I have made it through the chapter up to the last part where it has us list
all users and their roles. Page 165 in Chapter 6.
This is the template file root/authusers/list.tt
<html>
<head>
<title>All users and their roles</title>
</head>
<body>
<table>
<tr><th>UserID</th><th>
Username</th><th>eMail</th><th>Roles</th></tr>
[% WHILE (user = users_rs.next) %]
<tr>
<td>[% user.id %]</td>
<td>[% user.username %]</td>
<td>[% user.email %]</td>
<td>
<ul>
[% FOREACH role = user.user_roles %]
<li>[% role.role_id.role %]</li>
[% END %]
</ul>
</td>
</tr>
[% END %]
</table>
</body>
</html>
If I remove the last "role" in the FOREACH loop it will list the id for the
roles. It fails to list the text associated with the roles and I can't
figure out what is going wrong. The only major difference between the book
and what has happened for me locally is that DBIx::Class::Schema::Loader
(version 0.07025 ) created the schema results as
DBAuthTest$ ls lib/Auth/Schema/Result/
Role.pm User.pm UserRole.pm
Not the plurals as in the book (Roles.pm, User.pm and UserRoles.pm). I have
been trying to track these names and keep it consistent with what I am
doing as opposed to the book instructions and so far I have worked through
it.
The result gives the <li> dots but no values. So it is counting them
correctly but not retrieving the values. I am stumped on this and any help
at all would be greatly appreciated.
The other relevant files are:
======== The Controller =========
lib/DBAuthTest/Controller/AuthUsers.pm
package DBAuthTest::Controller::AuthUsers;
use Moose;
use namespace::autoclean;
BEGIN {extends 'Catalyst::Controller'; }
=head1 NAME
DBAuthTest::Controller::AuthUsers - Catalyst Controller
=head1 DESCRIPTION
Catalyst Controller.
=head1 METHODS
=cut
=head2 index
=cut
sub base : Chained('/'): PathPart('authusers'): CaptureArgs(0) {
my ( $self, $c ) = @_;
$c->stash(users_rs => $c->model('AuthDB::User'));
$c->stash(roles_rs => $c->model('AuthDB::Role'));
}
sub add : Chained('base'): PathPart('add'): Args(0) {
my ( $self, $c ) = @_;
if(lc $c->req->method eq 'post') {
my $params = $c->req->params;
## Retrieve the users_rs stashed by the base action:
my $users_rs = $c->stash->{users_rs};
## Create the user:
=head2 Original Code
- keep for now as I don't trust the code below.
my $newuser = $users_rs->create({
username => $params->{username},
email => $params->{email},
password => $params->{password},
});
=cut
=head2 Catching Errors
- No Workiee, not in their code either.
=cut
my $newuser = eval { $users_rs->create({
username => $params->{username},
email => $params->{email},
password => $params->{password},
}) };
if($@) {
$c->log->debug(
"User tried to sign up with an invalid email address, redoing...");
$c->stash( errors => { email => 'invalid' }, err => $@ );
return;
}
return $c->res->redirect( $c->uri_for(
$c->controller('AuthUsers')->action_for('profile'),
[ $newuser->id ]
) );
}
}
sub user : Chained('base'): PathPart(''): CaptureArgs(1) {
my ($self, $c, $userid) = @_;
my $user = $c->stash->{users_rs}->find({ id => $userid },{ key =>
'primary' });
die "No such user" if(!$user);
$c->stash(user => $user);
}
sub profile : Chained('user') :PathPart('profile'): Args(0) {
my ($self, $c) = @_;
}
sub edit : Chained('user') :PathPart('edit'): Args(0) {
my ($self, $c) = @_;
if(lc $c->req->method eq 'post') {
my $params = $c->req->params;
my $user = $c->stash->{user};
## Check user is allowed to update this profile
#if($c->user->object->id != $user->id) {
# die "Malicious attempt to update another user by: ".
$c->user->username;
#}
## Update user's email and/or password
$user->update({
email => $params->{email},
password => $params->{password},
});
## Send the user back to the changed profile
return $c->res->redirect( $c->uri_for(
$c->controller('AuthUsers')->action_for('profile'), [ $user->id ] ) );
}
}
=head2 Original
sub set_roles :Chained('user'): PathPart('set_roles'): Args() {
my ($self, $c) = @_;
my $user = $c->stash->{user};
if(lc $c->req->method eq 'post') {
## Fetch all role ids submitted as a list
my @roles = $c->req->param('role');
## Remove any existing roles, we're replacing them:
$user->user_roles->delete;
## Add new roles:
foreach my $role_id (@roles) {
$user->user_roles->create({ role_id => $role_id });
}
}
$c->res->redirect($c->uri_for($c->controller()->action_for('profile'),[
$user->id ] ));
}
=cut
sub set_roles :Chained('user'): PathPart('set_roles'): Args() {
my ($self, $c) = @_;
my $user = $c->stash->{user};
if(lc $c->req->method eq 'post') {
## Fetch all role ids submitted as a list
my @roles = $c->req->param('role');
$user->set_all_roles(@roles);
}
$c->res->redirect($c->uri_for($c->controller()->action_for('profile'),
[ $user->id ] ));
}
sub delete :Chained('user'): PatPart('delete'): Args() {
my ($self, $c) = @_;
my $user = $c->stash->{user};
$user->delete();
return $c->res->redirect( $c->uri_for('/') );
}
sub list : Chained('base'): PathPart('list'): Args(0) {
my ($self, $c) = @_;
}
__PACKAGE__->meta->make_immutable;
1;
------- The Result Classes ------
DBAuthTest$ ls lib/Auth/Schema/Result/
Role.pm User.pm UserRole.pm
======== User.pm =============
use utf8;
package Auth::Schema::Result::User;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Auth::Schema::Result::User
=cut
use strict;
use warnings;
use Moose;
use MooseX::NonMoose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Core';
=head1 COMPONENTS LOADED
=over 4
=item * L<DBIx::Class::InflateColumn::DateTime>
=item * L<DBIx::Class::TimeStamp>
=back
=cut
__PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp");
=head1 TABLE: C<users>
=cut
__PACKAGE__->table("users");
=head1 ACCESSORS
=head2 id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
=head2 username
data_type: 'text'
is_nullable: 1
=head2 email
data_type: 'text'
is_nullable: 1
=head2 password
data_type: 'text'
is_nullable: 1
=head2 last_modified
data_type: 'datetime'
is_nullable: 1
=cut
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"username",
{ data_type => "text", is_nullable => 1 },
"email",
{ data_type => "text", is_nullable => 1 },
"password",
{ data_type => "text", is_nullable => 1 },
"last_modified",
{ data_type => "datetime", is_nullable => 1 },
);
=head1 PRIMARY KEY
=over 4
=item * L</id>
=back
=cut
__PACKAGE__->set_primary_key("id");
=head1 UNIQUE CONSTRAINTS
=head2 C<username_unique>
=over 4
=item * L</username>
=back
=cut
__PACKAGE__->add_unique_constraint("username_unique", ["username"]);
=head1 RELATIONS
=head2 user_roles
Type: has_many
Related object: L<Auth::Schema::Result::UserRole>
=cut
__PACKAGE__->has_many(
"user_roles",
"Auth::Schema::Result::UserRole",
{ "foreign.user_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 roles
Type: many_to_many
Composing rels: L</user_roles> -> role
=cut
__PACKAGE__->many_to_many("roles", "user_roles", "role");
# Created by DBIx::Class::Schema::Loader v0.07025 @ 2012-07-09 00:18:52
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:6svl+CkzndehZ8+Zp5yXhw
# You can replace this text with custom code or comments, and it will be
preserved on regeneration
__PACKAGE__->meta->make_immutable;
__PACKAGE__->add_columns('last_modified',
{ %{__PACKAGE__->column_info('last_modified') },
set_on_create => 1,
set_on_update => 1
});
use Email::Valid;
sub new {
my ($class, $args)=@_;
if( exists $args->{email} && !Email::Valid->address($args->{email}) ) {
die 'Email invalid';
}
return $class->next::method($args);
}
sub has_role {
my ($self, $role) = @_;
## $role is a row object for a role
my $roles = $self->user_roles->find({ role_id => $role->id });
return $roles;
}
sub set_all_roles {
my ($self, @roleids) = @_;
## Remove any existing roles, we're replacing them:
$self->user_roles->delete;
## Add new roles:
foreach my $role_id (@roleids) {
$self->user_roles->create({ role_id => $role_id });
}
return $self;
}
1;
======== UserRole.pm =============
use utf8;
package Auth::Schema::Result::UserRole;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Auth::Schema::Result::UserRole
=cut
use strict;
use warnings;
use Moose;
use MooseX::NonMoose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Core';
=head1 COMPONENTS LOADED
=over 4
=item * L<DBIx::Class::InflateColumn::DateTime>
=item * L<DBIx::Class::TimeStamp>
=back
=cut
__PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp");
=head1 TABLE: C<user_roles>
=cut
__PACKAGE__->table("user_roles");
=head1 ACCESSORS
=head2 user_id
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
=head2 role_id
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
=cut
__PACKAGE__->add_columns(
"user_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"role_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
);
=head1 PRIMARY KEY
=over 4
=item * L</user_id>
=item * L</role_id>
=back
=cut
__PACKAGE__->set_primary_key("user_id", "role_id");
=head1 RELATIONS
=head2 role
Type: belongs_to
Related object: L<Auth::Schema::Result::Role>
=cut
__PACKAGE__->belongs_to(
"role",
"Auth::Schema::Result::Role",
{ id => "role_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
=head2 user
Type: belongs_to
Related object: L<Auth::Schema::Result::User>
=cut
__PACKAGE__->belongs_to(
"user",
"Auth::Schema::Result::User",
{ id => "user_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
# Created by DBIx::Class::Schema::Loader v0.07025 @ 2012-07-09 00:18:52
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:0RYpPqJtXPb7IMPYjImDng
# You can replace this text with custom code or comments, and it will be
preserved on regeneration
__PACKAGE__->meta->make_immutable;
1;
======== Role.pm =============
use utf8;
package Auth::Schema::Result::Role;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Auth::Schema::Result::Role
=cut
use strict;
use warnings;
use Moose;
use MooseX::NonMoose;
use MooseX::MarkAsMethods autoclean => 1;
extends 'DBIx::Class::Core';
=head1 COMPONENTS LOADED
=over 4
=item * L<DBIx::Class::InflateColumn::DateTime>
=item * L<DBIx::Class::TimeStamp>
=back
=cut
__PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp");
=head1 TABLE: C<roles>
=cut
__PACKAGE__->table("roles");
=head1 ACCESSORS
=head2 id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
=head2 role
data_type: 'text'
is_nullable: 1
=cut
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
"role",
{ data_type => "text", is_nullable => 1 },
);
=head1 PRIMARY KEY
=over 4
=item * L</id>
=back
=cut
__PACKAGE__->set_primary_key("id");
=head1 UNIQUE CONSTRAINTS
=head2 C<role_unique>
=over 4
=item * L</role>
=back
=cut
__PACKAGE__->add_unique_constraint("role_unique", ["role"]);
=head1 RELATIONS
=head2 user_roles
Type: has_many
Related object: L<Auth::Schema::Result::UserRole>
=cut
__PACKAGE__->has_many(
"user_roles",
"Auth::Schema::Result::UserRole",
{ "foreign.role_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 users
Type: many_to_many
Composing rels: L</user_roles> -> user
=cut
__PACKAGE__->many_to_many("users", "user_roles", "user");
# Created by DBIx::Class::Schema::Loader v0.07025 @ 2012-07-09 00:18:52
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:a8Hd9uGBQmPWRsNQd8WR6Q
# You can replace this text with custom code or comments, and it will be
preserved on regeneration
__PACKAGE__->meta->make_immutable;
1;
_______________________________________________
List: [email protected]
Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
Searchable archive: http://www.mail-archive.com/[email protected]/
Dev site: http://dev.catalyst.perl.org/