Repository: TechnologyAdvice/obey (js)

Issue:
initData is not updated
When you use `requireIf`, `requireIfNot` or `equalsTo` (soon) then you may find weird issue - first value of referenced fields are not updated.

### Example

```js
let schema = obey.model({
	phone: { type: "string" },
	phoneType: { type: "string", requireIf: "phone" }
});

schema.validate({ phone: "123" }).then(() => {
	console.error('Bad: Form is invalid, you should not see this message')
})
.catch(() => {

	let muchNewSchema = obey.model({
		phone: { type: "string" },
		phoneType: { type: "string", requireIf: "phone" }
	});

	muchNewSchema.validate({}).then(() => {
		console.log('Good: Bug is fixed');
	})
	.catch((e) => {
		console.error('Bad: Form is valid, you should not see this message');
		console.error(e.message);
	})

})
```

Result:

```diff
- Bad: Form is valid, you should not see this message
- phoneType (undefined): Value required because 'phone' exists
```

The problem is when you run validation second time - `obey.rules.initData` is already set so it doesn't updated.
https://github.com/TechnologyAdvice/obey/blob/master/src/rules.js#L85

Pull request description:
Pass init data recursively
I can see only three ways to solve the problem:

1. Use global `rules.initData` (so you can fill it only once per page load)
2. Use model `initData` property (so you need to redefine it for every validation)
3. Pass the data recursively on every `validate` call (this PR)

 fixes #59

Patch:
diff --git a/API.md b/API.md
index cd90c2b..d46348c 100644
--- a/API.md
+++ b/API.md
@@ -223,7 +223,7 @@ properties during validation
 * [rules](#rules) : <code>object</code>
     * [.props](#rules.props)
     * [.makeValidate(def)](#rules.makeValidate)
-    * [.validate(def, data, [opts], [key], [errors], [rejectOnFail])](#rules.validate) ⇒ <code>Promise.&lt;\*&gt;</code>
+    * [.validate(def, data, [opts], [key], [errors], [rejectOnFail], [initData])](#rules.validate) ⇒ <code>Promise.&lt;\*&gt;</code>
     * [.build(def)](#rules.build) ⇒ <code>Object</code>
     * [.getProps(def, data)](#rules.getProps) ⇒ <code>Array</code>
 
@@ -250,7 +250,7 @@ Binds rule definition in validate method
 
 <a name="rules.validate"></a>
 
-### rules.validate(def, data, [opts], [key], [errors], [rejectOnFail]) ⇒ <code>Promise.&lt;\*&gt;</code>
+### rules.validate(def, data, [opts], [key], [errors], [rejectOnFail], [initData]) ⇒ <code>Promise.&lt;\*&gt;</code>
 Iterates over the properties present in the rule definition and sets the
 appropriate bindings to required methods
 
@@ -266,6 +266,7 @@ Rejects with a ValidationError if applicable.
 | [key] | <code>string</code> &#124; <code>null</code> | <code>null</code> | Key for tracking parent in nested iterations |
 | [errors] | <code>Array.&lt;{type: string, sub: string, key: string, value: \*, message: string}&gt;</code> | <code>[]</code> | An error array to which any additional error objects will be added. If not specified, a new array will be created. |
 | [rejectOnFail] | <code>boolean</code> | <code>true</code> | If true, resulting promise will reject if the errors array is not empty; otherwise ValidationErrors will not cause a rejection |
+| [initData] | <code>Object</code> &#124; <code>null</code> | <code></code> | Initial data object |
 
 <a name="rules.build"></a>
 
@@ -301,7 +302,7 @@ data during validation
 * [types](#types) : <code>object</code>
     * [.strategies](#types.strategies)
     * [.checkSubType(def)](#types.checkSubType) ⇒ <code>Object</code>
-    * [.validate(def, value, key, errors)](#types.validate) ⇒ <code>\*</code> &#124; <code>Promise.&lt;\*&gt;</code>
+    * [.validate(def, value, key, errors, initData)](#types.validate) ⇒ <code>\*</code> &#124; <code>Promise.&lt;\*&gt;</code>
     * [.add(name, handler)](#types.add)
     * [.check(context)](#types.check) ⇒ <code>Promise.&lt;\*&gt;</code>
 
@@ -328,7 +329,7 @@ Checks for and applies sub-type to definition
 
 <a name="types.validate"></a>
 
-### types.validate(def, value, key, errors) ⇒ <code>\*</code> &#124; <code>Promise.&lt;\*&gt;</code>
+### types.validate(def, value, key, errors, initData) ⇒ <code>\*</code> &#124; <code>Promise.&lt;\*&gt;</code>
 Sets up the `fail` method and handles `empty` or `undefined` values. If neither
 empty or undefined, calls the appropriate `type` and executes validation
 
@@ -341,6 +342,7 @@ empty or undefined, calls the appropriate `type` and executes validation
 | value | <code>\*</code> | The value being validated |
 | key | <code>string</code> | The key name of the property |
 | errors | <code>Array.&lt;{type: string, sub: (string\|number), key: string, value: \*, message: string}&gt;</code> | An error array to which any additional error objects will be added |
+| initData | <code>Object</code> | Initial data object |
 
 <a name="types.add"></a>
 
diff --git a/src/rules.js b/src/rules.js
index 46464d0..ca5ee64 100644
--- a/src/rules.js
+++ b/src/rules.js
@@ -78,11 +78,12 @@ const rules = {
    * to which any additional error objects will be added. If not specified, a new array will be created.
    * @param {boolean} [rejectOnFail=true] If true, resulting promise will reject if the errors array is not empty;
    * otherwise ValidationErrors will not cause a rejection
+   * @param {Object|null} [initData=null] Initial data object
    * @returns {Promise.<*>} Resolves with the resulting data, with any defaults, creators, and modifiers applied.
    * Rejects with a ValidationError if applicable.
    */
-  validate: (def, data, opts = { partial: false }, key = null, errors = [], rejectOnFail = true) => {
-    if (!rules.initData) rules.initData = data
+  validate: (def, data, opts = { partial: false }, key = null, errors = [], rejectOnFail = true, initData = null) => {
+    let passthruData = initData === null ? data : initData
     let curData = data
     def.opts = opts
     const props = rules.getProps(def, data)
@@ -91,7 +92,7 @@ const rules = {
     props.forEach(prop => {
       if (def.hasOwnProperty(prop.name)) {
         chain = chain
-          .then(val => prop.fn(def, val, key, errors, rules.initData))
+          .then(val => prop.fn(def, val, key, errors, passthruData))
           .then(res => {
             if (res !== undefined) curData = res
             return curData
diff --git a/src/typeStrategies/array.js b/src/typeStrategies/array.js
index d3a41df..5c1dea1 100644
--- a/src/typeStrategies/array.js
+++ b/src/typeStrategies/array.js
@@ -18,7 +18,7 @@ const array = {
     // Specific array sub-validation
     if (!context.def.values) return true
     const promises = context.value.map((elem, idx) => {
-      return rules.validate(context.def.values, elem, context.def.opts, `${context.key}[${idx}]`, context.errors, false)
+      return rules.validate(context.def.values, elem, context.def.opts, `${context.key}[${idx}]`, context.errors, false, context.initData)
     })
     return Promise.all(promises)
   }
diff --git a/src/typeStrategies/object.js b/src/typeStrategies/object.js
index 7e98904..13a106e 100644
--- a/src/typeStrategies/object.js
+++ b/src/typeStrategies/object.js
@@ -13,7 +13,7 @@ const validateByKeys = (context, keyPrefix) => {
   const missingKeys = []
   const promises = {}
   _.forOwn(context.def.keys, (keyDef, key) => {
-    promises[key] = rules.validate(keyDef, context.value[key], context.def.opts, `${keyPrefix}${key}`, context.errors, false)
+    promises[key] = rules.validate(keyDef, context.value[key], context.def.opts, `${keyPrefix}${key}`, context.errors, false, context.initData)
       .then(val => {
         if (!context.value.hasOwnProperty(key) && val === undefined) missingKeys.push(key)
         return val
@@ -45,7 +45,7 @@ const validateByKeys = (context, keyPrefix) => {
 const validateByValues = (context, keyPrefix) => {
   const promises = {}
   _.forOwn(context.value, (val, key) => {
-    promises[key] = rules.validate(context.def.values, val, context.def.opts, `${keyPrefix}${key}`, context.errors, false)
+    promises[key] = rules.validate(context.def.values, val, context.def.opts, `${keyPrefix}${key}`, context.errors, false, context.initData)
   })
   return Promise.props(promises)
 }
diff --git a/src/types.js b/src/types.js
index 4da6ee9..90f9876 100644
--- a/src/types.js
+++ b/src/types.js
@@ -42,9 +42,10 @@ const types = {
    * @param {string} key The key name of the property
    * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
    * to which any additional error objects will be added
+   * @param {Object} initData Initial data object
    * @returns {*|Promise.<*>} The value if empty or undefined, check method if value requires type validation
    */
-  validate: function(def, value, key, errors) {
+  validate: function(def, value, key, errors, initData) {
     const parsedDef = types.checkSubType(def)
     const fail = message => {
       errors.push({ type: def.type, sub: def.sub, key, value, message })
@@ -59,7 +60,7 @@ const types = {
       return value
     }
     // Execute check
-    return types.check({ def: parsedDef, key, value, fail, errors })
+    return types.check({ def: parsedDef, key, value, fail, errors, initData })
   },
 
   /**


Test patch:
diff --git a/test/src/rules.spec.js b/test/src/rules.spec.js
index a3c05e8..3a86c50 100644
--- a/test/src/rules.spec.js
+++ b/test/src/rules.spec.js
@@ -24,6 +24,61 @@ describe('rules', () => {
           expect(data).to.equal(0)
         })
     })
+    it('should not persist init data forever', () => {
+      if (rules.initData) { delete rules.initData }
+      let def = {
+        type: 'object',
+        keys: {
+          phone: { type: 'string' },
+          phoneType: { type: 'string', requireIf: 'phone' }
+        }
+      }
+      return rules.validate(def, { phone: '123' })
+        .then(() => {
+          assert.fail(true, false, 'Data is invalid, marked as valid')
+        })
+        .catch(() => expect(rules.validate(def, {})).to.be.fulfilled)
+    })
+    it('should recursively pass init data to objects', () => {
+      let def = {
+        type: 'object',
+        keys: {
+          person: { type: 'object', keys: {
+            name: { type: 'string' }
+          }
+        },
+          contacts: { type: 'object', keys: {
+            phone: { type: 'string', requireIf: 'person.name' }
+          }}
+        }
+      }
+      let data = {
+        person: { name: 'John Smith' },
+        contacts: {}
+      }
+      return expect(rules.validate(def, data)).to.be.rejected
+    })
+    it('should recursively pass init data to arrays', () => {
+      let def = {
+        type: 'object',
+        keys: {
+          person: { type: 'object', keys: {
+            name: { type: 'string' }
+          }},
+          labels: { type: 'array', values: {
+            type: 'object', keys: {
+              label: { type: 'string' },
+              condRequired: { type: 'string', requireIf: 'person.name' }
+            }
+          }}
+        }
+      }
+      let data = {
+        person: { name: 'John Smith' },
+        labels: [{label: 'awesome', condRequired: 'nice'}, {label: 'awful'}]
+      }
+      return expect(rules.validate(def, data)).to.be.rejected
+    })
   })
   describe('build', () => {
     it('returns an object with the original definition and validate method', () => {