Grails Shared Custom Validator - customvalidator

I have multiple domain objects with differently names start and end dates. I would like to create a shared custom validator that accepts two dates and validates them.
All the documentation seems to state the second argument in the validator closure is the current object. Is there a way to do the following? If so how would the constraint closure in the domain object look?
grails.gorm.default.constraints = {
'*'(nullable: true)
endDateValidator(
validator: { endDateValue, startDateValue ->
if(!(endDateValue?.after(startDateValue))){
return 'end.date.before.start.date.error'
}
else{
return true
}
}
)
}

I guess you still need to create a validator for each domain class since each of them has differently named properties. However, you can share the validation business logic among these validators.
Example:
The shared validation logic would go to a static boolean validateDateOrdering(Date first, Date second) method and validators might look like:
fooValidator(
validator: { value, domainInst ->
validateDateOrdering(value, domainInst['bar'])
}
)
barValidator(
validator: { value, domainInst ->
validateDateOrdering(domainInst['foo'], value)
}
)

Related

Override sails update to return just one object

When I update a model, waterlock .update() always return an array of objects, even if I set on criteria a primaryKey.
on my code
Ad.update({ id: req.param('id') }, {
// desired attributed to be updated
}).exec(function(err, updatedRecord) {
// updatedRecord is always an array of objects
});
And in order to use the updatedRecord, I have to point out to 0 index like updatedRecord[0] which is something I consider not very clean. According to docs update() in sails, this is a common escenario.
Knowing that, I have 2 questions:
Wouldn't be better that when you find one model return just a updated object for that model, not an array?
If that is a convention, how could be overrided this function in order to return just an object instead of an array when .update() have only affected one record?
it is a convention that it will update all the records that matches the find criteria, but as you are probably using a unique validation on model, it will probably return an array of 1 or 0. You need to do it on hand.
You can override methods in model, by implementing a method with same name as waterline default. But as you will need to completely rewrite the code, it is not viable. Neither changing waterline underlying code.
A solution will be creating a new function on your Ad model:
module.exports = {
attributes: {
adid: {
unique: true,
required: true
},
updateMe: {
}
},
updateOne: function(adid, newUpdateMe, cb){
Ad.update({ id: req.param('id') }, {
// desired attributed to be updated
}).exec(function(err, updatedRecord) {
// updatedRecord is always an array of objects
if (updatedRecord.length == 1){
return cb(null, updatedRecord[0]);
}
return cb(null, {}); //also can error if not found.
});
}
};
Also. Avoid using id as an model attribute (use other name), as some databases like mongodb already add this attribute as default and may cause conflicts with your model.
I dont think its possible with waterline. Its because update method is a generalized one, passing a primary key in where condition is always not the case.

CakePHP magic find inflects to lower case

I'm migrating to postgres from mysql and having an issue with "magic find" and case sensitivity in Postgres.
Model->findByFirstname inflects to "Model"."firstname" and Postgres can't find that column, since it's named "Firstname" in my db.
Any ideas?
You can override the magic handling of FindBy<Fieldname> by adding the PHP handler __call to your AppModel base class. This will be called for each magic find method.
The first parameter is the function name, and the second parameter are the function arguments.
public function __call($method, $params)
{
if (strpos($method, 'findBy') === 0 || strpos($method, 'findAllBy') === 0)
{
// customer handler here
return ....
}
return parent::__call($method,$params);
}
For your custom handler. You can do something like return $this->find(...). Where you apply conditions that are for findBy or findAllBy. Except use the case sensitive field name for the condition. That field name can be extracted from $method.

Validate associated models in CakePHP2

I'm a noob in CakePHP and I've been trying to do some complex validations here:
I have the following models:
- Fonts (name, file);
- Settings(value1,value2,value3,type_id,script_id);
- Types(name)
Whenever I create a Font I also create a default setting associated to it. Also, this setting has a type associated. After the Font is created I can associate more settings to it (Font hasMany Settings), but I need to make sure that two settings of the same type are not added to that font. I don't know how to handle this case. Any help is appreciated. Thanks.
I'd use a simple beforeSave validation
//in setting.php model
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['font_id']) && isset($this->data[$this->alias]['type_id']) {
$otherSettings = $this->find('all', array('conditions'=>
array('type_id'=>$this->data[$this->alias]['type_id'],
'font_id'=>$this->data[$this->alias]['font_id']);
//check if it's insert or update
$updated_id = null;
if ($this->id)
$updated_id = $this->id;
if (isset($this->data[$this->alias][$this->primaryKey]))
$updated_id = $this->data[$this->alias][$this->primaryKey];
if (count($otherSettings) > 0) {
if ($updated_id == null)
return false; //it's not an update and we found other records, so fail
foreach ($otherSettings as $similarSetting)
if ($updated_id != $similarSetting['Setting']['id'])
return false; //found a similar record with other id, fail
}
}
return true; //don't forget this, it won't save otherwise
}
That will prevent inserting new settings to the same font with the same type. Have in mind that this validation will return false if the validation is incorrect, but you have to handle how you want to alert the user of the error. You can throw exceptions from the beforeSave and catch them in the controller to display a flash message to the user. Or you could just not save those settings and let the user figure it out (bad practice).
You could also create a similar function in the model like checkPreviousSettings with a similar logic as the one I wrote above, to check if the settings about to be saved are valid, if not display a message to the user before attempting a save.
The option I prefer is the exception handling error, in that case you'd have to replace the return false with
throw new Exception('Setting of the same type already associated to the font');
and catch it in the controller.
Actually, the better approach is to not even display the settings with the same type and font to the user, so he doesn't even have the option of choosing. But this behind-the-scenes validation would also be needed.

How to validate a pair of values from the same Model?

Use case
My use case is that I need to validate a Table Tennis score.
Form
<input name="data[MatchesPlayer][1][score]" type="number" id="MatchesPlayer1Score">
<input name="data[MatchesPlayer][2][score]" type="number" id="MatchesPlayer2Score">
Constraints
One score must be bigger than 11.
One score must be 2 points or greater than the other if the score is higher than 11.
Problem
When cake validates multiple rows from the same model, the model data is set to that record. This means that it's not possible to compare the two values as they aren't both available in $this->data. As I am using saveAll() each record is set to the model and then validated before it's saved.
Question
I'd like to know if there is a good way to validate this pair of data without resorting to saving it into the session or similar before I can validate it.
What I normally do here is I create a wrapper for the save method. This allows me to perform custom manipulation that would otherwise not be possible with model callbacks, or even use custom transactions etc.
In your case, it would be something like:
class MatchesPlayer extends Model {
protected $_saveData = null;
public function updateScore($data) {
$this->_saveData = $data;
try {
// You can use saveAll to validate
// only, and not actually save
$saved = $this->saveAll($data, array('validate' => 'only'));
} catch (Exception $e) {
// Catch exceptions here in case the
// saveAll is instead something that throws Exceptions
// Or your database uses exceptions
$saved = false;
}
$this->_saveData = null;
return $saved
}
}
You could then use $this->_saveData across the model. If you want to be clever with this, you could detect all sub-models that are being saved in the $data and then set the $this->_saveData on those as well - I would make this an AppModel method of course.
Note that you may want to throw exceptions from this updateScore() method when validation fails. Throwing an exception if validation fails - vs save - would allow you to set a custom flash message for the user as well, or even have an api that responds with a different status code.
Use custom validation rules in MatchesPlayer model, please check
http://book.cakephp.org/2.0/en/models/data-validation.html#adding-your-own-validation-methods

Cakephp check if model exists

I am creating an application in which I am using two plugins.
For future use I want to check whether the two plugins are being used together or separately.
I need to check if the model exists and if so perform some logic and if not - not.
If I try if($this->loadModel('Model')) { etc }
I get an error saying the model does not exist which is what I want but I don't want an error which prevents the logic from proceeding.
Basically I want:
if(Model->exists()) { do->this }
else { do->somethingelse }
I tried using the php function class_exists() but that returns false regardless of whether the Model exists or not.
I would use App::objects('model') as of 2.x (Not sure when this was implemented).
class AppController extents Controller {
private function _modelExists($modelName){
$models = App::objects('model');
return in_array($modelName,$models);
}
}
//Somewhere in your logic
if($this->_modelExists('SomeModel')){
//do model exists logic
} else {
//do other logic
}
*Note that App::objects('model') will not include models from plugins. You could do:
$models = array_merge(
App::objects('model'),
App::objects('MyPlugin.model')
);
You can also do this with pure php as follows
if(class_exists('SomeModel')){
//do model exists logic
} else {
//do other logic
}
// The pitfall of this approach, is that it will not assure
// that `SomeModel is a decedent of the `Model` class.
You can do this :
$model = ClassRegistry::init("User");
if $model is null this means that the User model does not exist
You can do this from every where in the code

Resources