Showing all users In this section, we’ll add the index action, which is designed to display all the users instead of just one. Along the way, we’ll learn how to seed the database with sample users and how to paginate the user output so that the index page can scale up to display a potentially large number of users. In “Deleting users” Section , we’ll add an administrative interface to the users index so that users can also be destroyed.
Users index To get started with the users index, we’ll first implement a security model. Although we’ll keep individual user show
pages visible to all site visitors, the user index
will be restricted to logged-in users so that there’s a limit to how much unregistered users can see by default.
To protect the index
page from unauthorized access, we’ll first add a short test to verify that the index
action is redirected properly.
public/test/e2e_test/controllers/users_controller_test.js
describe ( 'usersControllerTest' , function () {
...
it ( 'should redirect edit when not logged in' , function () {
var current_url = 'http://localhost:1337/#/users/1/edit' ;
browser . get ( current_url );
expect ( element . all ( by . css ( '.alert-danger' )). count () ). toEqual ( 1 );
expect ( browser . getCurrentUrl () ). toContain ( '#/login' );
})
it ( 'should redirect index when not logged in' , function () {
var current_url = 'http://localhost:1337/#/users' ;
browser . get ( current_url );
expect ( element . all ( by . css ( '.alert-danger' )). count () ). toEqual ( 1 );
expect ( browser . getCurrentUrl () ). toContain ( '#/login' );
})
...
});
Then we just need to add an index
action and include it in the list of actions protected by the logged_in_user
before filter.
app/controllers/users_controller.js
var sessionHelper = require ( '../helpers/sessions_helper.js' );
function UsersController () {
this . before_action = [
{ action : 'logged_in_user' , only : [ 'index' , 'update' ] },
{ action : 'correct_user' , only : [ 'update' ] }
];
this . index = function ( req , res , next ) {
};
...
}
module . exports = UsersController ;
To display the users themselves, we need to make a variable containing all the site’s users and then render each one by iterating through them in the index view. As you may recall from the corresponding action in the toy app, we can use User.findAll
to pull all the users out of the database, assigning them to an users
instance variable for use in the view.
app/controllers/users_controller.js
var sessionHelper = require ( '../helpers/sessions_helper.js' );
function UsersController () {
this . before_action = [
{ action : 'logged_in_user' , only : [ 'index' , 'update' ] },
{ action : 'correct_user' , only : [ 'update' ] }
];
this . index = function ( req , res , next ) {
var users = ModelSync ( User . findAll () );
res . end ( JSON . stringify ( users ));
};
...
}
module . exports = UsersController ;
public/services/user.js
var userService = angular . module ( 'userService' , [ 'ngResource' ]);
userService . factory ( 'User' , [ '$resource' , function ( $resource ){
return $resource ( 'users/:id' , { id : '@id' }, {
'get' : { method : 'GET' },
'create' : { method : 'POST' },
'update' : { method : 'PUT' },
'query' : { method : 'GET' , isArray : true },
});
}]);
To make the actual index page, we’ll make a view that iterates through the users and wraps each one in an li
tag.
public/app.js
...
sampleApp . config ([ '$stateProvider' , '$urlRouterProvider' , function ( $stateProvider , $urlRouterProvider ) {
$urlRouterProvider . otherwise ( '/home' );
$stateProvider
...
. state ( 'users_index' , {
url : '/users' ,
templateUrl : 'partials/users/index.html' ,
controller : 'UsersIndexCtrl' ,
data : {
title : 'All users'
},
resolve : {
current_user : current_user ,
users : [ '$q' , '$stateParams' , 'User' , function ( $q , $stateParams , User ){
var deferred = $q . defer ();
User . query ({ id : $stateParams . id }, function ( user ) {
deferred . resolve ( user );
}, function ( error ) {
deferred . reject ();
});
return deferred . promise ;
}]
}
})
}]);
...
public/controllers/users_controller.js
'use strict' ;
var usersController = angular . module ( 'usersController' , []);
....
usersController . controller (
'UsersIndexCtrl' ,
[ '$scope' , 'users' , function ( $scope , users ) {
$scope . users = users ;
}]
);
public/partials/users/index.html
<h1> All users</h1>
<ul class= "users" >
<li ng-repeat= "user in users" >
<img gravatar_for= "{{ user.email }}" alt= "{{ user.name }}" options-size= "50" />
<a href ui-sref= "user_detail({id: user.id})" ui-sref-opts= "{reload: true}" > {{ user.name }}</a>
</li>
</ul>
public/directives/gravatar_for.js
var gravatarForDirective = angular . module ( 'gravatarForDirective' , [ 'angular-md5' ]);
gravatarForDirective . directive ( 'gravatarFor' ,[ 'md5' , function ( md5 ) {
return {
restrict : 'A' ,
link : function ( scope , elem , attrs ) {
var gravatar_id = md5 . createHash ( attrs . gravatarFor . toLowerCase ());
var size = 50 ;
if ( elem . attr ( 'options-size' ))
size = elem . attr ( 'options-size' );
var gravatar_url = "https://secure.gravatar.com/avatar/" + gravatar_id + "?s=" + size ;
elem . attr ( 'src' , gravatar_url );
}
};
}]);
Let’s also add a little CSS for style
public/assets/stylesheets/custom.css
/* Users index */
.users {
list-style : none ;
margin : 0 ;
}
.users li {
overflow : auto ;
padding : 10px 0 ;
border-bottom : 1px solid #eee ;
}
Finally, we’ll add the URL to the users link in the site’s navigation header using users_index
public/partials/layouts/_header.html
<div class= "container" >
<a href ui-sref= "home" ui-sref-opts= "{reload: true}" id= "logo" > sample app</a>
<nav>
<ul class= "nav navbar-nav navbar-right" >
<li><a href ui-sref= "home" ui-sref-opts= "{reload: true}" > Home</a></li>
<li><a href ui-sref= "help" ui-sref-opts= "{reload: true}" > Help</a></li>
<li ng-if= "logged_in" ><a href ui-sref= "users_index" ui-sref-opts= "{reload: true}" > Users</a></li>
...
</ul>
</nav>
</div>
With that, the users index is fully functional, with all tests successful
~/sample_app $ protractor protractor.conf.js
22 specs, 0 failures
Sample users First, we’ll install the Faker, which will allow us to make sample users with semi-realistic names and email addresses
~/sample_app $ npm install --save faker
Next, we’ll add a seed with sample users
db/seeds.js
require ( 'trainjs' ). initServer ();
var faker = require ( 'faker' );
var time = 1 ;
function createUser () {
var name = faker . name . findName ();
var email = 'example-' + time + '@railstutorial.org' ;
var password = 'password' ;
User . create ({ name : name , email : email , password : password , password_confirmation : password }). then ( function () {
if ( time <= 98 ) {
createUser ();
}
});
time ++ ;
}
User . create ({
name : "Example User" ,
email : "[email protected] " ,
password : "123456" ,
password_confirmation : "123456"
}). then ( function () {
User . create ({
name : "Example User" ,
email : "[email protected] " ,
password : "password" ,
password_confirmation : "password"
}). then ( function () {
createUser ();
});
});
We can reset the database and then run seeds.js
~/sample_app $ rm -f db/development.sqlite3
~/sample_app $ sequelize db:migrate
~/sample_app $ node db/seeds.js
Our original user doesn’t suffer from loneliness any more, but now we have the opposite problem: our user has too many companions, and they all appear on the same page. Right now there are a hundred, which is already a reasonably large number, and on a real site it could be thousands. The solution is to paginate the users, so that (for example) only 30 show up on a page at any one time.
~/sample_app $ npm install angular-ui-bootstrap --save
public/index.html
...
<link rel= "stylesheet" href= "assets/stylesheets/custom.css" >
<link rel= "stylesheet" href= "../node_modules/angular-ui-bootstrap/dist/ui-bootstrap-csp.css" >
...
<script src= "../node_modules/angular-form-for/dist/form-for.bootstrap-templates.js" ></script>
<script src= "../node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js" ></script>
<script src= "../node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js" ></script>
...
<script src= "directives/pagination.js" ></script>
...
public/directives/pagination.js
var paginationDirective = angular . module ( 'paginationDirective' , []);
paginationDirective . directive ( 'uibPagination' , [ '$state' , '$stateParams' , function ( $state , $stateParams ) {
return {
restrict : 'E' ,
link : function ( scope , elem , attrs ) {
scope . currentPage = $stateParams . page ;
scope . itemsPerPage = $stateParams . limit ;
scope . pageChanged = function () {
$stateParams . page = scope . currentPage ;
$state . transitionTo ( $state . current , $stateParams , {
reload : true , inherit : false , notify : true
});
};
}
};
}]);
public/app.js
...
var sampleApp = angular . module ( 'sampleApp' , [
'ui.router' ,
'ui.bootstrap' ,
...
'paginationDirective' ,
...
]);
...
sampleApp . config ([ '$stateProvider' , '$urlRouterProvider' , function ( $stateProvider , $urlRouterProvider ) {
...
. state ( 'users_index' , {
url : '/users?page&limit' ,
templateUrl : 'partials/users/index.html' ,
controller : 'UsersIndexCtrl' ,
data : {
title : 'All users'
},
resolve : {
user : current_user ,
users : [ '$q' , '$stateParams' , 'User' , function ( $q , $stateParams , User ){
$stateParams . page = $stateParams . page ? $stateParams . page : 1 ;
$stateParams . limit = $stateParams . limit ? $stateParams . limit : 30 ;
var deferred = $q . defer ();
User . query ({ page : $stateParams . page , limit : $stateParams . limit }, function ( user ) {
deferred . resolve ( user );
}, function ( error ) {
deferred . reject ();
});
return deferred . promise ;
}]
}
})
}]);
...
public/partials/users/index.html
<h1> All users</h1>
<uib-pagination total-items= "totalItems" ng-model= "currentPage" ng-change= "pageChanged()" max-size= "5" class= "pagination-sm" boundary-link-numbers= "true" rotate= "false" items-per-page= "itemsPerPage" ></uib-pagination>
<ul class= "users" >
<li ng-repeat= "user in users" >
<img gravatar_for= "{{ user.email }}" alt= "{{ user.name }}" options-size= "50" />
<a href ui-sref= "user_detail({id: user.id})" ui-sref-opts= "{reload: true}" > {{ user.name }}</a>
</li>
</ul>
<uib-pagination total-items= "totalItems" ng-model= "currentPage" ng-change= "pageChanged()" max-size= "5" class= "pagination-sm" boundary-link-numbers= "true" rotate= "false" items-per-page= "itemsPerPage" ></uib-pagination>
public/controllers/users_controller.js
...
usersController . controller (
'UsersIndexCtrl' ,
[ '$scope' , 'users' , '$state' , '$stateParams' , function ( $scope , users , $state , $stateParams ) {
$scope . users = users . rows ;
$scope . totalItems = users . count ;
}]
);
public/services/user.js
var userService = angular . module ( 'userService' , [ 'ngResource' ]);
userService . factory ( 'User' , [ '$resource' , function ( $resource ){
return $resource ( 'users/:id' , { id : '@id' }, {
'get' : { method : 'GET' },
'create' : { method : 'POST' },
'update' : { method : 'PUT' },
'query' : { method : 'GET' , isArray : false },
});
}]);
app/controllers/users_controller.js
var sessionHelper = require ( '../helpers/sessions_helper.js' );
function UsersController () {
this . before_action = [
{ action : 'logged_in_user' , only : [ 'index' , 'update' ] },
{ action : 'correct_user' , only : [ 'update' ] }
];
this . index = function ( req , res , next ) {
var offset = ( req . query . page - 1 ) * req . query . limit ;
var users = ModelSync ( User . findAndCountAll ({ offset : offset , limit : req . query . limit }) );
res . end ( JSON . stringify ( users ));
};
...
}
module . exports = UsersController ;
Users index test Now that our users index page is working, we’ll write a lightweight test for it
public/test/e2e_test/integration/users_index_test.js
describe ( 'UsersIndexTest' , function () {
it ( 'index including pagination' , function () {
var current_url = 'http://localhost:1337/#/login' ;
browser . get ( current_url );
element ( by . css ( '[name="email"]' )). clear ( '' );
element ( by . css ( '[name="password"]' )). clear ( '' );
element ( by . css ( '[name="email"]' )). sendKeys ( '[email protected] ' );
element ( by . css ( '[name="password"]' )). sendKeys ( '123456' );
element ( by . css ( '[name="commit"]' )). click ();
expect ( browser . getCurrentUrl ()). toContain ( '#/users/' );
browser . get ( 'http://localhost:1337/#/users' );
expect ( element . all ( by . css ( '.pagination-page' )). count () ). toBeGreaterThan ( 0 );
})
})
The result should be a successful test suite
~/sample_app $ protractor protractor.conf.js
23 specs, 0 failures