Rob Sawyer
Rob Sawyer

Reputation: 2193

Invalid attributes sent to model. Validation issues

I'm using the validation service from https://gist.github.com/basco-johnkevin/8436644 and currently having some issues upon submission of my data. It seems like maybe the validation service doesn't like NaN values or I'm possibly missing something. Any ideas?

My form:

<div class="container">
    <form action="/standardplacement/create" method="POST" role="form">
        <div class="row-fluid">
            <h2 class="form-signin-heading">Create a <em>Standard Media</em> placement</h2>
            <% if (req.session.flash && req.session.flash.error) { %>
                <% var errors = req.flash('error') %>
                <div class="alert alert-danger">
                    <button type="button" class="close" aria-hidden="true" data-dismiss="alert">&times;</button>
                    <ul>
                    <% Object.keys(errors).forEach(function(error) { %>
                        <% Object.keys(errors[error]).forEach(function(error_message) { %>
                            <% Object.keys(errors[error][error_message][0]).forEach(function(error_message_res) { %>
                                <li><%- errors[error][error_message][0][error_message_res] %></li>
                            <% }); %>
                        <% }); %>
                    <% }); %>
                    </ul>
                </div>

                <% var t_results = req.flash('temp_results'); %>
                <% if(t_results[0]){ %>
                    <% 
                    t_results = t_results[0];
                    sails.log.info("Results Restored");
                    %>
                <% } %>
            <% } %>
        </div>
        <div class="row-fluid">
            <div class="form-group"> 
                <label for="pName">Placement Name</label>
                <input type="text" name="name" id="pName" class="form-control" required="required" title="Name" placeholder="12/27 Homepage 300x250 Banner" />   
            </div>
        </div>

        <div class="row-fluid">
            <blockquote>
                <p class="lead">General Specifications</p>
            </blockquote>
        </div>
        <div class="row-fluid">
            <div class="form-group row">
                <div class="col-xs-4">
                    <label for="pWidth">Width</label>
                    <div class="input-group">
                        <input type="integer" name="width" id="pWidth" class="form-control" title="Width (in PX)" placeholder="300" />   
                        <div class="input-group-addon">px</div>
                    </div>
                </div>
                <div class="col-xs-4">
                    <label for="pHeight">Height</label>
                    <div class="input-group">
                        <input type="integer" name="height" id="pHeight" class="form-control" title="Height (in PX)" placeholder="250" />  
                        <div class="input-group-addon">px</div>
                    </div> 
                </div>
            </div>
        </div>

        <br/>
        <div class="row-fluid">
            <div class="form-group">
                <button type="submit" class="btn btn-lg btn-primary" type="submit">Create Placement</button>
                <input type="hidden" name="_csrf" value="<%= _csrf %>" />
            </div>
        </div>
    </form>
</div>

My controller:

create: function(req, res){

    StandardPlacement.create( req.params.all() ).exec( function (error, placement) {

        sails.log( req.params.all() );

        if(error) {
            sails.log(error);
            sails.log(error.ValidationError);
            if(error.ValidationError) {
                error_object = ValidationService.transformValidation(StandardPlacement, error.Validation);
                sails.log.warn(error_object);

                res.send({result: false, errors: error_object});
            }
        }

        // After successfully creating the placement 
        // redirect the user to the show action
        res.redirect('/standardplacement/show/' + placement.id);
    });

},

My model:

module.exports = {

  attributes: {
    name: {
        type: 'string',
      minLength: 2,
      required: true
    },
    width: {
        type: 'integer',
        required: true
    },
    height: {
        type: 'integer',
        required: true
    }
  },

  validation_messages: {
    name: {
      required: 'You must supply a valid name for the placement. If you do not have a specific name, make up one.',
      minLength: 'The name must be more than one character long.'
    },
    width: {
        required: 'You must supply a width value in pixels.'
    },
    height: {
        required: 'You must supply a height value in pixels.'
    },
  }

};

The service in api/services/ValidationService.js:

/**
 * Takes a Sails Model object (e.g. User) and a ValidationError object and translates it into a friendly
 * object for sending via JSON to client-side frameworks.
 *
 * To use add a new object on your model describing what validation errors should be translated:
 *
 * module.exports = {
 *   attributes: {
 *     name: {
 *       type: 'string',
 *       required: true
 *     }
 *   },
 * 
 *   validation_messages: {
 *     name: {
 *       required: 'you have to specify a name or else'
 *     }
 *   }  
 * };
 *
 * Then in your controller you could write something like this:
 *
 * var validator = require('sails-validator-tool');
 *
 * Mymodel.create(options).done(function(error, mymodel) {
 *   if(error) {
 *     if(error.ValidationError) {
 *       error_object = validator(Mymodel, error.Validation);
 *       res.send({result: false, errors: error_object});
 *     }
 *   }
 * });
 *
 * @param model {Object} An instance of a Sails.JS model object.
 * @param validationErrors {Object} A standard Sails.JS validation object.
 *
 * @returns {Object} An object with friendly validation error conversions.
 */ 
exports.transformValidation = function(model, validationError) {
  sails.log.info("Validating Submission");

  var validation_response = {};
  var messages = model.validation_messages;
  validation_fields = Object.keys(messages);

  validation_fields.forEach(function(validation_field) {

    if(validationError[validation_field]) {
      var processField = validationError[validation_field];
      processField.forEach(function(rule) {
        if(messages[validation_field][rule.rule]) {
          if(!(validation_response[validation_field] instanceof Array)) {
            validation_response[validation_field] = new Array();
          }
          var newMessage = {};
          newMessage[rule.rule] = messages[validation_field][rule.rule];
          validation_response[validation_field].push(newMessage);
        }
      });

    }
  });

  sails.log.info("Validation Complete!");
  return validation_response;
};

The error:

error: Sending 500 ("Server Error") response:
 TypeError: Cannot read property 'width' of undefined
    at /Users/me/Sites/specs/api/services/ValidationService.js:49:22
    at Array.forEach (native)
    at Object.exports.transformValidation (/Users/me/Sites/specs/api/services/ValidationService.js:47:21)
    at /Users/me/Sites/specs/api/controllers/StandardPlacementController.js:34:39
    at bound (/usr/local/lib/node_modules/sails/node_modules/lodash/dist/lodash.js:957:21)
    at applyInOriginalCtx (/usr/local/lib/node_modules/sails/node_modules/waterline/lib/waterline/utils/normalize.js:416:80)
    at wrappedCallback (/usr/local/lib/node_modules/sails/node_modules/waterline/lib/waterline/utils/normalize.js:326:16)
    at _normalizeCallback.callback.error (/usr/local/lib/node_modules/sails/node_modules/waterline/node_modules/node-switchback/lib/normalize.js:42:31)
    at _switch (/usr/local/lib/node_modules/sails/node_modules/waterline/node_modules/node-switchback/lib/factory.js:33:28)
    at /usr/local/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/dql/create.js:51:22 [TypeError: Cannot read property 'width' of undefined]

Upvotes: 1

Views: 1041

Answers (2)

Faris Rayhan
Faris Rayhan

Reputation: 4636

Simply add below line of code to see the response of your error

SomeModel.create({}).then(function(){

}).catch(function(err){
   console.log(err.ValidationError);
})

Upvotes: 0

Rob Sawyer
Rob Sawyer

Reputation: 2193

After talking to some folks in #sailsjs, I figured it out. Ended up removing the create method from the controller. Updated api/responses/badRequest.js with the following.

In badRequest.js:

  var referringView = req.header('Referer').replace(req.protocol + "://" + req.host + ":" + req.port + '/', '');
  var errors = data.invalidAttributes;
  var error_object = {};
  if(errors) {
      error_object = ValidationService.transformValidation(StandardPlacement, errors);
      return res.view(referringView, {result: false, errors: error_object});
  }

In View (read/show the errors):

<% if (res.locals && res.locals.errors) { %>
                <% var errors = res.locals.errors; %>
                <div class="alert alert-danger">
                    <button type="button" class="close" aria-hidden="true" data-dismiss="alert">&times;</button>
                    <ul>
                    <% Object.keys(errors).forEach(function(error) { %>
                        <% Object.keys(errors[error]).forEach(function(error_message) { %>
                            <% sails.log(errors[error][error_message]); %>
                            <% Object.keys(errors[error][error_message]).forEach(function(error_message_res) { %>
                                <li><%- errors[error][error_message][error_message_res] %></li>
                            <% }); %>
                        <% }); %>
                    <% }); %>
                    </ul>
                </div>

                <% var t_results = req.flash('temp_results'); %>
                <% if(t_results[0]){ %>
                    <% 
                    t_results = t_results[0];
                    sails.log.info("Results Restored");
                    %>
                <% } %>
            <% } %>

Upvotes: 2

Related Questions