Browse Source

Added option['deep'] to saveAll, to save unlimited levels associated data
Squashed commit of the following:

commit 45caa54e3b25bc94ee10d7b3700ff334e7994257
Author: Ceeram <c33ram@gmail.com>
Date: Sun Feb 12 22:29:33 2012 +0100

update docblocks for deep save

commit 6f3c3b9abf12e394262b0a233188a52095f64b50
Merge: 1d32698 1dd0ff1
Author: Ceeram <c33ram@gmail.com>
Date: Sun Feb 12 18:17:34 2012 +0100

Merge branch '2.1' into 2.1-saveAllTheThings

commit 1d32698640fa1a3c1d606eeaf4740637ff8a1991
Author: Ceeram <c33ram@gmail.com>
Date: Sun Feb 12 18:16:57 2012 +0100

Revert "adding info in docblock about associated model fieldList"

This reverts commit 7cc10a2b5afc1007c388a6da449781dc351d50cd.

commit 7cc10a2b5afc1007c388a6da449781dc351d50cd
Author: Ceeram <c33ram@gmail.com>
Date: Sun Feb 12 18:05:18 2012 +0100

adding info in docblock about associated model fieldList

commit db2ad2759f6af460715a8bbee167262bcbb62d77
Author: Ceeram <c33ram@gmail.com>
Date: Sun Feb 12 17:51:44 2012 +0100

add tests for deep saveAll respecting fieldList option

commit 14123fccfc43fac1b4cddeea8350dca8fb9c9821
Merge: cfdf25d 2afb05b
Author: Ceeram <c33ram@gmail.com>
Date: Sun Feb 12 16:51:26 2012 +0100

Merge branch '2.1' into 2.1-saveAllTheThings

commit cfdf25d4b587c6f1a8cd6ec2bc46dc21fa3c6704
Author: Ceeram <c33ram@gmail.com>
Date: Tue Feb 7 13:54:59 2012 +0100

Make saveAllTheThings fully BC, use $options['deep'] = true, to save infinite recursive data

commit 6e8c4380c37a31efc2a37a5ab6438db26d293eb3
Merge: 203c7e1 95aa7e3
Author: Ceeram <c33ram@gmail.com>
Date: Wed Jan 25 14:22:26 2012 +0100

Merge branch '2.1' into 2.1-saveAllTheThings

commit 203c7e1d9870e282ec7098297d47e49132904401
Author: Ceeram <c33ram@gmail.com>
Date: Wed Jan 25 14:19:25 2012 +0100

validate all the things as well

commit d920d0000fcab80fc48a29b45dbba147a2eed27c
Merge: d648f6a 4f1be12
Author: Ceeram <c33ram@gmail.com>
Date: Mon Jan 23 18:22:12 2012 +0100

Merge branch '2.1' into 2.1-saveAllTheThings

commit d648f6a90419e0fa27ed1d9e9feb51c37fbf0397
Author: Ceeram <c33ram@gmail.com>
Date: Fri Dec 9 15:11:52 2011 +0100

making saveAll, saveMany and saveAssociated not limited to only save directly related models

Ceeram 14 years ago
parent
commit
bc07ba3839

+ 74 - 24
lib/Cake/Model/Model.php

@@ -1974,6 +1974,7 @@ class Model extends Object implements CakeEventListener {
  *       'AssociatedModel' => array('field', 'otherfield')
  *   )
  *   }}}
+ * - deep: see saveMany/saveAssociated
  *
  * @param array $data Record data to save. This can be either a numerically-indexed array (for saving multiple
  *     records of the same type), or an array indexed by association name.
@@ -1993,11 +1994,7 @@ class Model extends Object implements CakeEventListener {
 			return $this->saveMany($data, $options);
 		}
 		if ($options['validate'] === 'only') {
-			$validatesAssoc = $this->validateAssociated($data, $options);
-			if (isset($this->validationErrors[$this->alias]) && $this->validationErrors[$this->alias] === false) {
-				return false;
-			}
-			return $validatesAssoc;
+			return $this->validateAssociated($data, $options);
 		}
 		return $this->saveAssociated($data, $options);
 	}
@@ -2012,6 +2009,7 @@ class Model extends Object implements CakeEventListener {
  * - atomic: If true (default), will attempt to save all records in a single transaction.
  *   Should be set to false if database/table does not support transactions.
  * - fieldList: Equivalent to the $fieldList parameter in Model::save()
+ * - deep: If set to true, all associated data will be saved as well.
  *
  * @param array $data Record data to save. This should be a numerically-indexed array
  * @param array $options Options to use when saving record data, See $options above.
@@ -2025,7 +2023,7 @@ class Model extends Object implements CakeEventListener {
 			$data = $this->data;
 		}
 
-		$options = array_merge(array('validate' => 'first', 'atomic' => true), $options);
+		$options = array_merge(array('validate' => 'first', 'atomic' => true, 'deep' => false), $options);
 		$this->validationErrors = $validationErrors = array();
 
 		if (empty($data) && $options['validate'] !== false) {
@@ -2046,12 +2044,21 @@ class Model extends Object implements CakeEventListener {
 		}
 		$return = array();
 		foreach ($data as $key => $record) {
-			$validates = ($this->create(null) !== null && $this->save($record, $options));
+			$validates = $this->create(null) !== null;
+			$saved = false;
+			if ($validates) {
+				if ($options['deep']) {
+					$saved = $this->saveAssociated($record, array_merge($options, array('atomic' => false)));
+				} else {
+					$saved = $this->save($record, $options);
+				}
+			}
+			$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
 			if (!$validates) {
 				$validationErrors[$key] = $this->validationErrors;
 			}
 			if (!$options['atomic']) {
-				$return[] = $validates;
+				$return[$key] = $validates;
 			} elseif (!$validates) {
 				break;
 			}
@@ -2079,6 +2086,7 @@ class Model extends Object implements CakeEventListener {
  *
  * - atomic: If true (default), returns boolean. If false returns array.
  * - fieldList: Equivalent to the $fieldList parameter in Model::save()
+ * - deep: If set to true, all associated data will be validated as well.
  *
  * @param array $data Record data to validate. This should be a numerically-indexed array
  * @param array $options Options to use when validating record data (see above), See also $options of validates().
@@ -2088,14 +2096,21 @@ class Model extends Object implements CakeEventListener {
  *    depending on whether each record validated successfully.
  */
 	public function validateMany($data, $options = array()) {
-		$options = array_merge(array('atomic' => true), $options);
+		$options = array_merge(array('atomic' => true, 'deep' => false), $options);
 		$this->validationErrors = $validationErrors = $return = array();
 		foreach ($data as $key => $record) {
-			$validates = $this->create($record) && $this->validates($options);
-			if (!$validates) {
+			if ($options['deep']) {
+				$validates = $this->validateAssociated($record, $options);
+			} else {
+				$validates = $this->create($record) && $this->validates($options);
+			}
+			if ($validates === false || (is_array($validates) && in_array(false, $validates, true))) {
 				$validationErrors[$key] = $this->validationErrors;
+				$validates = false;
+			} else {
+				$validates = true;
 			}
-			$return[] = $validates;
+			$return[$key] = $validates;
 		}
 		$this->validationErrors = $validationErrors;
 		if (!$options['atomic']) {
@@ -2124,6 +2139,7 @@ class Model extends Object implements CakeEventListener {
  *       'AssociatedModel' => array('field', 'otherfield')
  *   )
  *   }}}
+ * - deep: If set to true, not only directly associated data is saved, but deeper nested associated data as well.
  *
  * @param array $data Record data to save. This should be an array indexed by association name.
  * @param array $options Options to use when saving record data, See $options above.
@@ -2137,7 +2153,7 @@ class Model extends Object implements CakeEventListener {
 			$data = $this->data;
 		}
 
-		$options = array_merge(array('validate' => 'first', 'atomic' => true), $options);
+		$options = array_merge(array('validate' => 'first', 'atomic' => true, 'deep' => false), $options);
 		$this->validationErrors = $validationErrors = array();
 
 		if (empty($data) && $options['validate'] !== false) {
@@ -2160,13 +2176,26 @@ class Model extends Object implements CakeEventListener {
 		$validates = true;
 		foreach ($data as $association => $values) {
 			if (isset($associations[$association]) && $associations[$association] === 'belongsTo') {
-				if ($this->{$association}->create(null) !== null && $this->{$association}->save($values, $options)) {
-					$data[$this->alias][$this->belongsTo[$association]['foreignKey']] = $this->{$association}->id;
+				$validates = $this->{$association}->create(null) !== null;
+				$saved = false;
+				if ($validates) {
+					if ($options['deep']) {
+						$saved = $this->{$association}->saveAssociated($values, array_merge($options, array('atomic' => false)));
+					} else {
+						$saved = $this->{$association}->save($values, array_merge($options, array('atomic' => false)));
+					}
+					$validates = ($saved === true || (is_array($saved) && !in_array(false, $saved, true)));
+				}
+				if ($validates) {
+					if (!empty($data[$this->alias])) {
+						$data[$this->alias][$this->belongsTo[$association]['foreignKey']] = $this->{$association}->id;
+					} else {
+						$data[$this->belongsTo[$association]['foreignKey']] = $this->{$association}->id;
+					}
 				} else {
 					$validationErrors[$association] = $this->{$association}->validationErrors;
-					$validates = false;
 				}
-				$return[$association][] = $validates;
+				$return[$association] = $validates;
 			}
 		}
 		if ($validates && !($this->create(null) !== null && $this->save($data, $options))) {
@@ -2184,11 +2213,20 @@ class Model extends Object implements CakeEventListener {
 				switch ($type) {
 					case 'hasOne':
 						$values[$this->{$type}[$association]['foreignKey']] = $this->id;
-						if (!($this->{$association}->create(null) !== null && $this->{$association}->save($values, $options))) {
+						$validates = $this->{$association}->create(null) !== null;
+						$saved = false;
+						if ($validates) {
+							if ($options['deep']) {
+								$saved = $this->{$association}->saveAssociated($values, array_merge($options, array('atomic' => false)));
+							} else {
+								$saved = $this->{$association}->save($values, $options);
+							}
+						}
+						$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
+						if (!$validates) {
 							$validationErrors[$association] = $this->{$association}->validationErrors;
-							$validates = false;
 						}
-						$return[$association][] = $validates;
+						$return[$association] = $validates;
 					break;
 					case 'hasMany':
 						foreach ($values as $i => $value) {
@@ -2231,6 +2269,7 @@ class Model extends Object implements CakeEventListener {
  *
  * - atomic: If true (default), returns boolean. If false returns array.
  * - fieldList: Equivalent to the $fieldList parameter in Model::save()
+ * - deep: If set to true, not only directly associated data , but deeper nested associated data is validated as well.
  *
  * @param array $data Record data to validate. This should be an array indexed by association name.
  * @param array $options Options to use when validating record data (see above), See also $options of validates().
@@ -2239,7 +2278,7 @@ class Model extends Object implements CakeEventListener {
  *    depending on whether each record validated successfully.
  */
 	public function validateAssociated($data, $options = array()) {
-		$options = array_merge(array('atomic' => true), $options);
+		$options = array_merge(array('atomic' => true, 'deep' => false), $options);
 		$this->validationErrors = $validationErrors = $return = array();
 		if (!($this->create($data) && $this->validates($options))) {
 			$validationErrors[$this->alias] = $this->validationErrors;
@@ -2248,12 +2287,23 @@ class Model extends Object implements CakeEventListener {
 			$return[$this->alias] = true;
 		}
 		$associations = $this->getAssociated();
-		$validates = true;
 		foreach ($data as $association => $values) {
+			$validates = true;
 			if (isset($associations[$association])) {
 				if (in_array($associations[$association], array('belongsTo', 'hasOne'))) {
-					$validates = $this->{$association}->create($values) && $this->{$association}->validates($options);
-					$return[$association][] = $validates;
+					if ($options['deep']) {
+						$validates = $this->{$association}->validateAssociated($values, $options);
+					} else {
+						$validates = $this->{$association}->create($values) !== null && $this->{$association}->validates($options);
+					}
+					if (is_array($validates)) {
+						if (in_array(false, $validates, true)) {
+							$validates = false;
+						} else {
+							$validates = true;
+						}
+					}
+					$return[$association] = $validates;
 				} elseif ($associations[$association] === 'hasMany') {
 					$validates = $this->{$association}->validateMany($values, $options);
 					$return[$association] = $validates;

File diff suppressed because it is too large
+ 906 - 51
lib/Cake/Test/Case/Model/ModelWriteTest.php


+ 7 - 0
lib/Cake/Test/Case/Model/models.php

@@ -641,6 +641,13 @@ class Attachment extends CakeTestModel {
  * @var string 'Attachment'
  */
 	public $name = 'Attachment';
+
+/**
+ * belongsTo property
+ *
+ * @var array
+ */
+	public $belongsTo = array('Comment');
 }
 
 /**