diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index f53b435ee..f3964edba 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -1600,6 +1600,9 @@ class Model extends Object { } /** + * Backwards compatible passtrough method for: + * saveMany(), validateMany(), saveAssociated() and validateAssociated() + * * Saves multiple individual records for a single model; Also works with a single record, as well as * all its associated records. * @@ -1612,7 +1615,7 @@ class Model extends Object { * Should be set to false if database/table does not support transactions. * - fieldList: Equivalent to the $fieldList parameter in Model::save() * - * @param array $data Record data to save. This can be either a numerically-indexed array (for saving multiple + * @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. * @param array $options Options to use when saving record data, See $options above. * @return mixed If atomic: True on success, or false on failure. @@ -1623,210 +1626,284 @@ class Model extends Object { * @link http://book.cakephp.org/view/1031/Saving-Your-Data */ public function saveAll($data = null, $options = array()) { + $options = array_merge(array('validate' => 'first'), $options); + if (Set::numeric(array_keys($data))) { + if ($options['validate'] === 'only') { + return $this->validateMany($data, $options); + } + return $this->saveMany($data, $options); + } + if ($options['validate'] === 'only') { + return $this->validateAssociated($data, $options); + } + return $this->saveAssociated($data, $options); + } + +/** + * Saves multiple individual records for a single model + * + * #### Options + * + * - validate: Set to false to disable validation, true to validate each record before saving, + * 'first' to validate *all* records before any are saved (default), + * - 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() + * + * @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. + * @return mixed If atomic: True on success, or false on failure. + * Otherwise: array similar to the $data array passed, but values are set to true/false + * depending on whether each record saved successfully. + * @access public + */ + public function saveMany($data = null, $options = array()) { if (empty($data)) { $data = $this->data; } - $db = $this->getDataSource(); $options = array_merge(array('validate' => 'first', 'atomic' => true), $options); $this->validationErrors = $validationErrors = array(); - $validates = true; - $return = array(); if (empty($data) && $options['validate'] !== false) { $result = $this->save($data, $options); return !empty($result); } - if ($options['atomic'] && $options['validate'] !== 'only') { + if ($options['validate'] === 'first') { + $validates = $this->validateMany($data, $options); + if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) { + return $validates; + } + } + + if ($options['atomic']) { + $db = $this->getDataSource(); $transactionBegun = $db->begin($this); } - - if (Set::numeric(array_keys($data))) { - while ($validates) { - $return = array(); - foreach ($data as $key => $record) { - if (!$currentValidates = $this->__save($record, $options)) { - $validationErrors[$key] = $this->validationErrors; - } - - if ($options['validate'] === 'only' || $options['validate'] === 'first') { - $validating = true; - if ($options['atomic']) { - $validates = $validates && $currentValidates; - } else { - $validates = $currentValidates; - } - } else { - $validating = false; - $validates = $currentValidates; - } - - if (!$options['atomic']) { - $return[] = $validates; - } elseif (!$validates && !$validating) { - break; - } - } - $this->validationErrors = $validationErrors; - - switch (true) { - case ($options['validate'] === 'only'): - return ($options['atomic'] ? $validates : $return); - break; - case ($options['validate'] === 'first'): - $options['validate'] = true; - break; - default: - if ($options['atomic']) { - if ($validates) { - if ($transactionBegun) { - return $db->commit($this) !== false; - } else { - return true; - } - } - $db->rollback($this); - return false; - } - return $return; - break; - } - } - if ($options['atomic'] && !$validates) { - $db->rollback($this); - return false; - } - return $return; - } - $associations = $this->getAssociated(); - - while ($validates) { - foreach ($data as $association => $values) { - if (isset($associations[$association])) { - switch ($associations[$association]) { - case 'belongsTo': - if ($this->{$association}->__save($values, $options)) { - $data[$this->alias][$this->belongsTo[$association]['foreignKey']] = $this->{$association}->id; - } else { - $validationErrors[$association] = $this->{$association}->validationErrors; - $validates = false; - } - if (!$options['atomic']) { - $return[$association][] = $validates; - } - break; - } - } - } - - if (!$this->__save($data, $options)) { - $validationErrors[$this->alias] = $this->validationErrors; - $validates = false; + $return = array(); + foreach ($data as $key => $record) { + $validates = ($this->create(null) !== null && $this->save($record, $options)); + if (!$validates) { + $validationErrors[$key] = $this->validationErrors; } if (!$options['atomic']) { - $return[$this->alias] = $validates; - } - $validating = ($options['validate'] === 'only' || $options['validate'] === 'first'); - - foreach ($data as $association => $values) { - if (!$validates && !$validating) { - break; - } - if (isset($associations[$association])) { - $type = $associations[$association]; - switch ($type) { - case 'hasOne': - $values[$this->{$type}[$association]['foreignKey']] = $this->id; - if (!$this->{$association}->__save($values, $options)) { - $validationErrors[$association] = $this->{$association}->validationErrors; - $validates = false; - } - if (!$options['atomic']) { - $return[$association][] = $validates; - } - break; - case 'hasMany': - foreach ($values as $i => $value) { - $values[$i][$this->{$type}[$association]['foreignKey']] = $this->id; - } - $_options = array_merge($options, array('atomic' => false)); - - if ($_options['validate'] === 'first') { - $_options['validate'] = 'only'; - } - $_return = $this->{$association}->saveAll($values, $_options); - - if ($_return === false || (is_array($_return) && in_array(false, $_return, true))) { - $validationErrors[$association] = $this->{$association}->validationErrors; - $validates = false; - } - if (is_array($_return)) { - foreach ($_return as $val) { - if (!isset($return[$association])) { - $return[$association] = array(); - } elseif (!is_array($return[$association])) { - $return[$association] = array($return[$association]); - } - $return[$association][] = $val; - } - } else { - $return[$association] = $_return; - } - break; - } - } - } - $this->validationErrors = $validationErrors; - - if (isset($validationErrors[$this->alias])) { - $this->validationErrors = $validationErrors[$this->alias]; - } - - switch (true) { - case ($options['validate'] === 'only'): - return ($options['atomic'] ? $validates : $return); + $return[] = $validates; + } elseif (!$validates) { break; - case ($options['validate'] === 'first'): - $options['validate'] = true; - $return = array(); - break; - default: - if ($options['atomic']) { - if ($validates) { - if ($transactionBegun) { - return $db->commit($this) !== false; - } else { - return true; - } - } else { - $db->rollback($this); - } - } - return $return; - break; - } - if ($options['atomic'] && !$validates) { - $db->rollback($this); - return false; } } - return $return; + $this->validationErrors = $validationErrors; + + if (!$options['atomic']) { + return $return; + } + if ($validates) { + if ($transactionBegun) { + return $db->commit($this) !== false; + } else { + return true; + } + } + $db->rollback($this); + return false; } /** - * Private helper method used by saveAll. + * Validates multiple individual records for a single model * - * @return boolean Success - * @access private - * @see Model::saveAll() + * #### Options + * + * - atomic: If true (default), returns boolean. If false returns array. + * - fieldList: Equivalent to the $fieldList parameter in Model::save() + * + * @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(). + * @return boolean True on success, or false on failure. + * @return mixed If atomic: True on success, or false on failure. + * Otherwise: array similar to the $data array passed, but values are set to true/false + * depending on whether each record validated successfully. + * @access public */ - private function __save($data, $options) { - if ($options['validate'] === 'first' || $options['validate'] === 'only') { - if (!($this->create($data) && $this->validates($options))) { - return false; + public function validateMany($data, $options = array()) { + $options = array_merge(array('atomic' => true), $options); + $this->validationErrors = $validationErrors = $return = array(); + foreach ($data as $key => $record) { + $validates = $this->create($record) && $this->validates($options); + if (!$validates) { + $validationErrors[$key] = $this->validationErrors; } - } elseif (!($this->create(null) !== null && $this->save($data, $options))) { + $return[] = $validates; + } + $this->validationErrors = $validationErrors; + if (!$options['atomic']) { + return $return; + } + if (empty($this->validationErrors)) { + return true; + } + return false; + } + +/** + * Saves a single record, as well as all its directly associated records. + * + * #### Options + * + * - validate: Set to false to disable validation, true to validate each record before saving, + * 'first' to validate *all* records before any are saved (default), + * - 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() + * + * @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. + * @return mixed If atomic: True on success, or false on failure. + * Otherwise: array similar to the $data array passed, but values are set to true/false + * depending on whether each record saved successfully. + * @access public + */ + public function saveAssociated($data = null, $options = array()) { + if (empty($data)) { + $data = $this->data; + } + + $options = array_merge(array('validate' => true, 'atomic' => true), $options); + $this->validationErrors = $validationErrors = array(); + + if (empty($data) && $options['validate'] !== false) { + $result = $this->save($data, $options); + return !empty($result); + } + + if ($options['validate'] === 'first') { + $validates = $this->validateAssociated($data, $options); + if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) { + return $validates; + } + } + if ($options['atomic']) { + $db = $this->getDataSource(); + $transactionBegun = $db->begin($this); + } + $associations = $this->getAssociated(); + $return = array(); + $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; + } else { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + $return[$association][] = $validates; + } + } + if ($validates && !($this->create(null) !== null && $this->save($data, $options))) { + $validationErrors[$this->alias] = $this->validationErrors; + $validates = false; + } + $return[$this->alias] = $validates; + + foreach ($data as $association => $values) { + if (!$validates) { + break; + } + if (isset($associations[$association])) { + $type = $associations[$association]; + switch ($type) { + case 'hasOne': + $values[$this->{$type}[$association]['foreignKey']] = $this->id; + if (!($this->{$association}->create(null) !== null && $this->{$association}->save($values, $options))) { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + $return[$association][] = $validates; + break; + case 'hasMany': + foreach ($values as $i => $value) { + $values[$i][$this->{$type}[$association]['foreignKey']] = $this->id; + } + $_return = $this->{$association}->saveMany($values, array_merge($options, array('atomic' => false))); + if (in_array(false, $_return, true)) { + $validationErrors[$association] = $this->{$association}->validationErrors; + $validates = false; + } + $return[$association] = $_return; + break; + } + } + } + $this->validationErrors = $validationErrors; + + if (isset($validationErrors[$this->alias])) { + $this->validationErrors = $validationErrors[$this->alias]; + } + + if (!$options['atomic']) { + return $return; + } + if ($validates) { + if ($transactionBegun) { + return $db->commit($this) !== false; + } else { + return true; + } + } + $db->rollback($this); + return false; + } + +/** + * Validates a single record, as well as all its directly associated records. + * + * #### Options + * + * - atomic: If true (default), returns boolean. If false returns array. + * - fieldList: Equivalent to the $fieldList parameter in Model::save() + * + * @param array $data Record data to validate. This should be an array indexed by association name. + * @param array Options to use when validating record data (see above), See also $options of validates(). + * @return mixed If atomic: True on success, or false on failure. + * Otherwise: array similar to the $data array passed, but values are set to true/false + * depending on whether each record validated successfully. + * @access public + */ + public function validateAssociated($data, $options = array()) { + $options = array_merge(array('atomic' => true), $options); + $this->validationErrors = $validationErrors = $return = array(); + if (!($this->create($data) && $this->validates($options))) { + $validationErrors[$this->alias] = $this->validationErrors; + $return[$this->alias] = false; + } else { + $return[$this->alias] = true; + } + $associations = $this->getAssociated(); + $validates = true; + foreach ($data as $association => $values) { + if (isset($associations[$association])) { + if (in_array($associations[$association], array('belongsTo', 'hasOne'))) { + $validates = $this->{$association}->create($values) && $this->{$association}->validates($options); + $return[$association][] = $validates; + } elseif($associations[$association] === 'hasMany') { + $validates = $this->{$association}->validateMany($values, $options); + $return[$association] = $validates; + } + if (!$validates || (is_array($validates) && in_array(false, $validates, true))) { + $validationErrors[$association] = $this->{$association}->validationErrors; + } + } + } + + $this->validationErrors = $validationErrors; + if (isset($validationErrors[$this->alias])) { + $this->validationErrors = $validationErrors[$this->alias]; + } + if (!$options['atomic']) { + return $return; + } + if (!empty($this->validationErrors)) { return false; } return true; diff --git a/lib/Cake/Test/Case/Model/ModelWriteTest.php b/lib/Cake/Test/Case/Model/ModelWriteTest.php index 30e152d39..d768772d3 100644 --- a/lib/Cake/Test/Case/Model/ModelWriteTest.php +++ b/lib/Cake/Test/Case/Model/ModelWriteTest.php @@ -2950,8 +2950,7 @@ class ModelWriteTest extends BaseModelTest { array('comment' => '', 'published' => 'Y', 'user_id' => 1), ) ), array('validate' => true)); - $expected = array('Comment' => array(false)); - $this->assertEqual($expected, $result); + $this->assertFalse($result); $expected = array('Comment' => array( array('comment' => array('This field cannot be left blank')) @@ -3048,7 +3047,7 @@ class ModelWriteTest extends BaseModelTest { 'password' => "sekret" ) ); - $Post->saveAll($data); + $Post->saveAll($data, array('validate' => true)); } /** @@ -3442,19 +3441,6 @@ class ModelWriteTest extends BaseModelTest { $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); $this->assertEqual($expected, $result); $this->assertEqual($TestModel->validationErrors, $errors); - - $data = array( - array( - 'title' => 'First new post', - 'body' => 'Woohoo!', - 'published' => 'Y' - ), - array( - 'title' => 'Empty body', - 'body' => '' - )); - - $TestModel->validate['body'] = 'notEmpty'; } /** @@ -3650,36 +3636,6 @@ class ModelWriteTest extends BaseModelTest { $this->assertEqual($expected, $result); } -/** - * testUpdateWithCalculation method - * - * @access public - * @return void - */ - public function testUpdateWithCalculation() { - $this->loadFixtures('DataTest'); - $model = new DataTest(); - $model->deleteAll(true); - $result = $model->saveAll(array( - array('count' => 5, 'float' => 1.1), - array('count' => 3, 'float' => 1.2), - array('count' => 4, 'float' => 1.3), - array('count' => 1, 'float' => 2.0), - )); - $this->assertFalse(empty($result)); - - $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); - $this->assertEqual($result, array(5, 3, 4, 1)); - - $this->assertTrue($model->updateAll(array('count' => 'count + 2'))); - $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); - $this->assertEqual($result, array(7, 5, 6, 3)); - - $this->assertTrue($model->updateAll(array('DataTest.count' => 'DataTest.count - 1'))); - $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); - $this->assertEqual($result, array(6, 4, 5, 2)); - } - /** * testSaveAllHasManyValidationOnly method * @@ -3757,6 +3713,1390 @@ class ModelWriteTest extends BaseModelTest { $this->assertEqual($TestModel->Comment->validationErrors, $expected); } +/** + * test that saveAll behaves like plain save() when suplied empty data + * + * @link http://cakephp.lighthouseapp.com/projects/42648/tickets/277-test-saveall-with-validation-returns-incorrect-boolean-when-saving-empty-data + * @access public + * @return void + */ + public function testSaveAllEmptyData() { + $this->skipIf($this->db instanceof Sqlserver, 'This test is not compatible with SQL Server.'); + + $this->loadFixtures('Article', 'ProductUpdateAll', 'Comment', 'Attachment'); + $model = new Article(); + $result = $model->saveAll(array(), array('validate' => 'first')); + $this->assertFalse(empty($result)); + + $model = new ProductUpdateAll(); + $result = $model->saveAll(array()); + $this->assertFalse($result); + } + +/** + * testSaveAssociated method + * + * @access public + * @return void + */ + public function testSaveAssociated() { + $this->loadFixtures('Post', 'Author', 'Comment', 'Attachment', 'Article', 'User'); + $TestModel = new Post(); + + $result = $TestModel->find('all'); + $this->assertEqual(count($result), 3); + $this->assertFalse(isset($result[3])); + $ts = date('Y-m-d H:i:s'); + + $TestModel->saveAssociated(array( + 'Post' => array( + 'title' => 'Post with Author', + 'body' => 'This post will be saved with an author' + ), + 'Author' => array( + 'user' => 'bob', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf90' + ))); + + $result = $TestModel->find('all'); + $expected = array( + 'Post' => array( + 'id' => '4', + 'author_id' => '5', + 'title' => 'Post with Author', + 'body' => 'This post will be saved with an author', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + ), + 'Author' => array( + 'id' => '5', + 'user' => 'bob', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf90', + 'created' => $ts, + 'updated' => $ts, + 'test' => 'working' + )); + $this->assertEqual($result[3], $expected); + $this->assertEqual(count($result), 4); + + $ts = date('Y-m-d H:i:s'); + + $TestModel = new Comment(); + $ts = date('Y-m-d H:i:s'); + $result = $TestModel->saveAssociated(array( + 'Comment' => array( + 'article_id' => 2, + 'user_id' => 2, + 'comment' => 'New comment with attachment', + 'published' => 'Y' + ), + 'Attachment' => array( + 'attachment' => 'some_file.tgz' + ))); + $this->assertFalse(empty($result)); + + $result = $TestModel->find('all'); + $expected = array( + 'id' => '7', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'New comment with attachment', + 'published' => 'Y', + 'created' => $ts, + 'updated' => $ts + ); + $this->assertEqual($result[6]['Comment'], $expected); + + $expected = array( + 'id' => '7', + 'article_id' => '2', + 'user_id' => '2', + 'comment' => 'New comment with attachment', + 'published' => 'Y', + 'created' => $ts, + 'updated' => $ts + ); + $this->assertEqual($result[6]['Comment'], $expected); + + $expected = array( + 'id' => '2', + 'comment_id' => '7', + 'attachment' => 'some_file.tgz', + 'created' => $ts, + 'updated' => $ts + ); + $this->assertEqual($result[6]['Attachment'], $expected); + } + +/** + * testSaveMany method + * + * @access public + * @return void + */ + public function testSaveMany() { + $this->loadFixtures('Post'); + $TestModel = new Post(); + $TestModel->deleteAll(true); + $this->assertEqual($TestModel->find('all'), array()); + + // SQLite seems to reset the PK counter when that happens, so we need this to make the tests pass + $this->db->truncate($TestModel); + + $ts = date('Y-m-d H:i:s'); + $TestModel->saveMany(array( + array( + 'title' => 'Multi-record post 1', + 'body' => 'First multi-record post', + 'author_id' => 2 + ), + array( + 'title' => 'Multi-record post 2', + 'body' => 'Second multi-record post', + 'author_id' => 2 + ))); + + $result = $TestModel->find('all', array( + 'recursive' => -1, + 'order' => 'Post.id ASC' + )); + $expected = array( + array( + 'Post' => array( + 'id' => '1', + 'author_id' => '2', + 'title' => 'Multi-record post 1', + 'body' => 'First multi-record post', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )), + array( + 'Post' => array( + 'id' => '2', + 'author_id' => '2', + 'title' => 'Multi-record post 2', + 'body' => 'Second multi-record post', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + ))); + $this->assertEqual($expected, $result); + } + +/** + * Test SaveAssociated with Habtm relations + * + * @access public + * @return void + */ + public function testSaveAssociatedHabtm() { + $this->loadFixtures('Article', 'Tag', 'Comment', 'User', 'ArticlesTag'); + $data = array( + 'Article' => array( + 'user_id' => 1, + 'title' => 'Article Has and belongs to Many Tags' + ), + 'Tag' => array( + 'Tag' => array(1, 2) + ), + 'Comment' => array( + array( + 'comment' => 'Article comment', + 'user_id' => 1 + ))); + $Article = new Article(); + $result = $Article->saveAssociated($data); + $this->assertFalse(empty($result)); + + $result = $Article->read(); + $this->assertEqual(count($result['Tag']), 2); + $this->assertEqual($result['Tag'][0]['tag'], 'tag1'); + $this->assertEqual(count($result['Comment']), 1); + $this->assertEqual(count($result['Comment'][0]['comment']['Article comment']), 1); + } + +/** + * Test SaveAssociated with Habtm relations and extra join table fields + * + * @access public + * @return void + */ + public function testSaveAssociatedHabtmWithExtraJoinTableFields() { + $this->loadFixtures('Something', 'SomethingElse', 'JoinThing'); + + $data = array( + 'Something' => array( + 'id' => 4, + 'title' => 'Extra Fields', + 'body' => 'Extra Fields Body', + 'published' => '1' + ), + 'SomethingElse' => array( + array('something_else_id' => 1, 'doomed' => '1'), + array('something_else_id' => 2, 'doomed' => '0'), + array('something_else_id' => 3, 'doomed' => '1') + ) + ); + + $Something = new Something(); + $result = $Something->saveAssociated($data); + $this->assertFalse(empty($result)); + $result = $Something->read(); + + $this->assertEqual(count($result['SomethingElse']), 3); + $this->assertTrue(Set::matches('/Something[id=4]', $result)); + + $this->assertTrue(Set::matches('/SomethingElse[id=1]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=1]/JoinThing[something_else_id=1]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=1]/JoinThing[doomed=1]', $result)); + + $this->assertTrue(Set::matches('/SomethingElse[id=2]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=2]/JoinThing[something_else_id=2]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=2]/JoinThing[doomed=0]', $result)); + + $this->assertTrue(Set::matches('/SomethingElse[id=3]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=3]/JoinThing[something_else_id=3]', $result)); + $this->assertTrue(Set::matches('/SomethingElse[id=3]/JoinThing[doomed=1]', $result)); + } + +/** + * testSaveAssociatedHasOne method + * + * @access public + * @return void + */ + public function testSaveAssociatedHasOne() { + $model = new Comment(); + $model->deleteAll(true); + $this->assertEqual($model->find('all'), array()); + + $model->Attachment->deleteAll(true); + $this->assertEqual($model->Attachment->find('all'), array()); + + $this->assertTrue($model->saveAssociated(array( + 'Comment' => array( + 'comment' => 'Comment with attachment', + 'article_id' => 1, + 'user_id' => 1 + ), + 'Attachment' => array( + 'attachment' => 'some_file.zip' + )))); + $result = $model->find('all', array('fields' => array( + 'Comment.id', 'Comment.comment', 'Attachment.id', + 'Attachment.comment_id', 'Attachment.attachment' + ))); + $expected = array(array( + 'Comment' => array( + 'id' => '1', + 'comment' => 'Comment with attachment' + ), + 'Attachment' => array( + 'id' => '1', + 'comment_id' => '1', + 'attachment' => 'some_file.zip' + ))); + $this->assertEqual($expected, $result); + + + $model->Attachment->bindModel(array('belongsTo' => array('Comment')), false); + $data = array( + 'Comment' => array( + 'comment' => 'Comment with attachment', + 'article_id' => 1, + 'user_id' => 1 + ), + 'Attachment' => array( + 'attachment' => 'some_file.zip' + )); + $this->assertTrue($model->saveAssociated($data, array('validate' => 'first'))); + } + +/** + * testSaveAssociatedBelongsTo method + * + * @access public + * @return void + */ + public function testSaveAssociatedBelongsTo() { + $model = new Comment(); + $model->deleteAll(true); + $this->assertEqual($model->find('all'), array()); + + $model->Article->deleteAll(true); + $this->assertEqual($model->Article->find('all'), array()); + + $this->assertTrue($model->saveAssociated(array( + 'Comment' => array( + 'comment' => 'Article comment', + 'article_id' => 1, + 'user_id' => 1 + ), + 'Article' => array( + 'title' => 'Model Associations 101', + 'user_id' => 1 + )))); + $result = $model->find('all', array('fields' => array( + 'Comment.id', 'Comment.comment', 'Comment.article_id', 'Article.id', 'Article.title' + ))); + $expected = array(array( + 'Comment' => array( + 'id' => '1', + 'article_id' => '1', + 'comment' => 'Article comment' + ), + 'Article' => array( + 'id' => '1', + 'title' => 'Model Associations 101' + ))); + $this->assertEqual($expected, $result); + } + +/** + * testSaveAssociatedHasOneValidation method + * + * @access public + * @return void + */ + public function testSaveAssociatedHasOneValidation() { + $model = new Comment(); + $model->deleteAll(true); + $this->assertEqual($model->find('all'), array()); + + $model->Attachment->deleteAll(true); + $this->assertEqual($model->Attachment->find('all'), array()); + + $model->validate = array('comment' => 'notEmpty'); + $model->Attachment->validate = array('attachment' => 'notEmpty'); + $model->Attachment->bindModel(array('belongsTo' => array('Comment'))); + + $this->assertEquals($model->saveAssociated( + array( + 'Comment' => array( + 'comment' => '', + 'article_id' => 1, + 'user_id' => 1 + ), + 'Attachment' => array('attachment' => '') + ), + array('validate' => 'first') + ), false); + $expected = array( + 'Comment' => array('comment' => array('This field cannot be left blank')), + 'Attachment' => array('attachment' => array('This field cannot be left blank')) + ); + $this->assertEqual($model->validationErrors, $expected['Comment']); + $this->assertEqual($model->Attachment->validationErrors, $expected['Attachment']); + } + +/** + * testSaveAssociatedAtomic method + * + * @access public + * @return void + */ + public function testSaveAssociatedAtomic() { + $this->loadFixtures('Article', 'User'); + $TestModel = new Article(); + + $result = $TestModel->saveAssociated(array( + 'Article' => array( + 'title' => 'Post with Author', + 'body' => 'This post will be saved with an author', + 'user_id' => 2 + ), + 'Comment' => array( + array('comment' => 'First new comment', 'user_id' => 2)) + ), array('atomic' => false)); + + $this->assertIdentical($result, array('Article' => true, 'Comment' => array(true))); + + $result = $TestModel->saveAssociated(array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'comment' => 'First new comment', + 'published' => 'Y', + 'user_id' => 1 + ), + array( + 'comment' => 'Second new comment', + 'published' => 'Y', + 'user_id' => 2 + )) + ), array('validate' => true, 'atomic' => false)); + $this->assertIdentical($result, array('Article' => true, 'Comment' => array(true, true))); + } + +/** + * testSaveManyAtomic method + * + * @access public + * @return void + */ + public function testSaveManyAtomic() { + $this->loadFixtures('Article', 'User'); + $TestModel = new Article(); + + $result = $TestModel->saveMany(array( + array( + 'id' => '1', + 'title' => 'Baleeted First Post', + 'body' => 'Baleeted!', + 'published' => 'N' + ), + array( + 'id' => '2', + 'title' => 'Just update the title' + ), + array( + 'title' => 'Creating a fourth post', + 'body' => 'Fourth post body', + 'user_id' => 2 + ) + ), array('atomic' => false)); + $this->assertIdentical($result, array(true, true, true)); + + $TestModel->validate = array('title' => 'notEmpty', 'author_id' => 'numeric'); + $result = $TestModel->saveMany(array( + array( + 'id' => '1', + 'title' => 'Un-Baleeted First Post', + 'body' => 'Not Baleeted!', + 'published' => 'Y' + ), + array( + 'id' => '2', + 'title' => '', + 'body' => 'Trying to get away with an empty title' + ) + ), array('validate' => true, 'atomic' => false)); + + $this->assertIdentical($result, array(true, false)); + + } + +/** + * testSaveAssociatedHasMany method + * + * @access public + * @return void + */ + public function testSaveAssociatedHasMany() { + $this->loadFixtures('Article', 'Comment'); + $TestModel = new Article(); + $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array(); + + $result = $TestModel->saveAssociated(array( + 'Article' => array('id' => 2), + 'Comment' => array( + array('comment' => 'First new comment', 'published' => 'Y', 'user_id' => 1), + array('comment' => 'Second new comment', 'published' => 'Y', 'user_id' => 2) + ) + )); + $this->assertFalse(empty($result)); + + $result = $TestModel->findById(2); + $expected = array( + 'First Comment for Second Article', + 'Second Comment for Second Article', + 'First new comment', + 'Second new comment' + ); + $this->assertEqual(Set::extract($result['Comment'], '{n}.comment'), $expected); + + $result = $TestModel->saveAssociated( + array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'comment' => 'Third new comment', + 'published' => 'Y', + 'user_id' => 1 + ))), + array('atomic' => false) + ); + $this->assertFalse(empty($result)); + + $result = $TestModel->findById(2); + $expected = array( + 'First Comment for Second Article', + 'Second Comment for Second Article', + 'First new comment', + 'Second new comment', + 'Third new comment' + ); + $this->assertEqual(Set::extract($result['Comment'], '{n}.comment'), $expected); + + $TestModel->beforeSaveReturn = false; + $result = $TestModel->saveAssociated( + array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'comment' => 'Fourth new comment', + 'published' => 'Y', + 'user_id' => 1 + ))), + array('atomic' => false) + ); + $this->assertEqual($result, array('Article' => false)); + + $result = $TestModel->findById(2); + $expected = array( + 'First Comment for Second Article', + 'Second Comment for Second Article', + 'First new comment', + 'Second new comment', + 'Third new comment' + ); + $this->assertEqual(Set::extract($result['Comment'], '{n}.comment'), $expected); + } + +/** + * testSaveAssociatedHasManyValidation method + * + * @access public + * @return void + */ + public function testSaveAssociatedHasManyValidation() { + $this->loadFixtures('Article', 'Comment'); + $TestModel = new Article(); + $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array(); + $TestModel->Comment->validate = array('comment' => 'notEmpty'); + + $result = $TestModel->saveAssociated(array( + 'Article' => array('id' => 2), + 'Comment' => array( + array('comment' => '', 'published' => 'Y', 'user_id' => 1), + ) + ), array('validate' => true)); + $this->assertFalse($result); + + $expected = array('Comment' => array( + array('comment' => array('This field cannot be left blank')) + )); + $this->assertEqual($TestModel->validationErrors, $expected); + $expected = array( + array('comment' => array('This field cannot be left blank')) + ); + $this->assertEqual($TestModel->Comment->validationErrors, $expected); + + $result = $TestModel->saveAssociated(array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1 + )) + ), array('validate' => 'first')); + $this->assertFalse($result); + } + +/** + * test saveMany with transactions and ensure there is no missing rollback. + * + * @return void + */ + public function testSaveManyTransactionNoRollback() { + $this->loadFixtures('Post'); + + $this->getMock('DboSource', array(), array(), 'MockManyTransactionDboSource'); + $db = ConnectionManager::create('mock_many_transaction', array( + 'datasource' => 'MockManyTransactionDboSource', + )); + + $db->expects($this->once()) + ->method('describe') + ->will($this->returnValue(array())); + $db->expects($this->once())->method('rollback'); + + $Post = new Post('mock_many_transaction'); + + $Post->validate = array( + 'title' => array('rule' => array('notEmpty')) + ); + + $data = array( + array('author_id' => 1, 'title' => 'New Fourth Post'), + array('author_id' => 1, 'title' => '') + ); + $Post->saveMany($data); + } + +/** + * test saveAssociated with transactions and ensure there is no missing rollback. + * + * @return void + */ + public function testSaveAssociatedTransactionNoRollback() { + $testDb = ConnectionManager::getDataSource('test'); + + $mock = $this->getMock('DboSource', array(), array(), 'MockAssociatedTransactionDboSource', false); + $db = ConnectionManager::create('mock_assoc_transaction', array( + 'datasource' => 'MockAssociatedTransactionDboSource', + )); + $this->mockObjects[] = $db; + $db->columns = $testDb->columns; + + $db->expects($this->once())->method('rollback'); + $db->expects($this->any())->method('describe') + ->will($this->returnValue(array( + 'id' => array('type' => 'integer'), + 'title' => array('type' => 'string'), + 'body' => array('type' => 'text'), + 'published' => array('type' => 'string') + ))); + + $Post = new Post(); + $Post->useDbConfig = 'mock_assoc_transaction'; + $Post->Author->useDbConfig = 'mock_assoc_transaction'; + + $Post->Author->validate = array( + 'user' => array('rule' => array('notEmpty')) + ); + + $data = array( + 'Post' => array( + 'title' => 'New post', + 'body' => 'Content', + 'published' => 'Y' + ), + 'Author' => array( + 'user' => '', + 'password' => "sekret" + ) + ); + $Post->saveAssociated($data, array('validate' => true, 'atomic' => true)); + } + +/** + * test saveMany with nested saveMany call. + * + * @return void + */ + public function testSaveManyNestedSaveMany() { + $this->loadFixtures('Sample'); + $TransactionManyTestModel = new TransactionManyTestModel(); + + $data = array( + array('apple_id' => 1, 'name' => 'sample5'), + ); + + $this->assertTrue($TransactionManyTestModel->saveMany($data, array('atomic' => true))); + } + +/** + * testSaveManyTransaction method + * + * @access public + * @return void + */ + public function testSaveManyTransaction() { + $this->loadFixtures('Post', 'Author', 'Comment', 'Attachment'); + $TestModel = new Post(); + + $TestModel->validate = array('title' => 'notEmpty'); + $data = array( + array('author_id' => 1, 'title' => 'New Fourth Post'), + array('author_id' => 1, 'title' => 'New Fifth Post'), + array('author_id' => 1, 'title' => '') + ); + $ts = date('Y-m-d H:i:s'); + $this->assertFalse($TestModel->saveMany($data)); + + $result = $TestModel->find('all', array('recursive' => -1)); + $expected = array( + array('Post' => array( + 'id' => '1', + 'author_id' => 1, + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + array('Post' => array( + 'id' => '2', + 'author_id' => 3, + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + )), + array('Post' => array( + 'id' => '3', + 'author_id' => 1, + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ))); + + if (count($result) != 3) { + // Database doesn't support transactions + $expected[] = array( + 'Post' => array( + 'id' => '4', + 'author_id' => 1, + 'title' => 'New Fourth Post', + 'body' => null, + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )); + + $expected[] = array( + 'Post' => array( + 'id' => '5', + 'author_id' => 1, + 'title' => 'New Fifth Post', + 'body' => null, + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )); + + $this->assertEqual($expected, $result); + // Skip the rest of the transactional tests + return; + } + + $this->assertEqual($expected, $result); + + $data = array( + array('author_id' => 1, 'title' => 'New Fourth Post'), + array('author_id' => 1, 'title' => ''), + array('author_id' => 1, 'title' => 'New Sixth Post') + ); + $ts = date('Y-m-d H:i:s'); + $this->assertFalse($TestModel->saveMany($data)); + + $result = $TestModel->find('all', array('recursive' => -1)); + $expected = array( + array('Post' => array( + 'id' => '1', + 'author_id' => 1, + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => '2007-03-18 10:41:31' + )), + array('Post' => array( + 'id' => '2', + 'author_id' => 3, + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => '2007-03-18 10:43:31' + )), + array('Post' => array( + 'id' => '3', + 'author_id' => 1, + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + ))); + + if (count($result) != 3) { + // Database doesn't support transactions + $expected[] = array( + 'Post' => array( + 'id' => '4', + 'author_id' => 1, + 'title' => 'New Fourth Post', + 'body' => 'Third Post Body', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )); + + $expected[] = array( + 'Post' => array( + 'id' => '5', + 'author_id' => 1, + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + )); + } + $this->assertEqual($expected, $result); + + $TestModel->validate = array('title' => 'notEmpty'); + $data = array( + array('author_id' => 1, 'title' => 'New Fourth Post'), + array('author_id' => 1, 'title' => 'New Fifth Post'), + array('author_id' => 1, 'title' => 'New Sixth Post') + ); + $this->assertTrue($TestModel->saveMany($data)); + + $result = $TestModel->find('all', array( + 'recursive' => -1, + 'fields' => array('author_id', 'title','body','published') + )); + + $expected = array( + array('Post' => array( + 'author_id' => 1, + 'title' => 'First Post', + 'body' => 'First Post Body', + 'published' => 'Y' + )), + array('Post' => array( + 'author_id' => 3, + 'title' => 'Second Post', + 'body' => 'Second Post Body', + 'published' => 'Y' + )), + array('Post' => array( + 'author_id' => 1, + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y' + )), + array('Post' => array( + 'author_id' => 1, + 'title' => 'New Fourth Post', + 'body' => '', + 'published' => 'N' + )), + array('Post' => array( + 'author_id' => 1, + 'title' => 'New Fifth Post', + 'body' => '', + 'published' => 'N' + )), + array('Post' => array( + 'author_id' => 1, + 'title' => 'New Sixth Post', + 'body' => '', + 'published' => 'N' + ))); + $this->assertEqual($expected, $result); + } + +/** + * testSaveManyValidation method + * + * @access public + * @return void + */ + public function testSaveManyValidation() { + $this->loadFixtures('Post', 'Author', 'Comment', 'Attachment'); + $TestModel = new Post(); + + $data = array( + array( + 'id' => '1', + 'title' => 'Baleeted First Post', + 'body' => 'Baleeted!', + 'published' => 'N' + ), + array( + 'id' => '2', + 'title' => 'Just update the title' + ), + array( + 'title' => 'Creating a fourth post', + 'body' => 'Fourth post body', + 'author_id' => 2 + )); + + $this->assertTrue($TestModel->saveMany($data)); + + $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); + $ts = date('Y-m-d H:i:s'); + $expected = array( + array( + 'Post' => array( + 'id' => '1', + 'author_id' => '1', + 'title' => 'Baleeted First Post', + 'body' => 'Baleeted!', + 'published' => 'N', + 'created' => '2007-03-18 10:39:23', + 'updated' => $ts + )), + array( + 'Post' => array( + 'id' => '2', + 'author_id' => '3', + 'title' => 'Just update the title', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', 'updated' => $ts + )), + array( + 'Post' => array( + 'id' => '3', + 'author_id' => '1', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + )), + array( + 'Post' => array( + 'id' => '4', + 'author_id' => '2', + 'title' => 'Creating a fourth post', + 'body' => 'Fourth post body', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + ))); + $this->assertEqual($expected, $result); + + $TestModel->validate = array('title' => 'notEmpty', 'author_id' => 'numeric'); + $data = array( + array( + 'id' => '1', + 'title' => 'Un-Baleeted First Post', + 'body' => 'Not Baleeted!', + 'published' => 'Y' + ), + array( + 'id' => '2', + 'title' => '', + 'body' => 'Trying to get away with an empty title' + )); + $result = $TestModel->saveMany($data); + $this->assertFalse($result); + + $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); + $errors = array(1 => array('title' => array('This field cannot be left blank'))); + $transactionWorked = Set::matches('/Post[1][title=Baleeted First Post]', $result); + if (!$transactionWorked) { + $this->assertTrue(Set::matches('/Post[1][title=Un-Baleeted First Post]', $result)); + $this->assertTrue(Set::matches('/Post[2][title=Just update the title]', $result)); + } + + $this->assertEqual($TestModel->validationErrors, $errors); + + $TestModel->validate = array('title' => 'notEmpty', 'author_id' => 'numeric'); + $data = array( + array( + 'id' => '1', + 'title' => 'Un-Baleeted First Post', + 'body' => 'Not Baleeted!', + 'published' => 'Y' + ), + array( + 'id' => '2', + 'title' => '', + 'body' => 'Trying to get away with an empty title' + )); + $result = $TestModel->saveMany($data, array('validate' => true, 'atomic' => false)); + $this->assertEqual($result, array(true, false)); + $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); + $errors = array(1 => array('title' => array('This field cannot be left blank'))); + $newTs = date('Y-m-d H:i:s'); + $expected = array( + array( + 'Post' => array( + 'id' => '1', + 'author_id' => '1', + 'title' => 'Un-Baleeted First Post', + 'body' => 'Not Baleeted!', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23', + 'updated' => $newTs + )), + array( + 'Post' => array( + 'id' => '2', + 'author_id' => '3', + 'title' => 'Just update the title', + 'body' => 'Second Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:41:23', + 'updated' => $ts + )), + array( + 'Post' => array( + 'id' => '3', + 'author_id' => '1', + 'title' => 'Third Post', + 'body' => 'Third Post Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:43:23', + 'updated' => '2007-03-18 10:45:31' + )), + array( + 'Post' => array( + 'id' => '4', + 'author_id' => '2', + 'title' => 'Creating a fourth post', + 'body' => 'Fourth post body', + 'published' => 'N', + 'created' => $ts, + 'updated' => $ts + ))); + $this->assertEqual($expected, $result); + $this->assertEqual($TestModel->validationErrors, $errors); + + $data = array( + array( + 'id' => '1', + 'title' => 'Re-Baleeted First Post', + 'body' => 'Baleeted!', + 'published' => 'N' + ), + array( + 'id' => '2', + 'title' => '', + 'body' => 'Trying to get away with an empty title' + )); + $this->assertFalse($TestModel->saveMany($data, array('validate' => 'first'))); + + $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC')); + $this->assertEqual($expected, $result); + $this->assertEqual($TestModel->validationErrors, $errors); + } + +/** + * testValidateMany method + * + * @access public + * @return void + */ + public function testValidateMany() { + $TestModel = new Article(); + $TestModel->validate = array('title' => 'notEmpty'); + $result = $TestModel->validateMany( + array( + 0 => array('title' => ''), + 1 => array('title' => 'title 1'), + 2 => array('title' => 'title 2'), + )); + $this->assertFalse($result); + $expected = array( + 0 => array('title' => array('This field cannot be left blank')), + ); + $this->assertEqual($TestModel->validationErrors, $expected); + + $result = $TestModel->validateMany( + array( + 0 => array('title' => 'title 0'), + 1 => array('title' => ''), + 2 => array('title' => 'title 2'), + )); + $this->assertFalse($result); + $expected = array( + 1 => array('title' => array('This field cannot be left blank')), + ); + $this->assertEqual($TestModel->validationErrors, $expected); + } + +/** + * testSaveAssociatedValidateFirst method + * + * @access public + * @return void + */ + public function testSaveAssociatedValidateFirst() { + $this->loadFixtures('Article', 'Comment', 'Attachment'); + $model = new Article(); + $model->deleteAll(true); + + $model->Comment->validate = array('comment' => 'notEmpty'); + $result = $model->saveAssociated(array( + 'Article' => array( + 'title' => 'Post with Author', + 'body' => 'This post will be saved author' + ), + 'Comment' => array( + array('comment' => 'First new comment'), + array('comment' => '') + ) + ), array('validate' => 'first')); + + $this->assertFalse($result); + + $result = $model->find('all'); + $this->assertEqual($result, array()); + $expected = array('Comment' => array( + 1 => array('comment' => array('This field cannot be left blank')) + )); + + $this->assertEqual($model->Comment->validationErrors, $expected['Comment']); + + $this->assertIdentical($model->Comment->find('count'), 0); + + $result = $model->saveAssociated( + array( + 'Article' => array( + 'title' => 'Post with Author', + 'body' => 'This post will be saved with an author', + 'user_id' => 2 + ), + 'Comment' => array( + array( + 'comment' => 'Only new comment', + 'user_id' => 2 + ))), + array('validate' => 'first') + ); + + $this->assertIdentical($result, true); + + $result = $model->Comment->find('all'); + $this->assertIdentical(count($result), 1); + $result = Set::extract('/Comment/article_id', $result); + $this->assertEquals($result[0], 4); + + + $model->deleteAll(true); + $data = array( + 'Article' => array( + 'title' => 'Post with Author saveAlled from comment', + 'body' => 'This post will be saved with an author', + 'user_id' => 2 + ), + 'Comment' => array( + 'comment' => 'Only new comment', 'user_id' => 2 + )); + + $result = $model->Comment->saveAssociated($data, array('validate' => 'first')); + $this->assertFalse(empty($result)); + + $result = $model->find('all'); + $this->assertEqual( + $result[0]['Article']['title'], + 'Post with Author saveAlled from comment' + ); + $this->assertEqual($result[0]['Comment'][0]['comment'], 'Only new comment'); + } + +/** + * test saveMany()'s return is correct when using atomic = false and validate = first. + * + * @return void + */ + public function testSaveManyValidateFirstAtomicFalse() { + $Something = new Something(); + $invalidData = array( + array( + 'title' => 'foo', + 'body' => 'bar', + 'published' => 'baz', + ), + array( + 'body' => 3, + 'published' =>'sd', + ), + ); + $Something->create(); + $Something->validate = array( + 'title' => array( + 'rule' => 'alphaNumeric', + 'required' => true, + ), + 'body' => array( + 'rule' => 'alphaNumeric', + 'required' => true, + 'allowEmpty' => true, + ), + ); + $result = $Something->saveMany($invalidData, array( + 'atomic' => false, + 'validate' => 'first', + )); + $expected = array(true, false); + $this->assertEqual($expected, $result); + + $Something = new Something(); + $validData = array( + array( + 'title' => 'title value', + 'body' => 'body value', + 'published' => 'baz', + ), + array( + 'title' => 'valid', + 'body' => 'this body', + 'published' =>'sd', + ), + ); + $Something->create(); + $result = $Something->saveMany($validData, array( + 'atomic' => false, + 'validate' => 'first', + )); + $expected = array(true, true); + $this->assertEqual($expected, $result); + } + +/** + * testValidateAssociated method + * + * @access public + * @return void + */ + public function testValidateAssociated() { + $TestModel = new Comment(); + $TestModel->Attachment->validate = array('attachment' => 'notEmpty'); + + $data = array( + 'Comment' => array( + 'comment' => 'This is the comment' + ), + 'Attachment' => array( + 'attachment' => '' + ) + ); + + $result = $TestModel->validateAssociated($data); + $this->assertFalse($result); + + $TestModel = new Article(); + $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array(); + $TestModel->Comment->validate = array('comment' => 'notEmpty'); + + $result = $TestModel->validateAssociated( + array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'id' => 1, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1), + array( + 'id' => 2, + 'comment' => + 'comment', + 'published' => 'Y', + 'user_id' => 1 + )))); + $this->assertFalse($result); + + $result = $TestModel->validateAssociated( + array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'id' => 1, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1 + ), + array( + 'id' => 2, + 'comment' => 'comment', + 'published' => 'Y', + 'user_id' => 1 + ), + array( + 'id' => 3, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1 + ))), + array( + 'atomic' => false + )); + $expected = array( + 'Article' => true, + 'Comment' => array(false, true, false) + ); + $this->assertIdentical($expected, $result); + + $expected = array('Comment' => array( + 0 => array('comment' => array('This field cannot be left blank')), + 2 => array('comment' => array('This field cannot be left blank')) + )); + $this->assertEqual($TestModel->validationErrors, $expected); + + $expected = array( + 0 => array('comment' => array('This field cannot be left blank')), + 2 => array('comment' => array('This field cannot be left blank')) + ); + $this->assertEqual($TestModel->Comment->validationErrors, $expected); + } + +/** + * test that saveMany behaves like plain save() when suplied empty data + * + * @link http://cakephp.lighthouseapp.com/projects/42648/tickets/277-test-saveall-with-validation-returns-incorrect-boolean-when-saving-empty-data + * @access public + * @return void + */ + public function testSaveManyEmptyData() { + $this->skipIf($this->db instanceof Sqlserver, 'This test is not compatible with SQL Server.'); + + $this->loadFixtures('Article', 'ProductUpdateAll', 'Comment', 'Attachment'); + $model = new Article(); + $result = $model->saveMany(array(), array('validate' => true)); + $this->assertFalse(empty($result)); + + $model = new ProductUpdateAll(); + $result = $model->saveMany(array()); + $this->assertFalse($result); + } + +/** + * test that saveAssociated behaves like plain save() when suplied empty data + * + * @link http://cakephp.lighthouseapp.com/projects/42648/tickets/277-test-saveall-with-validation-returns-incorrect-boolean-when-saving-empty-data + * @access public + * @return void + */ + public function testSaveAssociatedEmptyData() { + $this->skipIf($this->db instanceof Sqlserver, 'This test is not compatible with SQL Server.'); + + $this->loadFixtures('Article', 'ProductUpdateAll', 'Comment', 'Attachment'); + $model = new Article(); + $result = $model->saveAssociated(array(), array('validate' => true)); + $this->assertFalse(empty($result)); + + $model = new ProductUpdateAll(); + $result = $model->saveAssociated(array()); + $this->assertFalse($result); + } + +/** + * testUpdateWithCalculation method + * + * @access public + * @return void + */ + public function testUpdateWithCalculation() { + $this->loadFixtures('DataTest'); + $model = new DataTest(); + $model->deleteAll(true); + $result = $model->saveMany(array( + array('count' => 5, 'float' => 1.1), + array('count' => 3, 'float' => 1.2), + array('count' => 4, 'float' => 1.3), + array('count' => 1, 'float' => 2.0), + )); + $this->assertFalse(empty($result)); + + $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); + $this->assertEqual($result, array(5, 3, 4, 1)); + + $this->assertTrue($model->updateAll(array('count' => 'count + 2'))); + $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); + $this->assertEqual($result, array(7, 5, 6, 3)); + + $this->assertTrue($model->updateAll(array('DataTest.count' => 'DataTest.count - 1'))); + $result = Set::extract('/DataTest/count', $model->find('all', array('fields' => 'count'))); + $this->assertEqual($result, array(6, 4, 5, 2)); + } + /** * TestFindAllWithoutForeignKey * @@ -3936,26 +5276,6 @@ class ModelWriteTest extends BaseModelTest { $this->assertEqual($resultsFkFalse, $expected); } -/** - * test that saveAll behaves like plain save() when suplied empty data - * - * @link http://cakephp.lighthouseapp.com/projects/42648/tickets/277-test-saveall-with-validation-returns-incorrect-boolean-when-saving-empty-data - * @access public - * @return void - */ - public function testSaveAllEmptyData() { - $this->skipIf($this->db instanceof Sqlserver, 'This test is not compatible with SQL Server.'); - - $this->loadFixtures('Article', 'ProductUpdateAll', 'Comment', 'Attachment'); - $model = new Article(); - $result = $model->saveAll(array(), array('validate' => 'first')); - $this->assertFalse(empty($result)); - - $model = new ProductUpdateAll(); - $result = $model->saveAll(array()); - $this->assertFalse($result); - } - /** * test writing floats in german locale. * diff --git a/lib/Cake/Test/Case/Model/models.php b/lib/Cake/Test/Case/Model/models.php index bc6676a67..e099c520d 100644 --- a/lib/Cake/Test/Case/Model/models.php +++ b/lib/Cake/Test/Case/Model/models.php @@ -3449,6 +3449,18 @@ class TransactionTestModel extends CakeTestModel { } } +class TransactionManyTestModel extends CakeTestModel { + var $name = 'TransactionManyTestModel'; + var $useTable = 'samples'; + + public function afterSave($created) { + $data = array( + array('apple_id' => 1, 'name' => 'sample6'), + ); + $this->saveMany($data, array('atomic' => true, 'callbacks' => false)); + } +} + /** * TestModel class *