Skip to content

Commit 899e7b7

Browse files
committed
* feature : Added constraintProfiles to allow you to define which fields to validate according to defined profiles: #37
1 parent a618ed7 commit 899e7b7

13 files changed

+321
-141
lines changed

changelog.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
## 2.1.0
44

5+
* `feature` : Added `constraintProfiles` to allow you to define which fields to validate according to defined profiles: https://github.com/coldbox-modules/cbvalidation/issues/37
56
* `feature` : Updated `RequiredUnless` and `RequiredIf` to use struct literal notation instead of the weird parsing we did.
67
* `feature` : Added the `Unique` validator thanks to @elpete!
7-
* `improvement` : Added `null` support for the `RequiredIf,RequiredUnless` validators
8+
* `improvement` : Added `null` support for the `RequiredIf,RequiredUnless` validator values
89

910
## 2.0.0
1011

@@ -13,8 +14,8 @@
1314
* No more manual discovery of validators, automated registration and lookup process, cleaned lots of code on this one!
1415
* New Validator: `Accepted` - The field under validation must be yes, on, 1, or true. This is useful for validating "Terms of Service" acceptance.
1516
* New Validator: `Alpha` - Only allows alphabetic characters
16-
* New Validator: `RequiredUnless` with validation data: `anotherField:value,...` - The field under validation must be present and not empty unless the `anotherfield` field is equal to the passed `value`.
17-
* New Validator: `RequiredIf` with validation data: `anotherField:value,...` - The field under validation must be present and not empty if the `anotherfield` field is equal to the passed `value`.
17+
* New Validator: `RequiredUnless` with validation data as a struct literal `{ anotherField:value, ... }` - The field under validation must be present and not empty unless the `anotherfield` field is equal to the passed `value`.
18+
* New Validator: `RequiredIf` with validation data as a struct literal `{ anotherField:value, ... }` - The field under validation must be present and not empty if the `anotherfield` field is equal to the passed `value`.
1819
* Accelerated validation by removing type checks. ACF chokes on interface checks
1920

2021
### Improvements

models/Mixins.cfm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* @locale The i18n locale to use for validation messages
1010
* @excludeFields The fields to exclude from the validation
1111
* @includeFields The fields to include in the validation
12+
* @profiles If passed, a list of profile names to use for validation constraints
1213
*
1314
* @return cbvalidation.model.result.IValidationResult
1415
*/
@@ -26,6 +27,7 @@ function validate(){
2627
* @locale The i18n locale to use for validation messages
2728
* @excludeFields The fields to exclude from the validation
2829
* @includeFields The fields to include in the validation
30+
* @profiles If passed, a list of profile names to use for validation constraints
2931
*
3032
* @return The validated object or the structure fields that where validated
3133
* @throws ValidationException

models/ValidationManager.cfc

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ component accessors="true" serialize="false" singleton {
117117
* @locale An optional locale to use for i18n messages
118118
* @excludeFields An optional list of fields to exclude from the validation.
119119
* @IncludeFields An optional list of fields to include in the validation.
120+
* @profiles If passed, a list of profile names to use for validation constraints
120121
*
121122
* @return IValidationResult
122123
*/
@@ -126,7 +127,8 @@ component accessors="true" serialize="false" singleton {
126127
any constraints = "",
127128
string locale = "",
128129
string excludeFields = "",
129-
string includeFields = ""
130+
string includeFields = "",
131+
string profiles = ""
130132
){
131133
var targetName = "";
132134

@@ -152,10 +154,38 @@ component accessors="true" serialize="false" singleton {
152154
locale : arguments.locale,
153155
targetName : targetName,
154156
resourceService : resourceService,
155-
constraints : allConstraints
157+
constraints : allConstraints,
158+
profiles : arguments.profiles
156159
}
157160
);
158161

162+
// Discover profiles, and update the includeFields list from it
163+
if( len( arguments.profiles ) ){
164+
arguments.includeFields = arguments.profiles
165+
.listToArray()
166+
// Check if profiles defined in target and iterated one exists
167+
.filter( function( profileKey ){
168+
return structKeyExists( target, "constraintProfiles" ) && structKeyExists( target.constraintProfiles, profileKey );
169+
} )
170+
// Incorporate fields from each profile
171+
.map( function( profileKey ){
172+
// iterate all declared profile fields and incorporate into the includeFields
173+
return target.constraintProfiles
174+
.find( arguments.profileKey )
175+
.listToArray();
176+
} )
177+
// Reduce all fields into a single hashset to do a distinct collection
178+
.reduce( function( result, item ){
179+
item
180+
.each( function( thisField ){
181+
result.add( thisField );
182+
} );
183+
return result;
184+
}, createObject( "java", "java.util.HashSet" ) )
185+
.toArray();
186+
arguments.includeFields = arrayToList( arguments.includeFields );
187+
}
188+
159189
// iterate over constraints defined
160190
for ( var thisField in allConstraints ) {
161191
var validateField = true;
@@ -193,6 +223,7 @@ component accessors="true" serialize="false" singleton {
193223
* @locale An optional locale to use for i18n messages
194224
* @excludeFields An optional list of fields to exclude from the validation.
195225
* @IncludeFields An optional list of fields to include in the validation.
226+
* @profiles If passed, a list of profile names to use for validation constraints
196227
*
197228
* @throws ValidationException
198229
* @return any,struct: The target object that was validated, or the structure fields that where validated.
@@ -203,7 +234,8 @@ component accessors="true" serialize="false" singleton {
203234
any constraints = "",
204235
string locale = "",
205236
string excludeFields = "",
206-
string includeFields = ""
237+
string includeFields = "",
238+
string profiles = ""
207239
){
208240
var vResults = this.validate( argumentCollection = arguments );
209241

models/result/ValidationResult.cfc

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,49 @@
77
component accessors="true" {
88

99
/**
10-
* A collection of error objects represented in this result object
11-
*/
10+
* A collection of error objects represented in this result object
11+
*/
1212
property name="errors" type="array";
1313

1414
/**
15-
* Extra metadata you can store in the results object
16-
*/
15+
* Extra metadata you can store in the results object
16+
*/
1717
property name="resultMetadata" type="struct";
1818

1919
/**
20-
* The locale this result validation is using
21-
*/
20+
* The locale this result validation is using
21+
*/
2222
property name="locale" type="string";
2323

2424
/**
25-
* The name of the target object
26-
*/
25+
* The name of the target object
26+
*/
2727
property name="targetName" type="string";
2828

2929
/**
30-
* The constraints evaluated in the validation process
31-
*/
30+
* The constraints evaluated in the validation process
31+
*/
3232
property name="constraints" type="struct";
3333

3434
/**
35-
* The resource bundle object
36-
*/
35+
* The resource bundle object
36+
*/
3737
property name="resourceService";
3838

3939
/**
40-
* Constructor
41-
*/
40+
* The profiles used in the validation
41+
*/
42+
property name="profiles" type="string";
43+
44+
/**
45+
* Constructor
46+
*/
4247
ValidationResult function init(
4348
string locale = "",
4449
string targetName = "",
4550
any resourceService = "",
46-
struct constraints = structNew()
51+
struct constraints = structNew(),
52+
string profiles = ""
4753
){
4854
variables.errors = [];
4955
variables.resultMetadata = {};
@@ -53,6 +59,7 @@ component accessors="true" {
5359
variables.targetName = arguments.targetName;
5460
variables.resourceService = arguments.resourceService;
5561
variables.constraints = arguments.constraints;
62+
variables.profiles = arguments.profiles;
5663
return this;
5764
}
5865

test-harness/handlers/Main.cfc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ component{
5757
}
5858

5959
/**
60-
* validateOrFailWithObject
61-
*/
60+
* validateOrFailWithObject
61+
*/
6262
function validateOrFailWithObject( event, rc, prc ){
6363

6464
var oModel = populateModel( "User" );
@@ -69,6 +69,19 @@ component{
6969
return "Validated";
7070
}
7171

72+
/**
73+
* validateOrFailWithObjectProfiles
74+
*/
75+
function validateOrFailWithProfiles( event, rc, prc ){
76+
77+
var oModel = populateModel( "User" );
78+
79+
// validate
80+
prc.object = validateOrFail( target=oModel, profiles=rc._profiles );
81+
82+
return "Validated";
83+
}
84+
7285

7386
// Run on first init
7487
any function onAppInit( event, rc, prc ){

test-harness/models/User.cfc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
component accessors="true"{
1+
component accessors="true" {
22

33
property name="username" default="";
44
property name="password" default="";
5+
property name="email" default="";
56

7+
this.constraintProfiles = {
8+
new : "username,password,email",
9+
update : "username,email"
10+
};
611
this.constraints = {
7-
username = {required=true, size="2..20"},
8-
password = {required=true, size="2..20"}
12+
username : { required : true, size : "2..20" },
13+
password : { required : true, size : "2..20" },
14+
email : { required : true, type : "email" }
915
};
1016

1117
function init(){
1218
return this;
1319
}
1420

15-
}
21+
}

test-harness/tests/specs/ValidationIntegrations.cfc

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
1717
function run(){
1818
describe( "Integrations Specs", function(){
1919
beforeEach( function( currentSpec ){
20+
structDelete( application, "cbController" );
21+
structDelete( application, "wirebox" );
2022
// Setup as a new ColdBox request, VERY IMPORTANT. ELSE EVERYTHING LOOKS LIKE THE SAME REQUEST.
2123
setup();
2224
} );
@@ -71,6 +73,7 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
7173
params = {
7274
username : "luis",
7375
password : "luis",
76+
email : "[email protected]",
7477
bogus : now(),
7578
anotherBogus : now()
7679
},
@@ -83,7 +86,63 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
8386
} );
8487
} );
8588
} );
89+
90+
91+
story( "I want to Validate with contraint profiles", function(){
92+
given( "a single profile", function(){
93+
then( "it must validate it with only those fields in that profile", function(){
94+
var e = this.request(
95+
route = "/main/validateOrFailWithProfiles",
96+
params = {
97+
username : "luis",
98+
email : "[email protected]",
99+
bogus : now(),
100+
anotherBogus : now(),
101+
_profiles : "update"
102+
},
103+
method = "post"
104+
);
105+
106+
var object = e.getPrivateValue( "object" );
107+
debug( object );
108+
expect( object ).toBeComponent();
109+
});
110+
});
111+
given( "a multiple profiles", function(){
112+
then( "it must validate it with only distinct fields in those profiles", function(){
113+
var e = this.request(
114+
route = "/main/validateOrFailWithProfiles",
115+
params = {
116+
username : "luis",
117+
email : "[email protected]",
118+
password : "luis",
119+
anotherBogus : now(),
120+
_profiles : "update,new,bogus"
121+
},
122+
method = "post"
123+
);
124+
125+
var object = e.getPrivateValue( "object" );
126+
debug( object );
127+
expect( object ).toBeComponent();
128+
});
129+
});
130+
given( "a single profile and invalid data", function(){
131+
then( "then throw the exception", function(){
132+
133+
expect( function(){
134+
this.request( route = "/main/validateOrFailWithProfiles", params = {
135+
username : "luis"
136+
}, method = "post" );
137+
} ).toThrow();
138+
139+
});
140+
});
141+
});
142+
143+
86144
} );
145+
87146
}
88147

89148
}

0 commit comments

Comments
 (0)