Tutorial Sails


NOTA: Este tutorial asume que ya ha instalado Node.js y Sails.

Creación del proyecto y vista índice

Lo primero que haremos será crear un proyecto, al que llamaremos ekembi. Para ello iremos a la consola, nos posicionaremos donde cada uno crea conveniente crear su proyecto y ejecutaremos el siguiente comando en la consola:

~/Desktop $ sails new ekembi

Ahora, accedemos a la carpeta del proyecto y arrancamos el servidor con:

~/ekembi $ sails lift

Como vemos en el navegador, se nos abre la vista wellcome que se crea por defecto. Una vez abierto el proyecto con Sublime Text, para cambiar esto lo que haremos será lo siguiente:

Si abrimos el fichero layout.ejs, que está dentro de la carpeta views, veremos que hay una línea que pone: <%- body -%>. Esto lo que hace es cargar en layout.ejs lo que hay en 'static/index' y en los diferentes ficheros .ejs que iremos creando a lo largo del tutorial.

Además, si añadimos archivos css dentro de la carpeta assets/styles y ficheros javascript dentro de la carpeta assets/js; cuando hacemos esto el servidor los añade automaticamente al fichero, sin necesidad de hacerlo manualmente.

Ahora descargaremos los ficheros bootstrap necesarios para añadirlos en sus correspondientes carpetas para tener nuestro index más atractivo.

Aquí puede descargar los ficheros que debe añadir en assets/js y aquí puede descargar los ficheros que debe añadir en assets/styles.

Una vez hecho todo esto, añadiremos el siguiente código dentro del fichero index.ejs de la carpeta static:

 
    < div class="container">
      < form class="form-signin">
        < h2 class="form-signin-heading">Please sign in
        < label for="inputEmail" class="sr-only">Email address
        < input type="email" id="inputEmail" class="form-control" placeholder="Email address" name="email" required autofocus>
        < label for="inputPassword" class="sr-only">Password
        < input type="password" id="inputPassword" class="form-control" placeholder="Password" name="password" required>
        < div class="checkbox">
          < label>
            < input type="checkbox" value="remember-me"> Remember me
          < /label>
        < /div>
        < button class="btn btn-lg btn-primary btn-block" type="submit">Sign in
        < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
      < /form>
    < /div> < !-- /container -->

Finalmente, dentro del fichero layout.ejs, insertaremos el siguiente código entre la cabecera de body y <%- body %>


      < nav class="navbar navbar-inverse navbar-fixed-top">
      < div class="container">
        < div class="navbar-header">
          < button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            < span class="sr-only">Toggle navigation< /span>
            < span class="icon-bar">< /span>
            < span class="icon-bar">< /span>
            < span class="icon-bar">< /span>
          < /button>
          < a class="navbar-brand" href="http://localhost:1337/">Ekembi< /a>
          < a class="navbar-brand" href="/user/create/">Crear Usuario< /a>
        < /div>
        < div id="navbar" class="navbar-collapse collapse">
          < form action="" class="navbar-form navbar-right">
            < div class="form-group">
              < input type="text" placeholder="Email" name="email" class="form-control">
            < /div>
            < div class="form-group">
              < input type="password" placeholder="Password" name="password" class="form-control">
            < /div>
            < button type="submit" class="btn btn-success">Sign in< /button>
            < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
          < /form>
        < /div>
      < /div>
    < /nav>

Su fichero layout.ejs debería quedar de la siguiente manera.

Cuando creemos el controlador y el modelo de user, modificaremos los forms para que tras pulsar el botón podamos autenticarnos.

Con esto tenemos la página index terminada y el código introducido en layout.ejs nos mostrará siempre la barra para autenticarnos y que luego modificaremos cuando estemos autenticados.


Creación del Modelo Usuario y Autenticación

Lo primero que vamos a hacer es crear el modelo y el controlador, para ello ejecutaremos la siguiente sentencia en la consola:

~/ekembi $ sails generate api user 

Ahora, en el modelo escribiremos los siguiente:


      /**
        * User.js
        *
        * @description :: TODO: You might write a short summary of how this model works and what it represents here.
        * @docs        :: http://sailsjs.org/#!documentation/models
        */

        module.exports = {

          attributes: {

            name: {
              type: 'string',
              required: true
            },
            lastname: {
              type: 'string',
              required: true
            },
            email: {
              type: 'string',
              required: true,
              unique: true
            },
            encryptedPassword: {
              type: 'string'
            },

            // Este método es para evitar pasar toda la información del modelo
            // Evitamos pasar los siguientes parámetros: password, confirmation, encryptedpassword y _csrf. 
            toJSON: function() { 
              var obj = this.toObject();
              delete obj.password;
              delete obj.confirmation;
              delete obj.encryptedPassword;
              delete obj._csrf;
              return obj;
            }
          }
        };

Después iremos a views y crearemos la carpeta user y dentro de dicha carpeta los ficheros index.ejs:


      < div class="container">
      < h3>Usuarios
      < table class='table'>
          < tr>
              < th>ID
              < th>Nombre
              < th>Apellidos
              < th>Email
              < th>
              < th>
              < th>
          < /tr>
          <% _.each(users, function(user) { %>
          < tr data-id="<%= user.id %>" data-model="user">
              < td><%= user.id %>
              < td><%= user.name %>
              < td><%= user.lastname %>
              < td><%= user.email %>
              < td>< a href="/user/show/<%= user.id %>" class="btn btn-sm btn-primary">Show
              < td>< a href="/user/edit/<%= user.id %>" class="btn btn-sm btn-warning">Edit
            < td>< form action="/user/destroy/<%= user.id %>" method="POST"> 
                      < input type="hidden" name="_method" value="delete"/>
                      < input type="submit" class="btn btn-sm btn-danger" value="Delete"/>
                      < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                  < /form>
              < /td>
          < /tr>
          <% }) %>
      < /table>
      < a href="/user/new">Crear Nuevo Usuario
      < /div>

Como podemos ver lo que hacemos es crear una tabla donde mostraremos la información de cada usuario creado.

Respecto al código podemos ver la siguiente línea: <% _.each(user, function(user) { %>

Lo que hace es recorrer cada uno de los usuarios (users) y llama a la funcion function(user) donde le pasa el user.id buscando dentro del modelo mediante data-id pasandole el nombre del modelo: data-model="user".


        <% _.each(users, function(user) { %>
        < tr data-id="<%= user.id %>" data-model="user">
            < td><%= user.id %>
            < td><%= user.name %>
            < td><%= user.lastname %>
            < td><%= user.email %>
            < td>< a href="/user/show/<%= user.id %>" class="btn btn-sm btn-primary">Show
            < td>< a href="/user/edit/<%= user.id %>" class="btn btn-sm btn-warning">Edit
            < td>< form action="/user/destroy/<%= user.id %>" method="POST"> 
                  < input type="hidden" name="_method" value="delete"/>
                  < input type="submit" class="btn btn-sm btn-danger" value="Delete"/>
                  < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                < /form>
              < /td>
          < /tr>
        <% }) %>

Y ahora el fichero new.ejs:


          < form action="/user/create" method="POST" class="form-signin">
            < h2 class="form-signin-heading"> Crear Usuario 
            < div class="control-group"> 
            < input type="text" class="form-control" placeholder="Nombre" name="name">
            < /div>
            < div class="control-group">
                  < input type="text" class="form-control" placeholder="Apellidos" name="lastname">
                  < /div>
                  < div class="control-group">
                  < input type="text" class="form-control" placeholder="Email" name="email">
                  < /div>
                  < div class="control-group">
                  < input type="password" class="form-control" placeholder="Contraseña" name="password">
                  < /div>
                  < div class="control-group">
                  < input type="password" class="form-control" placeholder="Repita Contraseña" name="confirmation">
                  < /div>
            < br/>
                  < input type="submit" class="btn btn-lg btn-primary btn-block" value="Create User"/>
                  < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
          < /form>

Ahora vamos al controlador y cremos los métodos para dichas vistas. Los nombres serán los que están en el action.


          /**
           * UserController
           *
           * @description :: Server-side logic for managing users
           * @help        :: See http://links.sailsjs.org/docs/controllers
           */

          module.exports = {
            'new': function (req, res) {
              res.view();
            },
            index: function (req, res) {
              User.find(function foundUser (err, users) {
                if(err) return res.redirect('/user/new');
                res.view({
                  users: users
                });
              });
            },
            create: function(req, res) {
                User.create(req.params.all(), function userCreated(err, user) {
                  if (err) return res.redirect('/user/new');
                  res.redirect('/user/show/' + user.id); 
                });
              }
          };

Finalmente, accedemos al archivo routes.js y añadimos una nueva ruta para que cuando accedamos a cualquier vista de user no redireccione a 403, sino que permita renderizar las vistas. Para ello añadiremos el siguiente código debajo de la ruta raíz con una coma (,).


          module.exports.policies = {

            // '*': true,

            'user': {
              'new': true,
              index: 'sessionAuth',
              '*': true
            }
          };

Que significa esto, pues vamos a explicar:


Reducir información de la cuenta y validación de errores con mensajes flash

Si intentamos crear un usuario y no rellenamos los parametros requerido se producirá un error. Para ello, en este tutorial, vamos a manejar los errores mediante JQUERY, pero siempre puede tratar de usar otras opciones más atractivas.

Como pudimos ver, en el método create de UserController si se generaba un error nos redireccionaba de nuevo a la vista de crear. Ahora vamos a tratar ese error de otra manera, vamos a capturarlo y notificarselo al usuario.


          /**
           * UserController
           *
           * @description :: Server-side logic for managing users
           * @help        :: See http://links.sailsjs.org/docs/controllers
           */

          module.exports = {
            'new': function (req, res) {
                  res.view();
              },
            index: function (req, res) {
              User.find(function foundUser (err, users) {
                if(err) return res.redirect('/user/new');
                res.view({
                  users: users
                });
              });
            },
            create: function(req, res) {
                User.create(req.params.all(), function userCreated(err, user) {
                  if (err) {
                    req.session.flash = {
                      err: err
                    }
                    return res.redirect('/user/new');
                  }
                  res.redirect('/user/show/' + user.id);
                });
            }
          };

Ahora añadiremos el siguiente código en el fichero new.ejs para comprobar si existe algun campo que no cumpla los requisitos del modelo:


          < form action="/user/create" method="POST" class="form-signin">
            < h2 class="form-signin-heading"> Crear Usuario 

            < % if(flash && flash.err) { %>
                < ul class="alert alert-success">
            < % Object.keys(flash.err).forEach(function(error) { %>
                < li><%- JSON.stringify(flash.err[error]) %>< /li>
            < % }) %>
            < /ul>
            < % } %>

            < div class="control-group"> 
            < input type="text" class="form-control" placeholder="Nombre" name="name" required>
            < /div>
            < div class="control-group">
                  < input type="text" class="form-control" placeholder="Apellidos" name="lastname" required>
                  < /div>
                  < div class="control-group">
                  < input type="text" class="form-control" placeholder="Email" name="email" required>
                  < /div>
                  < div class="control-group">
                  < input id="password" type="password" class="form-control" placeholder="Contraseña" name="password" required>
                  < /div>
                  < div class="control-group">
                  < input type="password" class="form-control" placeholder="Repita Contraseña" name="confirmation" required>
                  < /div>
            < br/>
                  < input type="submit" class="btn btn-lg btn-primary btn-block" value="Create User"/>
                  < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
          < /form>

Lo siguiente será ir a la carpeta api/policies y crear el fichero flash.js, el cual tendrá el siguiente código:


            module.exports = function(req, res, next) {
            res.locals.flash = {};
            
            if(!req.session.flash) return next();

            res.locals.flash = _.clone(req.session.flash);

            // clear flash
            req.session.flash={};
            next();
          }

Despues vamos al fichero csrf.js que se encuentra en la carpeta config y descomentamos la siguiente línea y la ponemos a true, quedando así:

module.exports.csrf = true;

Con esto ayudaremos a hacer nuestra web más segura, gracias al protocolo CSRF.

Ahora iremos a config/policies.js y modificaremos la política que se ejecutará en todas las vistas restantes ('*'):


            module.exports.policies = {
              'user': {
                'new': 'flash',
                index: 'sessionAuth',
                '*': 'flash'
              }
            };

Ahora iremos a la carpeta assets/js y crearemos el fichero customValidate.js con el siguiente contenido:


              $(document).ready(function(){

                // Validate
                // http://bassistance.de/jquery-plugins/jquery-plugin-validation/
                // http://docs.jquery.com/Plugins/Validation/
                // http://docs.jquery.com/Plugins/Validation/validate#toptions

                    $('#sign-up-form').validate({
                    rules: {
                      name: {
                        required: true
                      },
                      lastname: {
                        required: true
                      },
                      email: {
                        required: true,
                        email: true
                      },
                      password: {
                        minlength: 6,
                        required: true
                      },
                      confirmation: {
                        minlength: 6,
                        equalTo: "#password" 
                      }
                    },
                    messages: {
                      name: "Please enter your name",
                      lastname: "Please enter your lastname",
                      email: "Please enter your email with a valid structure",
                      password: {
                        required: "Please provide a password",
                        minlength: "Your password must be at least 6 characters long"
                      },
                      confirmation: {
                        required: "Please provide a password",
                        minlength: "Your password must be at least 6 characters long",
                        equalTo: "Please enter the same password as above"
                      }
                    },
                    success: function(element) {
                      element.text('Success').addClass('valid')
                    }
                  });
            });

Puede bajarse el customValidate.js aquí.

Y finalmente vamos a views/user y en el fichero new.ejs añadimos al form lo siguiente, de manera que quedaría de la siguiente formas:

< form action="/user/create" method="POST" id="sign-up-form" class="form-signin">

Conectar nuestro proyecto con MongoDB

Vamos a preparar nuestra base de datos mongo en modo local.

Para ello, haremos lo siguiente:

Añadir la acción show al controlador

Para crear la acción show lo primero que haremos es ir a nuestro controlador y añadir el siguiente código:

    
            show: function(req, res) {
              User.findOne(req.param('id'), function foundUser (err,user) {
              if(err || !user) return res.serverError(err);
                res.view({
                  user: user
                });
              });
            }

Como podemos ver lo que hace es muy sencillo. Dentro del modelo User, busca un usuario con el parámetro id que le pasamos en la request y si no lo encuentra nos devuelve un error y si lo encuentra renderiza la vista show.ejs. Pero no tenemos el fichero show.ejs!! Por lo que nuestro siguiente paso será crear dicho fichero y añadir el código necesario para mostrar la información del usuario.

NOTA: Recuerde que cada acción acaba con una coma, excepto en la última acción, como en el modelo.

Ahora iremos a la carpeta views y dentro de la carpeta user crearemos el fichero show.ejs con el siguiente código:


              < div class='container'>
                < a class="btn btn-medium btn-primary" href="/user/">Usuarios
                < br />
                < div class="hero-unit center">
                          < form class="form-signin" >
                              < h1>Info:
                              < p>< strong>Nombre: <%= user.name %> < /p>
                              < p>< strong>Apellidos: <%= user.lastname %> < /p>
                              < p>< strong>Email: <%= user.email %> < /p>  
                          < /form>
                  < /div>
                  < hr>
                < a class="btn btn-medium btn-primary" href="/user/edit/<%= user.id %>">Edit 
              < /div>

NOTA: Si os habeis dado cuenta, en algunas ocasiones para cargar código hacemos esto: <% if(...) %>, <%- user.id %> o <%= user.id %>.

Al crear el usuario comprobaremos que nos redicrecciona correctamente pero que si intentamos navegar mediante los botónes o url, nos dirá que no tenemos permisos. Eso es porque no hemos añadido a las policies, las acciones que hemos creado.

Por lo que tendremos que tener lo siguiente en nuestro fichero policies.js dentro de la carpeta config:


              module.exports.policies = {
                'user': {
                  'new': 'flash',
                  index: 'flash',
                  create: 'flash',
                  show: true,
                  '*': false
                }
              };

Crear el controllador Session

Como ya hicimos para crear el modelo y controlador user, ahora lo haremos para crear session, para mantener la sesión tras autenticarnos. Para ello ejecutaremos el siguiente código:

sails generate api session

Ahora iremos al controlador SessionController, y añadiremos el siguiente método:

/**
                        * SessionController
                        *
                        * @description :: Server-side logic for managing sessions
                        * @help        :: See http://links.sailsjs.org/docs/controllers
                        */

                       module.exports = {
                        'new': function (req, res) {
                              res.view('session/new');
                          }
                       };

Ahora iremos a la carpeta views y crearemos la carpeta session y dentro crearemos el fichero new.ejs con el siguiente código:


            < div class="container">
              < form action="/session/create" method="POST" class="form-signin">
                  < h2 class="form-signin-heading">Please sign in< /h2>
                  < label for="inputEmail" class="sr-only">Email address< /label>
                  < input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
                  < label for="inputPassword" class="sr-only">Password< /label>
                  < input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
                  < div class="checkbox">
                    < label>
                      < input type="checkbox" value="remember-me"> Remember me
                    < /label>
                  < /div>
                  < button class="btn btn-lg btn-primary btn-block" type="submit">Sign in
                  < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
              < /form>
          < /div> 
          

Como podemos ver en action llama a /session/create por lo que en el fichero layout.ejs que está dentro de la carpeta views y el fichero index.ejs que se encuentra en la carpeta static dentro de la carpeta views, deberemos añadir el mismo action, donde redireccionará a /session/create.

action="/session/create"

Ahora crearemos dos acciones para nuestro SessionController: new, create y destroy.

Como ya se habrá dado cuenta mediante create, crearemos la sesión para que si el usuario se autentica correctamente pueda navegar y realizar todas las operaciones que desee dentro de la aplicación. Y con destroy, destruirá la sesión, dejando así de estar conectado.

Lo primero que haremos será instalar el módulo bcrypt para encriptar nuestra contraseña, para ello la instalaremos como hemos hecho siempre, mediante npm:

npm install bcrypt --save 

Una vez instalado lo añadiremos a SessionController, quedando el código de la siguiente manera:


          /**
           * SessionController
           *
           * @description :: Server-side logic for managing sessions
           * @help        :: See http://links.sailsjs.org/docs/controllers
           */

          var bcrypt = require('bcrypt');

          module.exports = {
            // Las acciones que hayamos creado
          };

Este es el código para la acción new:


          'new': function (req, res){ 
            res.view('session/new');
          }

Este es el código para la acción create:


            'create': function(req, res, next) {
              if(!req.param('email') || !req.param('password')) {
                var usernamePasswordRequiredError = [{name: 'usernamePasswordRequired', message: 'You must enter both username and password.'}]

                req.session.flash = {
                  err: usernamePasswordRequiredError
                }
                res.redirect('/session/new');
                return;
              }

              User.findOneByEmail(req.param('email'), function foundUser (err, user) {
                if (err) return next(err);
                
                if(!user) {
                  var noAccountError = [{ name: 'noAccount', message: 'The email address '+req.param('email') + ' not found.' }]
                  req.session.flash = {
                    err: noAccountError
                  }
                  res.redirect('/session/new');
                  return;
                }

                bcrypt.compare(req.param('password'), user.encryptedPassword, function (err, valid) {
                  console.log(err);
                  if (err) return next(err);
                  
                  if(!valid) {
                    var usernamePasswordMismatchError = [{ name: 'usernamePasswordMismatch', message: 'Invalid username and password combination.' }]
                    req.session.flash = {
                      err: usernamePasswordMismatchError
                    }
                    res.redirect('/session/new');
                    return;
                  } 

                  //if the password is valid we get here and log the user in
                  req.session.authenticated = true;
                  req.session.User = user;

                  //redirect the user to the profile page
                  res.redirect('/user/show/'+ user.id);  
                  
                }); //end bcrypt.compare
              });//end findOneByEmail
            }

Como podemos observar buscamos un usuario por su email, si no existe se lanza un error y se redirecciona a autenticarse, sino se comparan los passwords; si no es válido se lanza un error y se vuelve a redireccionar para volver a autenticarse y si finalmente es válido la session y el usuario están autorizados y se les redirecciona a su perfil.

Este es el código para la acción destroy:


            destroy: function(req, res, next) {
              //destroy the session
              req.session.destroy();
              res.redirect('/session/new');
            }

Ahora vamos al fichero sessionAuth.js que genera sails, que se encuentra dentro de la carpeta policies de la carpeta api y cambiamos un par de cosas:


            /**
             * sessionAuth
             *
             * @module      :: Policy
             * @description :: Simple policy to allow any authenticated user
             *                 Assumes that your login action in one of your controllers sets `req.session.authenticated = true;`
             * @docs        :: http://sailsjs.org/#!documentation/policies
             *
             */
            module.exports = function(req, res, next) {

              // User is allowed, proceed to the next policy, 
              // or if this is the last policy, the controller
              if (req.session.authenticated) {
                return next();
              }
              // User is not allowed
              else {
                var requireLoginError = [{name: 'requireLogin', message: 'You must be signed in.'}]
                 req.session.flash = {
                  err: requireLoginError
                 }
                 res.redirect('/session/new');
              }
            };

Como se habrá dado cuenta, habrá pensado que cuando un usuario se crea y accede a la aplicación también tendrá que estar autenticado para poder navegar por el resto de la aplicación por lo que tendremos que añadir unas líneas para dar ese permiso y lo haremos de la siguiente manera:


            create: function(req, res) {
              User.create(req.params.all(), function userCreated(err, user) {
                if (err) {
                //Assign the error to the session.flash
                  req.session.flash = {
                    err: err
                  };
                  return res.redirect('/user/new');
                }
                // Log user in
                if (req.session.authenticated == true) {
                  res.redirect('/user/');
                }
                else {
                  req.session.authenticated = true;
                  req.session.User = user;

                  res.redirect('/user/show/'+user.id);
                }
              });
            }

Para finalizar, solo quedará cambiar las policies que están en la carpeta config:


              module.exports.policies = { 
  
                // Por defecto, sails permite el acceso a toda la app
                // '*': true,
                
                'session': {
                  'new': 'flash',
                  create: 'flash'
                },

                'user': {
                  'new': 'flash', // dentro de user para acceder a create debe pasar los criterios de registro
                  index: 'sessionAuth', // dentro de user para acceder a index debe estar autenticado
                  create: 'flash', // dentro de user para acceder a create debe pasar los criterios de registro
                  show: 'sessionAuth', // dentro de user para acceder a show debe estar autenticado
                  '*': false // para el resto de acciones o vistas no tiene acceso
                }
              };

Nota: Si tiene usuarios guardados e intenta autenticarse con ellos no podrá ya que ese usuarios no tenía el password enciptado, por lo que le recomiendo eliminar todos los usuarios creados y ahora volver a crearlos.

Falta ir al modelo Usuario y crear la siguiente Lifecycle callback:


                beforeCreate: function (values, next) {
                  // This checks to make sure the password and password confirmation match before creating record
                  if (!values.password || values.password != values.confirmation) {
                    return next({err: ["Password doesn't match password confirmation."]});
                  }
                      bcrypt.genSalt(10, function(err, salt) {
                      if (err) return next(err);
                      bcrypt.hash(values.password, salt, function passwordEncrypted(err, encryptedPass) {
                      if (err) return next(err);
                      values.encryptedPassword = encryptedPass;
                      next();
                  });
                });
              }

Nota: Acordaros de importar bcrypt:


                /**
                * User.js
                *
                * @description :: TODO: You might write a short summary of how this model works and what it represents here.
                * @docs        :: http://sailsjs.org/#!documentation/models
                */

                var bcrypt = require("bcrypt");

                module.exports = { ... };

Queda una última cosa muy importante, si nos fijamos cuando nos autenticamos correctamente o creamos un usuario, la barra superior no cambia y debería desaparecer la opción de autenticarse y mostrar las opciones que queramos más la de cerrar sesión, asi que para ello iremos al fichero layout.ejs que está dentro de views y modificaremos el código:


              < nav class="navbar navbar-inverse navbar-fixed-top">
                < div class="container">
                  < div class="navbar-header">
                    < button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                      < span class="sr-only">Toggle navigation< /span>
                      < span class="icon-bar">< /span>
                      < span class="icon-bar">< /span>
                      < span class="icon-bar">< /span>
                    < /button>
                    < a class="navbar-brand" href="http://localhost:1337/">Ekembi< /a>
                    <% if (!session.authenticated) { %>
                    < a class="navbar-brand" href="/user/create/">Crear Usuario< /a>
                    <% } %>
                  < /div>
                  < div id="navbar" class="navbar-collapse collapse">
                    <% if (!session.authenticated) { %>
                    < form action="/session/create" method="POST" class="navbar-form navbar-right">
                      < div class="form-group">
                        < input type="email" placeholder="Email" name="email" class="form-control" required>
                      < /div>
                      < div class="form-group">
                        < input type="password" placeholder="Password" name="password" class="form-control" required>
                      < /div>
                      < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
                      < button type="submit" class="btn btn-success">Sign in< /button>
                    < /form>
                    <% } else { %>
                    < a class="navbar-brand" href="/user/show/<%= session.User.id %>"><%= session.User.name %> < /a>< /li>
                    <% } %> 
                    <% if (session.authenticated) { %>
                      < a class="navbar-brand" href="/user/">Usuarios< /a>
                      < a class="navbar-brand" href="/#/">Mascotas< /a>
                      < a class="navbar-brand" href="/#/">Fotos< /a>
                    <% } %>
                    < ul class="nav navbar-nav navbar-right">
                    <% if (session.authenticated) { %>
                      < a class="btn btn-medium btn-primary" style="margin:5px 5px;" href="/session/destroy">log-out< /a>
                    <% } %>
                < /ul>
                  < /div>
                < /div>
              < /nav>

Métodos Edit, Update y Destroy de cada usuario

Cuando listamos los usuarios, cada uno tienen diferentes acciones: "show", "edit", y "destroy". Así que vamos a añadir cada una de las acciones.

Nota: La acción "show" ya la tenemos implementada.

Trás pulsar sobre "show" nos llevará a una vista donde podremos actualizar los campos que deseemos, pero tendremos que crear la acción update en su controlador:


          update: function(req, res, next) {
            User.update(req.param('id'), req.params.all(), function userUpdated(err) {
              if (err) {
                return res.redirect('/user/edit/' + req.param('id'));
              }
              res.redirect('/user/show/' + req.param('id'));
            });
          }

Acción Edit

Lo primero que haremos será crear dentro de la carpeta user en views, crearemos un fichero llamado edit.ejs, que contendrá el siguiente código:


          < form action="/user/update/<%= user.id %>" method="POST" class="form-signin">
            < h2> Editar Usuario < /h2>

            < input value="<%= user.name %>" name="name" type="text" class="form-control"/>
            < input value="<%= user.lastname %>" name="lastname" type="text" class="form-control"/>
            < input value="<%= user.email %>" name="email" type="email" class="form-control"/>
            
            < input type="submit" value="Proceed" class="btn btn-lg btn-primary btn-block"/>
            < input type="hidden" name="_csrf" value="<%= _csrf %>" />
          < /form>

Ahora en el controlador añadiremos su acción:


            edit: function (req, res) {
              // Find the user from the id passed in via params
              User.findOne(req.param('id'), function foundUser (err, user) {
                if (err) return res.serverError(err);
                if (!user) return res.serverError(err);

                res.view({
                  user: user
                });
              });
            }

Acción Destroy

Esta acción sirve para eliminar un usuario de la lista de usuarios, y comprueba si el usuario que está autenticado se borra así mismo o a uno que no está autenticado:


            destroy: function(req, res, next) {
              User.findOne(req.param('id'), function foundUser(err, user) {
                if (err) return next(err);

                if (!user) return next('User doesn\'t exist.');

                User.destroy(req.param('id'), function userDestroyed(err) {
                  if (err) return next(err);

                  if (user.id.toString() === req.session.User.id) {
                    res.redirect('/session/destroy');
                  }
                  else {
                    res.redirect('/user/');
                  }
                });
              });
            }

Finalmente iremos a la carpeta config y añadiremos las nuevas acciones a policies.js:


              //'*': true,

              'session': {
                '*': 'sessionAuth'
              },

              'user': {
                'new': 'flash',
                create: 'flash',
                index: 'sessionAuth',
                show: 'sessionAuth',
                edit: 'sessionAuth',
                update: 'sessionAuth',
                destroy: 'sessionAuth',
                '*': false
               }

Ya hemos terminado todo sobre los usuarios.

Proteger los parámetros

Cuando usamos el navegador, por ejemplo el chrome, podemos añadir código y hackear el servidor. Por ejemplo, si hubiera un usuario admin, habría usuarios normales por lo que nos podriamos conectar como un usuario normal y añadir que somos admin, hackeando así el proyecto y tendría una vulnerabilidad.

Hacer Marshalling es hacer que nosotros creamos un objeto y le pasamos los atributos que nosotros deseemos y así evitamos la vulnerabilidad en nuestro sistema.


            update: function(req, res, next) {
              // Campos que queremos modificar
              var userObj = { 
                name: req.param('name'),
                lastname: req.param('lastname'),
                email: req.param('email')
              }

              User.update(req.param('id'), userObj, function userUpdated(err) {
                if (err) {
                  return res.redirect('/user/edit/' + req.param('id'));
                }
                res.redirect('/user/show/' + req.param('id'));
              });
            }

Subir ficheros

Lo primero que haremos será instalar las dependencias que necesitaremos para subir archivos:

La dependencia para leer, escribir ficheros.

npm install fs --save

La dependencia para crear carpetas o comprobar que existen.

npm install mkdirp --save

La dependencia para leer y borrar ficheros.

npm install read-remove-file --save

Primero empezaremos con crear un nuevo modelo y un nuevo controlador, para ello haremos ejecutaremos la siguiente instrucción:

sails generate api file

Ahora ya tenemos nuestro modelo y nuestro controlador, primero rellenaremos el modelo:


          /**
            * File.js
            *
            * @description :: TODO: You might write a short summary of how this model works and what it represents here.
            * @docs        :: http://sailsjs.org/#!documentation/models
            */

            module.exports = {

              attributes: {
                toJSON: function() {
                      var obj = this.toObject();
                      delete obj._csrf;
                      delete obj.createdAt;
                      delete obj.updatedAt;
                      return obj;
                }
              }
            };

Ahora escribiremos las acciones que realizará nuestro controlador:


          /**
           * FileController
           *
           * @description :: Server-side logic for managing files
           * @help        :: See http://links.sailsjs.org/docs/controllers
           */

          module.exports = {
            
            index: function(req, res, next) {
                  File.find(function foundFiles(err, files) {
                      if (err) return next(err);
                      res.view({
                          files: files
                      });
                  });
              },
              show: function(req, res, next) {

                  File.findOne(req.param('id'))
                  .populate('subtitulo_del_video')
                  .exec(function(err,file) {
                      res.view({
                          subtitles: file.subtitulo_del_video,
                          file: file
                      });
                  });
              },
              destroy: function(req, res, next) {

                  File.findOne(req.param('id'), function foundFile(err, file) {
                      if (err) return next(err);
                      if (!file) return next('File doesn\'t exist.');

                      var pathToRemove = file.fullPath;
                      
                      File.destroy(req.param('id'), function fileDestroyed(err) {
                          if (err) return next(err);

                          var fs = require('fs');
                          var readRemoveFile = require('read-remove-file');

                          readRemoveFile(pathToRemove, function(err, buf) {
                              if (err) return next(err);
                              buf;
                              fs.existsSync(pathToRemove); //=> false 
                              return res.redirect('/file/');
                          });
                      });
                  });
              },
              'upload-file' :function (req,res) {
                  res.view();
              },
              getFile: function (req, res) {
                var query = {name: req.param('name')};

                  if (req.param('version'))
                  query = {name: req.param('fileName'), version: req.param('version')};

                  File.findOne({where : query, sort: 'version DESC'}).exec(function(err, videoFile) {
                      if (err) {     
                          console.log("Error: " + err);
                          res.json(err);
                          return;
                      }
                      else if (videoFile) {   
                          console.log("videoFile: " + videoFile);
                          console.log("videoFile name: " + videoFile.name);

                          res.download(videoFile.fullPath, videoFile.name);
                      }
                      else
                          res.json("videoFile not found!");
                  });
              },
              upload: function  (req, res) {
                  if(req.method === 'GET')
                      return res.json({'status':'GET not allowed'});
                      // Call to /upload via GET is error
                      var upFile = req.file('upFile');

                      var path = './tempd/pets/images/';

                      var mkdirp = require('mkdirp');
                      mkdirp(path, function(err) { 
                          if (err) {
                              console.log("Video folder not created: ", err);
                              return res.serverError(err);
                          }
                          console.log("Video folder created or existed!")
                          
                          upFile.upload({maxBytes: 1000000000, dirname: path },function onUploadComplete (err, files) {                                                                                                      
                              if (err) {
                                  console.log("Video file not uploaded. Error: "+err);
                                  return res.serverError(err);
                              }
                              else {
                               var filePath = files[0].fd;
                               var fileSize = files[0].size;
                               var fileType = files[0].type;
                               var fileName = files[0].filename;

                               File.create({name: fileName,
                                  version: 0,
                                  description: fileType,
                                  file: fileName,
                                  fileSize: fileSize,
                                  fullPath: filePath}, function Done(err) {
                                      res.redirect('file/index/');
                                  });
                              }
                          });
                      });
              }
          };

Ahora iremos a la carpeta views y crearemos una carpeta con el nombre "file" y dentro de dicha carpeta crearemos los siguientes ficheros: "index.ejs", "show.ejs" y "upload.ejs".

En el fichero "index.ejs" escribiremos el siguiente código:


          < div class="container">
            < h3>Imágenes< /h3>
            < table class='table'>
                < tr>
                    < th>Nombre< /th>
                    < th>Descripción< /th>
                    < th>Formato< /th>
                    < th>Tamaño< /th>
                    < th>Path completo< /th>
                    < th>Versión< /th>
                    < th>< /th>
                    < th>< /th>
                < /tr>

                <% _.each(files, function(file) { %>
                < tr data-id="<%= file.id %>" data-model="file">
                    < td><%= file.name %>< /td>
                    < td><%= file.description %>< /td>
                    < td><%= file.file %>< /td>
                    < td><%= file.fileSize %>< /td>
                    < td><%= file.fullPath %>< /td>
                    < td><%= file.version %>< /td>
                    < td>< a href="/file/show/<%= file.id %>" class="btn btn-sm btn-primary">Show< /a>< /td>
                    < td>< form action="/file/destroy/<%= file.id %>" method="POST">
                            < input type="hidden" name="_method" value="delete"/>
                            < input type="submit" class="btn btn-sm btn-danger" value="Delete"/>
                            < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                        < /form>
                    < /td>
                < /tr>
                <% }) %>
            < /table>
            < br />
            < a class="btn btn-medium btn-primary" href="/file/upload-file/">Subir Nueva Imagen< /a>
          < /div>

En el fichero "show.ejs" escribiremos el siguiente código:


            < div class='container'>
              < a class="btn btn-medium btn-primary" href="/file/">Imágenes
              < br />
                < div class="hero-unit center">
                    < form class="form-signin" >
                      < h1>Info:< /h1>
                      < p>< strong>Nombre:< /strong> <%= file.name %> < /p>
                      < p>< strong>Descripción:< /strong> <%= file.description %> < /p>
                      < p>< strong>Formato:< /strong> <%= file.file %> < /p>
                      < p>< strong>Tamaño:< /strong> <%= file.fileSize %> < /p>
                      < p>< strong>Path completo:< /strong> <%= file.fullPath %> < /p>
                        < p>< strong>Versión:< /strong> <%= file.version %> < /p>
                    < /form>
                < /div>
              < hr>
            < /div>
          

En el fichero "upload-file.ejs" escribiremos el siguiente código:


          < form enctype="multipart/form-data" action="/file/upload" method="POST" id="sign-up-form" class="form-signin">
            < h2 class="form-signin-heading"> Upload a file 
            < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
            < input type="file" name="upFile" />
            < br/>
            < input type="submit" class="btn btn-lg btn-primary btn-block" value="upload"/>
          < /form>  
          

Ahora vamos al fichero "layout.ejs" y añadiremos el path a donde deberá redireccionar el navegador cuando estemos autenticados y pulsemos sobre "Fotos".


          ...
          <% if (session.authenticated) { %>
            < a class="navbar-brand" href="/user/">Usuarios< /a>
            < a class="navbar-brand" href="/#/">Mascotas< /a>
            < a class="navbar-brand" href="/file/">Fotos< /a>
          <% } %>
          ...
          

Finalmente iremos al fichero "policies.js" que hay en la carpeta "config" y añadiremos la política que realizará la aplicación sobre las acciones de file.



          module.exports.policies = {

            //'*': true,

            user: {
              'new': 'flash',
              create: 'flash',
              index: 'sessionAuth',
              show: 'sessionAuth',
              edit: 'sessionAuth',
              update: 'sessionAuth',
              destroy: 'sessionAuth',
              '*': false
            },

            session: {
              '*': 'sessionAuth'
            },

            file: {
              '*': "sessionAuth"
            }
          };

Ya hemos acabado con la parte de subir archivos, ahora cada vez que subamos un archivo, que en este caso será una imagen, se almacenará en el siguiente path: "./tempd/pets/images/".

Relaciones

Existen 4 tipos de relaciones:

Para ver cada una de las relaciones vamos a desarrollar en nuestro proyecto el siguiente uml:

UML

Tendremos que crear los modelos y controladores para Mascota y Raza, a las que llamaremos pet y breed. Para el controlador y modelo de imagen usaremos el de file.

Nota: Aquí podeis ver todos los atributos que podeis declarar en sails.

Primero empezaremos creando los modelos y controladores:

sails generate api pet
sails generate api breed

Ahora rellenaremos el modelo de pet:


          /**
            * Pet.js
            *
            * @description :: TODO: You might write a short summary of how this model works and what it represents here.
            * @docs        :: http://sailsjs.org/#!documentation/models
            */

            module.exports = {

              attributes: {
                name: {
                  type: 'string',
                  required: true
                },
                id_chip: {
                  type: 'string',
                  required: true,
                  unique: true
                },

                toJSON: function() {
                  var obj = this.toObject();
                  delete obj._csrf;
                  return obj;
                }
              }
            };

Y ahora rellenaremos el modelo de breed:


          /**
            * Breed.js
            *
            * @description :: TODO: You might write a short summary of how this model works and what it represents here.
            * @docs        :: http://sailsjs.org/#!documentation/models
            */

            module.exports = {

              attributes: {
                name: {
                  type: 'string',
                  required: true
                },
                color: {
                  type: 'string',
                  required: true
                },
                country: {
                  type: 'string',
                  required: true,
                  unique: true
                },

                toJSON: function() {
                  var obj = this.toObject();
                  delete obj._csrf;
                  return obj;
                }
              }
            };

Ahora rellenaremos el controlador de pet:


            /**
             * PetController
             *
             * @description :: Server-side logic for managing pets
             * @help        :: See http://links.sailsjs.org/docs/controllers
             */

            module.exports = {
              'new': function (req, res) {
                    res.view();
                },
              index: function (req, res) {
                Pet.find(function foundPet (err, pets) {
                  if(err) return res.redirect('/pet/new');
                  res.view({
                    pets: pets
                  });
                });
              },
                create: function(req, res) {
                  Pet.create(req.params.all(), function petCreated(err, pet) {
                    if (err) {
                    //Assign the error to the session.flash
                        req.session.flash = {
                          err: err
                        };
                        return res.redirect('/pet/new');
                    }
                    res.redirect('/pet/show/' + pet.id); 
                  });
                },
                show: function(req, res) {
                    Pet.findOne(req.param('id'), function foundPet (err,pet) {
                        if(err || !pet) return res.serverError(err);
                        res.view({
                            pet: pet
                        });
                    });
                },
                edit: function (req, res) {
                  // Find the user from the id passed in via params
                  Pet.findOne(req.param('id'), function foundPet (err, pet) {
                    if (err) return res.serverError(err);
                    if (!pet) return res.serverError(err);
                    res.view({
                      pet: pet
                    });
                  });
              },
                update: function(req, res, next) {

                  var petObj = {
                    name: req.param('name'),
                    id_chip: req.param('id_chip')
                  }
                  Pet.update(req.param('id'), petObj, function petUpdated(err) {
                    if (err) {
                      return res.redirect('/pet/edit/' + req.param('id'));
                    }
                    res.redirect('/pet/show/' + req.param('id'));
                  });
                },  
                destroy: function(req, res, next) {
                Pet.findOne(req.param('id'), function foundPet(err, pet) {
                  if (err) return next(err);
                  if (!pet) return next('Pet doesn\'t exist.');

                  Pet.destroy(req.param('id'), function petDestroyed(err) {
                    if (err) return next(err);
                      res.redirect('/pet/');
                  });
                });
              }
            };

Ahora rellenaremos el controlador de breed:


            /**
             * BreedController
             *
             * @description :: Server-side logic for managing breeds
             * @help        :: See http://links.sailsjs.org/docs/controllers
             */

            module.exports = {
              'new': function (req, res) {
                    res.view();
              },
              index: function (req, res) {
                Breed.find(function foundBreed (err, breeds) {
                  if(err) return res.redirect('/breed/new');
                  res.view({
                    breeds: breeds
                  });
                });
              },
              create: function(req, res) {
                  Breed.create(req.params.all(), function breedCreated(err, breed) {
                    if (err) {
                    //Assign the error to the session.flash
                        req.session.flash = {
                          err: err
                        };
                        return res.redirect('/breed/new');
                    }
                    res.redirect('/breed/show/' + breed.id); 
                  });
              },
              show: function(req, res) {
                    Breed.findOne(req.param('id'), function foundBreed (err,breed) {
                        if(err || !breed) return res.serverError(err);
                        res.view({
                            breed: breed
                        });
                    });
              },
              edit: function (req, res) {
                  // Find the user from the id passed in via params
                  Breed.findOne(req.param('id'), function foundBreed (err, breed) {
                    if (err) return res.serverError(err);
                    if (!breed) return res.serverError(err);
                    res.view({
                      breed: breed
                    });
                  });
              },
              update: function(req, res, next) {
                  var breedObj = {
                    name: req.param('name'),
                    color: req.param('color'),
                    country: req.param('country')
                  }
                  Breed.update(req.param('id'), breedObj, function breedUpdated(err) {
                    if (err) {
                      return res.redirect('/breed/edit/' + req.param('id'));
                    }
                    res.redirect('/breed/show/' + req.param('id'));
                  });
                },  
                destroy: function(req, res, next) {
                Breed.findOne(req.param('id'), function foundBreed(err, breed) {
                  if (err) return next(err);
                  if (!breed) return next('Breed doesn\'t exist.');

                  Breed.destroy(req.param('id'), function breedDestroyed(err) {
                    if (err) return next(err);
                      res.redirect('/breed/');
                  });
                });
              }
            };

Si os habeis fijado ambos códigos son práctimente identicos, solo cambia el modelo al que llama y la redirección, porque las variables podrían ser las mismas pero es mejor que cada controlador use la que le toca.

Ahora lo que tendremos es que crear en la carpeta view, la carpeta pet y la carpeta breed. Y dentro de cada una de ellas, los siguientes ficheros: index.ejs, edit.ejs, new.ejs y show.ejs.

Ahora empezaremos a rellenar cada uno de los ficheros:

Empezeremos con los ficheros de pet:

index.ejs

          < div class="container">
            < h3>Mascotas< /h3>
            < table class='table'>
                < tr>
                    < th>Chip< /th>
                    < th>Nombre< /th>
                    < th>< /th>
                    < th>< /th>
                    < th>< /th>
                < /tr>
                <% _.each(pets, function(pet) { %>
                < tr data-id="<%= pet.id %>" data-model="pet">
                    < td><%= pet.id_chip %>< /td>
                    < td><%= pet.name %>< /td>
                    < td>< a href="/pet/show/<%= pet.id %>" class="btn btn-sm btn-primary">Show< /a>< /td>
                    < td>< a href="/pet/edit/<%= pet.id %>" class="btn btn-sm btn-warning">Edit< /a>< /td>
                  < td>< form action="/pet/destroy/<%= pet.id %>" method="POST"> 
                            < input type="hidden" name="_method" value="delete"/>
                            < input type="submit" class="btn btn-sm btn-danger" value="Delete"/>
                            < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                        < /form>
                    < /td>
                < /tr>
                <% }) %>
            < /table>
            < a href="/pet/new">Crear Nueva Mascota< /a>
          < /div>
edit.ejs

          < form action="/pet/update/<%= pet.id %>" method="POST" class="form-signin">
            < h2> Editar Mascota < /h2>

            < input value="<%= pet.name %>" name="name" type="text" class="form-control"/>
            < input value="<%= pet.id_chip %>" name="id_chip" type="text" class="form-control"/>
            
            < input type="submit" value="Proceed" class="btn btn-lg btn-primary btn-block"/>
            < input type="hidden" name="_csrf" value="<%= _csrf %>" />
          < /form>
new.ejs

          < form action="/pet/create" method="POST" class="form-signin">
            < h2 class="form-signin-heading"> Crear Mascota < /h2>

            < div class="control-group"> 
               < input type="text" class="form-control" placeholder="Nombre" name="name" required>
            < /div>
            < div class="control-group">
                  < input type="text" class="form-control" placeholder="Chip" name="id_chip" required>
              < /div>
            < br/>
                  < input type="submit" class="btn btn-lg btn-primary btn-block" value="Create Pet"/>
                  < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
          < /form>
show.ejs

          < div class='container'>
            < a class="btn btn-medium btn-primary" href="/pet/">Mascotas< /a>
            < br />

            < div class="hero-unit center">
                < form class="form-signin" >
                  < h1>Info:< /h1>
                  < p>< strong>Chip:< /strong> <%= pet.id_chip %> < /p>
                  < p>< strong>Nombre:< /strong> <%= pet.name %> < /p> 
                < /form>
              < /div>
              < hr>
            < a class="btn btn-medium btn-primary" href="/pet/edit/<%= pet.id %>">Edit< /a> 
          < /div>

Ahora pasaremos a los ficheros de breed:

index.ejs

          < div class="container">
            < h3>Razas< /h3>
            < table class='table'>
                < tr>
                    < th>País< /th>
                    < th>Nombre< /th>
                    < th>Color< /th>
                    < th>< /th>
                    < th>< /th>
                    < th>< /th>
                < /tr>
                <% _.each(breeds, function(breed) { %>
                < tr data-id="<%= breed.id %>" data-model="breed">
                    < td><%= breed.country %>< /td>
                    < td><%= breed.name %>< /td>
                    < td><%= breed.color %>< /td>
                    < td>< a href="/breed/show/<%= breed.id %>" class="btn btn-sm btn-primary">Show< /a>< /td>
                    < td>< a href="/breed/edit/<%= breed.id %>" class="btn btn-sm btn-warning">Edit< /a>< /td>
                  < td>< form action="/breed/destroy/<%= breed.id %>" method="POST"> 
                            < input type="hidden" name="_method" value="delete"/>
                            < input type="submit" class="btn btn-sm btn-danger" value="Delete"/>
                            < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                        < /form>
                    < /td>
                < /tr>
                <% }) %>
            < /table>
            < a href="/breed/new">Crear Nueva Raza< /a>
          < /div>
edit.ejs

          < form action="/breed/update/<%= breed.id %>" method="POST" class="form-signin">
            < h2> Editar Raza < /h2>
            < input value="<%= breed.name %>" name="name" type="text" class="form-control"/>
            < input value="<%= breed.color %>" name="color" type="text" class="form-control"/>
            < input value="<%= breed.country %>" name="country" type="text" class="form-control"/>
            
            < input type="submit" value="Proceed" class="btn btn-lg btn-primary btn-block"/>
            < input type="hidden" name="_csrf" value="<%= _csrf %>" />
          < /form>
new.ejs

          < form action="/breed/create" method="POST" class="form-signin">
            < h2 class="form-signin-heading"> Crear Raza < /h2>

              < div class="control-group"> 
                 < input type="text" class="form-control" placeholder="País" name="country" required>
              < /div>
            < div class="control-group"> 
               < input type="text" class="form-control" placeholder="Nombre" name="name" required>
            < /div>
            < div class="control-group">
                  < input type="text" class="form-control" placeholder="Color" name="color" required>
              < /div>
            < br/>
                  < input type="submit" class="btn btn-lg btn-primary btn-block" value="Create Breed"/>
                  < input type="hidden" name="_csrf" value="<%=_csrf %>"/>
          < /form>
show.ejs

          < div class='container'>
            < a class="btn btn-medium btn-primary" href="/breed/">Razas< /a>
            < br />
            < div class="hero-unit center">
                < form class="form-signin" >
                  < h1>Info:< /h1>
                  < p>< strong>País:< /strong> <%= breed.country %> < /p>
                  < p>< strong>Nombre:< /strong> <%= breed.name %> < /p> 
                  < p>< strong>Color:< /strong> <%= breed.color %> < /p> 
                < /form>
              < /div>
              < hr>
            < a class="btn btn-medium btn-primary" href="/breed/edit/<%= breed.id %>">Edit< /a> 
          < /div>

No olvide que como siempre deberemos ir al fichero policies.js de la carpeta config y añadir las politicas a los nuevos modelos creados:


          module.exports.policies = {

            user: {
              'new': 'flash',
              create: 'flash',
              index: 'sessionAuth',
              show: 'sessionAuth',
              edit: 'sessionAuth',
              update: 'sessionAuth',
              destroy: 'sessionAuth',
              '*': false
            },

            session: {
              '*': "flash"
            },

            file: {
              '*': "sessionAuth"
            },

            pet: {
              '*': "sessionAuth"
            },

            breed: {
              '*': "sessionAuth"
            }
          };

Además, deberemos ir al fichero layout.ejs dentro de la carpeta views y modificar las redirecciones de mascotas y crear un nuevo enlace para razas:


          ...
          <% if (session.authenticated) { %>
            < a class="navbar-brand" href="/user/">Usuarios< /a>
            < a class="navbar-brand" href="/pet/">Mascotas< /a>
            < a class="navbar-brand" href="/breed/">Razas< /a>
            < a class="navbar-brand" href="/file/">Fotos< /a>
          <% } %>
          ...

Ahora ya podemos empezar a trabajar con las relaciones!!!

Nota: Si ha seguido todos los pasos debería arrancar el servidor. Y deberíamos rellenar cada modelo con datos para luego realizar las relaciones.

1.) Uno a Muchos

Una asociación uno-a-muchos afirma que un modelo se puede asociar a muchos otros modelos. Para construir esta asociación de un atributo virtual se añade a un modelo usando la propiedad colección. En una asociación uno-a-muchos un lado debe tener un atributo de captura y el otro lado debe contener un atributo de modelo. Esto permite que el lado muchos sepa que registra y que necesita para conseguir cuando usamos populate.

Debido a que es posible que desee un modelo en el que pueda tener múltiples asociaciones uno-a-muchos en el otro modelo necesita necesita una clave via en el atributo colección. Esto indica que el modelo de atributos de un lado de la asociación se utiliza para rellenar los registros.

Como hemos visto en el UML, 1 Usuario puede tener N mascotas, pues realicemos la asociación. Para ello haremos lo siguiente en los respectivos modelos.

Tenga en cuenta que al ser una relación 1 a N, el usuario juan puede asociar N mascotas (pet1, pet2, pet3), pero si el usuario pedro se asocia a pet2, el usuario juan ya no tendrá la relación pet2, porque ahora le pertenece a pedro y se le desasociará de juan.

En el modelo User añadiremos lo siguiente, quedando el modelo de la siguiente manera:


          /**
            * User.js
            *
            * @description :: TODO: You might write a short summary of how this model works and what it represents here.
            * @docs        :: http://sailsjs.org/#!documentation/models
            */

            var bcrypt = require("bcrypt");

            module.exports = {

              attributes: {

                name: {
                  type: 'string',
                  required: true
                },
                lastname: {
                  type: 'string',
                  required: true
                },
                email: {
                  type: 'email',
                  required: true,
                  unique: true
                },
                encryptedPassword: {
                  type: 'string'
                },

                // ASSOCIATION

                pets: {
                  collection: 'Pet',
                  via: 'owner'
                },

                // JSON

                toJSON: function() {
                  var obj = this.toObject();
                  delete obj.password;
                  delete obj.confirmation;
                  delete obj.encryptedPassword;
                  delete obj._csrf;
                  return obj;
                }

              },

              // VALIDATIONS

              beforeCreate: function (values, next) {
                  // This checks to make sure the password and password confirmation match before creating record
                  if (!values.password || values.password != values.confirmation) {
                    return next({err: ["Password doesn't match password confirmation."]});
                  }
                      bcrypt.genSalt(10, function(err, salt) {
                      if (err) return next(err);
                      bcrypt.hash(values.password, salt, function passwordEncrypted(err, encryptedPass) {
                      if (err) return next(err);
                      values.encryptedPassword = encryptedPass;
                      next();
                  });
                });
              }
            };

Y ahoraEn el modelo Pet añadiremos lo siguiente, quedando el modelo de la siguiente manera:


          /**
            * Pet.js
            *
            * @description :: TODO: You might write a short summary of how this model works and what it represents here.
            * @docs        :: http://sailsjs.org/#!documentation/models
            */

            module.exports = {

              attributes: {
                name: {
                  type: 'string',
                  required: true
                },
                id_chip: {
                  type: 'string',
                  required: true,
                  unique: true
                },

                // ASSOCIATIONS

                owner: {
                  model: 'User'
                },

                // JSON

                toJSON: function() {
                  var obj = this.toObject();
                  delete obj._csrf;
                  return obj;
                }
              }
            };

Ahora añadiremos las acciones para asociar los modelos:


          // ASSOCIATE USER - PET (1-N)

          associatePet: function(req, res, next) {
            Pet.find(function foundPets(err, pets) {
              if (err) return next(err);
              res.view({
                pets: pets,
                user_id: req.param('id')
              });
            });
          },

          deassociatePet: function(req,res,next){
            var pet_id = req.param('pet_id');
            var user_id = req.param('user_id');
            
            User.findOne(user_id, function foundPet(err, user) {
              if (err) return next(err);
              if (!user) return next();

              user.pets.remove(pet_id);
              user.save(function createDeassociation(err, saved) {
                
                if (err) res.redirect('/user/show/' + user_id);
                res.redirect('/user/show/' + user_id);
              });
            });
          },
              
          associatePetToUser: function(req,res,next){
            var pet_id = req.param('pet_id');
            var user_id = req.param('user_id');

            User.findOne({id:user_id}).then(function(user) {
              user.pets.add(pet_id);
              return user.save().fail(function() {
                sails.log.info('User already has that pet!');
              });
            })
            .then(function(){
              res.redirect('/user/show/' + user_id);
            })
            .fail(function(err) {
              sails.log.error('Unexpected error: ' +err);
              res.redirect('/user/associatePet/');
            });
          }

Tambien tendremos que modificar el método show de UserController, porque ahora además de mostrar la información de dicho usuario, mostraremos la información de las mascotas asociadas:


          show: function(req, res) {
            User.findOne(req.param('id'))
            .populate('pets')
            .exec(function(err,user) {
                    //console.log(found);
                    res.view({
                      pets: user.pets,
                      user: user
                    });
                });
          }

E iremos al fichero policies.js de la carpeta config para dar permisos a las acciones que hemos creado:


          user: {
            'new': 'flash',
            create: 'flash',
            index: 'sessionAuth',
            show: 'sessionAuth',
            edit: 'sessionAuth',
            update: 'sessionAuth',
            destroy: 'sessionAuth',
            associatePet: 'sessionAuth',
            deassociatePet: 'sessionAuth',
            associatePetToUser: 'sessionAuth',
            '*': false
          }

Además añadiremos las policies del resto de modelos:


          session: {
            '*': "flash"
          },

          file: {
            '*': "sessionAuth"
          },

          pet: {
            '*': "sessionAuth"
          },

          breed: {
            '*': "sessionAuth"
          }

Ya tenemos la relación 1 a N entre Usuario y Mascota. Pero nos falta poder asociarlos mediante botones en el html. Para ello, primero modificaremos el fichero show.ejs de la carpeta user:


          < div class='container'>
            < a class="btn btn-medium btn-primary" href="/user/">Usuarios< /a>
            < br />
            < div class="hero-unit center">
                < form class="form-signin" >
                    < h1>Info:
                    < p>< strong>Nombre:< /strong> <%= user.name %> < /p>
                    < p>< strong>Apellidos:< /strong> <%= user.lastname %> < /p>
                    < p>< strong>Email:< /strong> <%= user.email %> < /p>
                < /form>
            < /div>
            < hr>
            < h3>Mascotas< /h3> < a href="/user/associatePet/<%= user.id %>">Asociar Nueva Mascota< /a>
            < table class='table'>
                < tr>
                    < th>Chip< /th>
                    < th>Nombre< /th>
                    < th>< /th>
                    < th>< /th>
                < /tr>
                <% _.each(pets, function(pet) { %>
                < tr data-id="<%= pet.id %>" data-model="pet">
                    < td>< strong>Chip:< /strong> <%= pet.id_chip %> < /td>
                    < td>< strong>Nombre:< /strong> <%= pet.name %> < /td> 
                    < td>< a href="/pet/show/<%= pet.id %>" class="btn btn-sm btn-primary">Show< /a>< /td>
                    < td>
                        < form action="/user/deassociatePet/" method="POST" >                    
                            < input type="hidden" name="pet_id" value="<%= pet.id %>" />
                            < input type="hidden" name="user_id" value="<%= user.id %>" />
                            < input type="submit" value="Desasociar mascota" class="btn btn-sm "/>
                            < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                        < /form>
                    < /td>
                < /tr>
                <% }) %>
            < /table>
            < hr>
            < a class="btn btn-medium btn-primary" href="/user/edit/<%= user.id %>">Edit< /a>   
          < /div>

Ahora crearemos el fichero asssociatePet.ejs dentro de la carpeta user, con el siguiente código:


          < div class='container'>
            < h3>Elige la mascota a asociar con el usuario< /h3>    
              < table class='table'>
                  < tr>
                      < th>Chip< /th>
                      < th>Nombre< /th>
                      < th>< /th>
                      < th>< /th>
                      < th>< /th>
                  < /tr>
                  <% _.each(pets, function(pet) { %>
                  < tr data-id="<%= pet.id %>" data-model="pet">
                      < td><%= pet.id_chip %>< /td>
                      < td><%= pet.name %>< /td>
                      < td>
                          < form action="/user/associatePetToUser/" method="POST" >                    
                              < input type="hidden" name="pet_id" value="<%= pet.id %>" />
                              < input type="hidden" name="user_id" value="<%= user_id %>" />
                              < input type="submit" value="Asociar" class="btn btn-sm"/>
                              < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                          < /form>
                      < /td>
                  < /tr>
                  <% }) %>
              < /table>
              < br />
          < /div>

Ahora ya podemos lanzar nuestro servidor y asociar tantas mascotas como deseemos al mismo usuario.

Nota: Recuerde que si asocia una mascota que ya pertenece a un usuario a otro usuario, la primera asociación se eliminará y la mascota pasará a ser del último usuario que lo ha asociado.

2.) Muchos a Muchos

Una asociación muchos-a-muchos afirma que un modelo se puede asociar a muchos otros modelos y viceversa. Debido a que ambos modelos pueden tener muchos modelos relacionados tendremos que crear una tabla de intermediaria (de unión) para realizar un seguimiento de estas relaciones.

Waterline se encarga de revisar sus modelos y si encuentra que mabos modelos tienen una colección (collection) atribuye ese punto el uno al otro, automáticamente construye una tabla de unión.

Debido a que es posible que desee un modelo a tener asociaciones múltiples-muchos-a-muchos en otro modelo se necesita la clave via en el atributo colección (collection). Esto indica que el modelo de atributos de un lado de la asociación se utiliza para rellenar los registros.

Como hemos visto en el UML, N Mascotas pueden tener N razas, eso es porque una mascota puede ser una mezcla de dos razas o más, y una raza puede ser de una o varias mascotas. Para ello haremos lo siguiente en los respectivos modelos.

En el modelo Pet añadiremos lo siguiente, quedando el modelo de la siguiente manera:


          /**
          * Pet.js
          *
          * @description :: TODO: You might write a short summary of how this model works and what it represents here.
          * @docs        :: http://sailsjs.org/#!documentation/models
          */

          module.exports = {

            attributes: {
              name: {
                type: 'string',
                required: true
              },
              id_chip: {
                type: 'string',
                required: true,
                unique: true
              },

              // ASSOCIATIONS

              owner: {
                model: 'User'
              },

              pet_breed: {
                collection: 'Breed',
                via: 'breed'
              },

              // JSON

              toJSON: function() {
                var obj = this.toObject();
                delete obj._csrf;
                return obj;
              }
            }
          };

En el modelo Breed añadiremos lo siguiente, quedando el modelo de la siguiente manera:


          /**
          * Breed.js
          *
          * @description :: TODO: You might write a short summary of how this model works and what it represents here.
          * @docs        :: http://sailsjs.org/#!documentation/models
          */

          module.exports = {

            attributes: {
              name: {
                type: 'string',
                required: true
              },
              color: {
                type: 'string',
                required: true
              },
              country: {
                type: 'string',
                required: true,
                unique: true
              },

              // ASSOCIATIONS

              breed: {
                collection: 'Pet',
                via: 'pet_breed'
              },

              toJSON: function() {
                var obj = this.toObject();
                delete obj._csrf;
                return obj;
              }
            }
          };

Ahora añadiremos las acciones para asociar los modelos en su controlador (PetController.ejs):


          // ASSOCIATE BREED - PET (N-N)

          associateBreed: function(req, res, next) {
            Breed.find(function foundBreeds(err, breeds) {
              if (err) return next(err);
              res.view({
                breeds: breeds,
                pet_id: req.param('id')
              });
            });
          },

          deassociateBreed: function(req,res,next){
            var breed_id = req.param('breed_id');
            var pet_id = req.param('pet_id');
            
            Breed.findOne(breed_id, function foundBreed(err, breed) {
              if (err) return next(err);
              if (!breed) return next();

              breed.breed.remove(pet_id);
              breed.save(function createDeassociation(err, saved) {
                
                if (err) res.redirect('/pet/show/' + pet_id);
                res.redirect('/pet/show/' + pet_id);
              });
            });
          },
              
          associateBreedToPet: function(req,res,next){
            var breed_id = req.param('breed_id');
            var pet_id = req.param('pet_id');

            Breed.findOne({id:breed_id})
            .then(function(breed) {
              breed.breed.add(pet_id);
              return breed.save().fail(function() {
                sails.log.info('Pet already has that breed!');
              });
            })
            .then(function(){
              res.redirect('/pet/show/' + pet_id);
            })
            .fail(function(err) {
              sails.log.error('Unexpected error: ' +err);
              res.redirect('/breed/associatePet/');
            });
          }

Ya tenemos la relación N a N entre Mascota y Raza. Pero nos falta poder asociarlos mediante botones en el html. Para ello, primero modificaremos el fichero show.ejs de la carpeta pet:


          < div class='container'>
            < a class="btn btn-medium btn-primary" href="/pet/">Mascotas
            < br />
            < div class="hero-unit center">
                  < form class="form-signin" >
                      < h1>Info:< /h1>
                      < p>< strong>Chip:< /strong> <%= pet.id_chip %> < /p>
                      < p>< strong>Nombre:< /strong> <%= pet.name %> < /p> 
                  < /form>
              < /div>
              < hr>
              < h3>Razas< /h3> < a href="/pet/associateBreed/<%= pet.id %>">Asociar Nueva Raza< /a>
              < table class='table'>
                  < tr>
                      < th>País< /th>
                      < th>Nombre< /th>
                      < th>Color< /th>
                      < th>< /th>
                      < th>< /th>
                      < th>< /th>
                      < th>< /th>
                  < /tr>
                  <% _.each(breeds, function(breed) { %>
                  < tr data-id="<%= breed.id %>" data-model="breed">
                      < td>< strong>País:< /strong> <%= breed.country %> < /td>
                      < td>< strong>Nombre:< /strong> <%= breed.name %> < /td> 
                      < td>< strong>Color:< /strong> <%= breed.color %> < /td> 
                      < td>< /td>
                      < td>< /td>
                      < td>< a href="/pet/show/<%= pet.id %>" class="btn btn-sm btn-primary">Show< /a>< /td>
                      < td>
                          < form action="/pet/deassociateBreed/" method="POST" >                    
                              < input type="hidden" name="breed_id" value="<%= breed.id %>" />
                              < input type="hidden" name="pet_id" value="<%= pet.id %>" />
                              < input type="submit" value="Desasociar Raza" class="btn btn-sm "/>
                              < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                          < /form>
                      < /td>
                  < /tr>
                  <% }) %>
              < /table>
              < hr>
            < a class="btn btn-medium btn-primary" href="/pet/edit/<%= pet.id %>">Edit< /a> 
          < /div>

Ahora crearemos el fichero asssociateBreed.ejs dentro de la carpeta pet, con el siguiente código:


          < div class='container'>
            < h3>Elige la raza de la mascota< /h3>    
              < table class='table'>
                  < tr>
                      < th>País< /th>
                      < th>Nombre< /th>
                      < th>Color< /th>
                      < th>< /th>
                      < th>< /th>
                  < /tr>
                  <% _.each(breeds, function(breed) { %>
                  < tr data-id="<%= breed.id %>" data-model="breed">
                      < td><%= breed.country %>< /td>
                      < td><%= breed.name %>< /td>
                      < td><%= breed.color %>< /td>
                      < td>
                          < form action="/pet/associateBreedToPet/" method="POST" >                    
                              < input type="hidden" name="breed_id" value="<%= breed.id %>" />
                              < input type="hidden" name="pet_id" value="<%= pet_id %>" />
                              < input type="submit" value="Asociar" class="btn btn-sm"/>
                              < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                          < /form>
                      < /td>
                  < /tr>
                  <% }) %>
              < /table>
              < br />
          < /div>

Ahora ya podemos lanzar nuestro servidor y asociar tantas razas a nuestra mascota como queramos.

Nota: En este caso no tendrá problemas en que varias mascotas tengan las mismas razas.

2.1) Adaptador con Dominancia

Es fácil ver lo que está pasando en esta relación. Hay una relación muchos-a-muchos entre los usuarios y los productos. De hecho, puede imaginarse que existiese otras relaciones (como por ejemplo, compras), pero eso sería probablemente mejor representarlo mediante un modelo intermediario. "ProductUser", si se me permite la nomenclatura orientada SQL. Sabemos que va a terminar en un lado o el otro, pero lo que si queremos controlar en qué base de datos termina deberemos elegir cual de las dos es la dominante.

Con el tiempo, puede incluso ser posible especificar una tercera tabla de unión. Por ahora, nos centraremos en la elección de uno u otro bando.

Nos dirigimos a esto a través del concepto de "posición dominante". En cualquier relación modelo-adaptador, un lado se supone que es dominante. Puede ser útil pensar en la analogía de un niño con unos padres de diferentes ciudadanías en la que tienen que elegir su ciudadanía de uno de los dos paises.

Nota: Esta analogía esta pensada en un ciudadano que no puede tener ambas nacionalidades.

Aquí está la ontología de nuevo, pero esta vez vamos a indicar la base de datos MySQL como "dominante". Esto significa que la relación "ProductUser" se almacena como una tabla de MySQL.

¿Cómo escoger la Relación Dominante?

Varios factores pueden influir en su decisión:

3.) Uno a Uno

Una asociación uno-a-uno dice que un modelo sólo puede asociarse con otro modelo. Para que el modelo sepa a que otro modelo que está asociado, debe ser incluid auna clave externa en el registro.

Como hemos visto en el UML, 1 Mascota puede tener 1 Foto. Para ello haremos lo siguiente en los respectivos modelos.

En el modelo Pet añadiremos lo siguiente, quedando el modelo de la siguiente manera:


          /**
          * Pet.js
          *
          * @description :: TODO: You might write a short summary of how this model works and what it represents here.
          * @docs        :: http://sailsjs.org/#!documentation/models
          */

          module.exports = {

            attributes: {
              name: {
                type: 'string',
                required: true
              },
              
              id_chip: {
                type: 'string',
                required: true,
                unique: true
              },

              // ASSOCIATIONS

              owner: {
                model: 'User'
              },

              pet_breed: {
                collection: 'Breed',
                via: 'breed'
              },

              pet_image: {
                model: 'File'
              },

              // JSON

              toJSON: function() {
                var obj = this.toObject();
                delete obj._csrf;
                return obj;
              }
            }
          };

En el modelo File añadiremos lo siguiente, quedando el modelo de la siguiente manera:


          /**
          * File.js
          *
          * @description :: TODO: You might write a short summary of how this model works and what it represents here.
          * @docs        :: http://sailsjs.org/#!documentation/models
          */

          module.exports = {

            attributes: {

              // Los atributos se crean en el controlador al subir el fichero

              // ASSOCIATIONS

              image: {
                model: 'Pet'
              },

              toJSON: function() {
                    var obj = this.toObject();
                    delete obj._csrf;
                    delete obj.createdAt;
                    delete obj.updatedAt;
                    return obj;
              }

            }
          };

Ahora añadiremos las acciones para asociar los modelos en su controlador (PetController.ejs):


          // ASSOCIATE PET - FILE (1-1)

          associateFile: function(req, res, next) {
            File.find(function foundFiles(err, files) {
              if (err) return next(err);
              res.view({
                files: files,
                pet_id: req.param('id')
              });
            });
          },

          deassociateFile: function(req,res,next){
            var file_id = req.param('file_id');
            var pet_id = req.param('pet_id');
            
            File.findOne(breed_id, function foundFile(err, file) {
              if (err) return next(err);
              if (!file) return next();

              file.image.remove(pet_id);
              file.save(function createDeassociation(err, saved) {
                
                if (err) res.redirect('/pet/show/' + pet_id);
                res.redirect('/pet/show/' + pet_id);
              });
            });
          },
              
          associateFileToPet: function(req,res,next){
            var file_id = req.param('file_id');
            var pet_id = req.param('pet_id');

            File.findOne(file_id, function foundFile(err, file) {
              if (err) return next(err);
              if (!file) return next();

              Pet.update(pet_id, {pet_image:file}, function petUpdated(err) {
                  if (err) {
                    return res.redirect('/pet/associateFile/');
                  }
                  res.redirect('/pet/show/' + pet_id);
              });
            });
          }

También tendremos que modificar la acción show, ya que tendrá que mostrar la imagen asociada:


          show: function(req, res) {
            Pet.findOne(req.param('id'))
            .populate('pet_breed')
            .populate('pet_image')
            .exec(function(err,pet) {
              res.view({
                breeds: pet.pet_breed,
                files: [pet.pet_image],
                // Fijese que lo hemos convertido en un array para que lo renderice como siempre, con _.each()
                pet: pet
              });
            });
          }

Ya tenemos la relación 1 a 1 entre Mascota e Imagen. Pero nos falta poder asociarlos mediante botones en el html. Para ello, primero modificaremos el fichero show.ejs de la carpeta pet:


          < div class='container'>
            < a class="btn btn-medium btn-primary" href="/pet/">Mascotas< /a>
            < br />
            < div class="hero-unit center">
                  < form class="form-signin" >
                      < h1>Info:< /h1>
                      < p>< strong>Chip:< /strong> <%= pet.id_chip %> < /p>
                      < p>< strong>Nombre:< /strong> <%= pet.name %> < /p> 
                  < /form>
              < /div>
              < hr>
              < h3>Razas< /h3> < a href="/pet/associateBreed/<%= pet.id %>">Asociar Nueva Raza< /a>
              < table class='table'>
                  < tr>
                      < th>País< /th>
                      < th>Nombre< /th>
                      < th>Color< /th>
                      < th>< /th>
                      < th>< /th>
                      < th>< /th>
                      < th>< /th>
                  < /tr> 
                  <% _.each(breeds, function(breed) { %>
                  < tr data-id="<%= breed.id %>" data-model="breed">
                      < td><%= breed.country %> < /td>
                      < td><%= breed.name %> < /td> 
                      < td><%= breed.color %> < /td> 
                      < td>< /td>
                      < td>< /td>
                      < td>< a href="/pet/show/<%= pet.id %>" class="btn btn-sm btn-primary">Show
                      < td>
                          < form action="/pet/deassociateBreed/" method="POST" >                    
                              < input type="hidden" name="breed_id" value="<%= breed.id %>" />
                              < input type="hidden" name="pet_id" value="<%= pet.id %>" />
                              < input type="submit" value="Desasociar Raza" class="btn btn-sm "/>
                              < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                          < /form>
                      < /td>
                  < /tr>
                  <% }) %>
              < /table>
              < hr>
              
              < h3>Ficheros< /h3> < a href="/pet/associateFile/<%= pet.id %>">Asociar Nuevo Fichero< /a>
              < table class='table'>
                  < tr>
                      < th>Nombre< /th>
                      < th>Descripción< /th>
                      < th>Formato< /th>
                      < th>Tamaño< /th>
                      < th>Path completo< /th>
                      < th>Versión< /th>
                      < th>< /th>
                      < th>< /th>
                  < /tr>
                  <% _.each(files, function(file) { %>
                  < tr data-id="<%= file.id %>" data-model="file">
                      < td><%= file.name %>< /td>
                      < td><%= file.description %>< /td>
                      < td><%= file.file %>< /td>
                      < td><%= file.fileSize %>< /td>
                      < td><%= file.fullPath %>< /td>
                      < td><%= file.version %>< /td>
                      < td>< a href="/pet/show/<%= pet.id %>" class="btn btn-sm btn-primary">Show< /a>< /td>
                      < td>
                          < form action="/pet/deassociateFile/" method="POST" >                    
                              < input type="hidden" name="file_id" value="<%= file.id %>" />
                              < input type="hidden" name="pet_id" value="<%= pet.id %>" />
                              < input type="submit" value="Desasociar Imagen" class="btn btn-sm "/>
                              < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                          < /form>
                      < /td>
                  < /tr>
                  <% }) %>
              < /table>
              < hr>
              
            < a class="btn btn-medium btn-primary" href="/pet/edit/<%= pet.id %>">Edit< /a> 
          < /div>

Ahora crearemos el fichero asssociateFile.ejs dentro de la carpeta pet, con el siguiente código:


          < div class='container'>
            < h3>Elige el fichero de la mascota< /h3>    
              < table class='table'>
                  < tr>
                      < th>Nombre< /th>
                      < th>Descripción< /th>
                      < th>Formato< /th>
                      < th>Tamaño< /th>
                      < th>Path completo< /th>
                      < th>Versión< /th>
                      < th>< /th>
                      < th>< /th>
                  < /tr>
                  <% _.each(files, function(file) { %>
                  < tr data-id="<%= file.id %>" data-model="file">
                      < td><%= file.name %>< /td>
                      < td><%= file.description %>< /td>
                      < td><%= file.file %>< /td>
                      < td><%= file.fileSize %>< /td>
                      < td><%= file.fullPath %>< /td>
                      < td><%= file.version %>< /td>
                      < td>
                          < form action="/pet/associateFileToPet/" method="POST" >                    
                              < input type="hidden" name="file_id" value="<%= file.id %>" />
                              < input type="hidden" name="pet_id" value="<%= pet_id %>" />
                              < input type="submit" value="Asociar" class="btn btn-sm"/>
                              < input type="hidden" name="_csrf" value="<%= _csrf %>" />
                          < /form>
                      < /td>
                  < /tr>
                  <% }) %>
              < /table>
              < br />
          < /div>

Ahora ya podemos lanzar nuestro servidor y asociar una imagen a nuestra mascota.

Nota: YA HEMOS FINALIZADO EL TUTORIAL!!!.

4.) En una sola dirección

Una asociación es donde un modelo se asocia con otro modelo. Usted podría consultar ese modelo y probar para obtener el modelo asociado. Sin embargo consultar el modelo asociado y poblar conseguir el modelo que asocia.

Implementar la aplicación en Eroku

Aquí teneis un enlace para ver como implementar nuestro servidor en heroku.

Cómo hacer que nuestro servidor se levante solo tras caerse

Existe un módulo que podemos añadir a nuestro proyecto y server para que siempre esté en ejecución nuestro servidor, en el que si algún momento se cae, se vuelva a levantar automáticamente. Y ese módulo se llama "forever". Si deseais más información sobre ese módulo podeis hacerlo aquí.

Instalar Forever

Para instalarlo solo debe escribir lo siguiente en la consola:

sudo npm install forever --global

Ejecutarlo Siempre

Para ejecutarlo siempre ejecutaremos el siguiente comando:

forever start nombre_de_la_app.js

Para detenerlo ejecutaremos el siguiente comando:

forever stop 0

Podemos crear un fichero de prueba para probar su funcionamiento y entenderlo mejor, para ello crearemos el fichero "test.js" con el siguiente código:


          var util = require('util'),
          http = require('http');

          http.createServer(function (req, res) {
             res.writeHead(200, {'Content-Type': 'text/plain'});
             res.write('hello, i know nodejitsu.')
             res.end();
          }).listen(8000);
          

Lo lanzamos con: forever start test.js y vemos que el servidor está siempre en funcionamiento.

Devolver JSON

Para que devuelva un JSON tan solo tendremos que poner: res.json(user);

La diferencia que hemos estado haciendo es que en vez de devolver un JSON es que renderice la vista con los atributos que le pasamos: res.view({ user: user });.


          resJSON: function(req, res) {
            User.findOne(req.param('id'))
            .populate('pets')
            .exec(function(err,user) {
                res.json(user);
                });
          }

Crear una diferencia entre admin y usuarios

Si desea añadir un admin y diferenciarse de los usuarios normales puede ver como hacerlo aquí.

Socket.io Eventos en tiempo real

Understanding websockets and socket.io

Setting up the sign-in and sign-out ui

Adding real-time events to models in 4 lines of code

Integrating socket.io and sails using real time model events

Manipulating the DOM via Real Time Model Events

Adding real time flash messages using real-time model events

Proyecto Finalizado

Aquí puede descargar el proyecto finalizado.

Si lo desea tb puede descargar el proyecto en el que está implementado los eventos en tiempo real con socket.io aquí.

También está disponible en github. Cuando os lo descargueis de github recordad que tendreis que instalar todos los módulos, así que tan solo debereis estar en el path donde haceis sails lift y escribir el siguiente comando:

npm install