Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Splitting saveAll into separate save and validation methods for many …

…rows and associated rows. Closes ticket #1157
  • Loading branch information...
commit b8daa99cac6e62f6050b1b54092e1b2cb6eab254 1 parent 8e6d018
ceeram ceeram authored
411 lib/Cake/Model/Model.php
View
@@ -1600,6 +1600,9 @@ protected function _prepareUpdateFields($data) {
}
/**
+ * 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 @@ protected function _prepareUpdateFields($data) {
* 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 @@ protected function _prepareUpdateFields($data) {
* @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') {
- $transactionBegun = $db->begin($this);
+ if ($options['validate'] === 'first') {
+ $validates = $this->validateMany($data, $options);
+ if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) {
+ return $validates;
+ }
}
- 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']) {
+ $db = $this->getDataSource();
+ $transactionBegun = $db->begin($this);
+ }
+ $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'] && !$validates) {
- $db->rollback($this);
- return false;
+ if (!$options['atomic']) {
+ $return[] = $validates;
+ } elseif (!$validates) {
+ break;
}
- return $return;
}
- $associations = $this->getAssociated();
+ $this->validationErrors = $validationErrors;
- 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 (!$options['atomic']) {
+ return $return;
+ }
+ if ($validates) {
+ if ($transactionBegun) {
+ return $db->commit($this) !== false;
+ } else {
+ return true;
}
+ }
+ $db->rollback($this);
+ return false;
+ }
- if (!$this->__save($data, $options)) {
- $validationErrors[$this->alias] = $this->validationErrors;
- $validates = false;
- }
- if (!$options['atomic']) {
- $return[$this->alias] = $validates;
+/**
+ * Validates multiple individual records for a single model
+ *
+ * #### 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
+ */
+ 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;
}
- $validating = ($options['validate'] === 'only' || $options['validate'] === 'first');
+ $return[] = $validates;
+ }
+ $this->validationErrors = $validationErrors;
+ if (!$options['atomic']) {
+ return $return;
+ }
+ if (empty($this->validationErrors)) {
+ return true;
+ }
+ return false;
+ }
- 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));
+/**
+ * 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;
+ }
- if ($_options['validate'] === 'first') {
- $_options['validate'] = 'only';
- }
- $_return = $this->{$association}->saveAll($values, $_options);
+ $options = array_merge(array('validate' => true, 'atomic' => true), $options);
+ $this->validationErrors = $validationErrors = array();
- 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 (empty($data) && $options['validate'] !== false) {
+ $result = $this->save($data, $options);
+ return !empty($result);
+ }
- if (isset($validationErrors[$this->alias])) {
- $this->validationErrors = $validationErrors[$this->alias];
+ 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;
- switch (true) {
- case ($options['validate'] === 'only'):
- return ($options['atomic'] ? $validates : $return);
- break;
- case ($options['validate'] === 'first'):
- $options['validate'] = true;
- $return = array();
+ foreach ($data as $association => $values) {
+ if (!$validates) {
break;
- default:
- if ($options['atomic']) {
- if ($validates) {
- if ($transactionBegun) {
- return $db->commit($this) !== false;
- } else {
- return true;
- }
- } else {
- $db->rollback($this);
+ }
+ 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 $return;
- break;
+ $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;
+ }
}
- if ($options['atomic'] && !$validates) {
- $db->rollback($this);
- return false;
+ }
+ $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;
}
}
- return $return;
+ $db->rollback($this);
+ return false;
}
/**
- * Private helper method used by saveAll.
+ * Validates a single record, as well as all its directly associated records.
*
- * @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 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
*/
- private function __save($data, $options) {
- if ($options['validate'] === 'first' || $options['validate'] === 'only') {
- if (!($this->create($data) && $this->validates($options))) {
- return false;
+ 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;
+ }
}
- } elseif (!($this->create(null) !== null && $this->save($data, $options))) {
+ }
+
+ $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;
1,592 lib/Cake/Test/Case/Model/ModelWriteTest.php
View
@@ -2950,8 +2950,1330 @@ public function testSaveAllHasManyValidation() {
array('comment' => '', 'published' => 'Y', 'user_id' => 1),
)
), array('validate' => true));
- $expected = array('Comment' => array(false));
+ $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->saveAll(array(
+ 'Article' => array('id' => 2),
+ 'Comment' => array(
+ array(
+ 'comment' => '',
+ 'published' => 'Y',
+ 'user_id' => 1
+ ))
+ ), array('validate' => 'first'));
+ $this->assertFalse($result);
+ }
+
+/**
+ * test saveAll with transactions and ensure there is no missing rollback.
+ *
+ * @return void
+ */
+ public function testSaveAllManyRowsTransactionNoRollback() {
+ $this->loadFixtures('Post');
+
+ $this->getMock('DboSource', array(), array(), 'MockTransactionDboSource');
+ $db = ConnectionManager::create('mock_transaction', array(
+ 'datasource' => 'MockTransactionDboSource',
+ ));
+
+ $db->expects($this->once())
+ ->method('describe')
+ ->will($this->returnValue(array()));
+ $db->expects($this->once())->method('rollback');
+
+ $Post = new Post('mock_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->saveAll($data, array('atomic' => true));
+ }
+
+/**
+ * test saveAll with transactions and ensure there is no missing rollback.
+ *
+ * @return void
+ */
+ public function testSaveAllAssociatedTransactionNoRollback() {
+ $testDb = ConnectionManager::getDataSource('test');
+
+ $mock = $this->getMock('DboSource', array(), array(), 'MockTransactionAssociatedDboSource', false);
+ $db = ConnectionManager::create('mock_transaction_assoc', array(
+ 'datasource' => 'MockTransactionAssociatedDboSource',
+ ));
+ $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_transaction_assoc';
+ $Post->Author->useDbConfig = 'mock_transaction_assoc';
+
+ $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->saveAll($data, array('validate' => true));
+ }
+
+/**
+ * test saveAll with nested saveAll call.
+ *
+ * @return void
+ */
+ public function testSaveAllNestedSaveAll() {
+ $this->loadFixtures('Sample');
+ $TransactionTestModel = new TransactionTestModel();
+
+ $data = array(
+ array('apple_id' => 1, 'name' => 'sample5'),
+ );
+
+ $this->assertTrue($TransactionTestModel->saveAll($data, array('atomic' => true)));
+ }
+
+/**
+ * testSaveAllTransaction method
+ *
+ * @access public
+ * @return void
+ */
+ public function testSaveAllTransaction() {
+ $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->saveAll($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->saveAll($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->saveAll($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);
+ }
+
+/**
+ * testSaveAllValidation method
+ *
+ * @access public
+ * @return void
+ */
+ public function testSaveAllValidation() {
+ $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->saveAll($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->saveAll($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->saveAll($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->saveAll($data, array('validate' => 'first')));
+
+ $result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC'));
+ $this->assertEqual($expected, $result);
+ $this->assertEqual($TestModel->validationErrors, $errors);
+ }
+
+/**
+ * testSaveAllValidationOnly method
+ *
+ * @access public
+ * @return void
+ */
+ public function testSaveAllValidationOnly() {
+ $this->loadFixtures('Comment', 'Attachment');
+ $TestModel = new Comment();
+ $TestModel->Attachment->validate = array('attachment' => 'notEmpty');
+
+ $data = array(
+ 'Comment' => array(
+ 'comment' => 'This is the comment'
+ ),
+ 'Attachment' => array(
+ 'attachment' => ''
+ )
+ );
+
+ $result = $TestModel->saveAll($data, array('validate' => 'only'));
+ $this->assertFalse($result);
+
+ $TestModel = new Article();
+ $TestModel->validate = array('title' => 'notEmpty');
+ $result = $TestModel->saveAll(
+ array(
+ 0 => array('title' => ''),
+ 1 => array('title' => 'title 1'),
+ 2 => array('title' => 'title 2'),
+ ),
+ array('validate'=>'only')
+ );
+ $this->assertFalse($result);
+ $expected = array(
+ 0 => array('title' => array('This field cannot be left blank')),
+ );
+ $this->assertEqual($TestModel->validationErrors, $expected);
+
+ $result = $TestModel->saveAll(
+ array(
+ 0 => array('title' => 'title 0'),
+ 1 => array('title' => ''),
+ 2 => array('title' => 'title 2'),
+ ),
+ array('validate'=>'only')
+ );
+ $this->assertFalse($result);
+ $expected = array(
+ 1 => array('title' => array('This field cannot be left blank')),
+ );
+ $this->assertEqual($TestModel->validationErrors, $expected);
+ }
+
+/**
+ * testSaveAllValidateFirst method
+ *
+ * @access public
+ * @return void
+ */
+ public function testSaveAllValidateFirst() {
+ $this->loadFixtures('Article', 'Comment', 'Attachment');
+ $model = new Article();
+ $model->deleteAll(true);
+
+ $model->Comment->validate = array('comment' => 'notEmpty');
+ $result = $model->saveAll(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->saveAll(
+ 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->saveAll($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 saveAll()'s return is correct when using atomic = false and validate = first.
+ *
+ * @return void
+ */
+ public function testSaveAllValidateFirstAtomicFalse() {
+ $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->saveAll($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->saveAll($validData, array(
+ 'atomic' => false,
+ 'validate' => 'first',
+ ));
+ $expected = array(true, true);
+ $this->assertEqual($expected, $result);
+ }
+
+/**
+ * testSaveAllHasManyValidationOnly method
+ *
+ * @access public
+ * @return void
+ */
+ public function testSaveAllHasManyValidationOnly() {
+ $this->loadFixtures('Article', 'Comment', 'Attachment');
+ $TestModel = new Article();
+ $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array();
+ $TestModel->Comment->validate = array('comment' => 'notEmpty');
+
+ $result = $TestModel->saveAll(
+ 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('validate' => 'only')
+ );
+ $this->assertFalse($result);
+
+ $result = $TestModel->saveAll(
+ 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(
+ 'validate' => 'only',
+ '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 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'))
@@ -2962,7 +4284,7 @@ public function testSaveAllHasManyValidation() {
);
$this->assertEqual($TestModel->Comment->validationErrors, $expected);
- $result = $TestModel->saveAll(array(
+ $result = $TestModel->saveAssociated(array(
'Article' => array('id' => 2),
'Comment' => array(
array(
@@ -2975,16 +4297,16 @@ public function testSaveAllHasManyValidation() {
}
/**
- * test saveAll with transactions and ensure there is no missing rollback.
+ * test saveMany with transactions and ensure there is no missing rollback.
*
* @return void
*/
- public function testSaveAllManyRowsTransactionNoRollback() {
+ public function testSaveManyTransactionNoRollback() {
$this->loadFixtures('Post');
- $this->getMock('DboSource', array(), array(), 'MockTransactionDboSource');
- $db = ConnectionManager::create('mock_transaction', array(
- 'datasource' => 'MockTransactionDboSource',
+ $this->getMock('DboSource', array(), array(), 'MockManyTransactionDboSource');
+ $db = ConnectionManager::create('mock_many_transaction', array(
+ 'datasource' => 'MockManyTransactionDboSource',
));
$db->expects($this->once())
@@ -2992,7 +4314,7 @@ public function testSaveAllManyRowsTransactionNoRollback() {
->will($this->returnValue(array()));
$db->expects($this->once())->method('rollback');
- $Post = new Post('mock_transaction');
+ $Post = new Post('mock_many_transaction');
$Post->validate = array(
'title' => array('rule' => array('notEmpty'))
@@ -3002,20 +4324,20 @@ public function testSaveAllManyRowsTransactionNoRollback() {
array('author_id' => 1, 'title' => 'New Fourth Post'),
array('author_id' => 1, 'title' => '')
);
- $Post->saveAll($data, array('atomic' => true));
+ $Post->saveMany($data);
}
/**
- * test saveAll with transactions and ensure there is no missing rollback.
+ * test saveAssociated with transactions and ensure there is no missing rollback.
*
* @return void
*/
- public function testSaveAllAssociatedTransactionNoRollback() {
+ public function testSaveAssociatedTransactionNoRollback() {
$testDb = ConnectionManager::getDataSource('test');
- $mock = $this->getMock('DboSource', array(), array(), 'MockTransactionAssociatedDboSource', false);
- $db = ConnectionManager::create('mock_transaction_assoc', array(
- 'datasource' => 'MockTransactionAssociatedDboSource',
+ $mock = $this->getMock('DboSource', array(), array(), 'MockAssociatedTransactionDboSource', false);
+ $db = ConnectionManager::create('mock_assoc_transaction', array(
+ 'datasource' => 'MockAssociatedTransactionDboSource',
));
$this->mockObjects[] = $db;
$db->columns = $testDb->columns;
@@ -3030,8 +4352,8 @@ public function testSaveAllAssociatedTransactionNoRollback() {
)));
$Post = new Post();
- $Post->useDbConfig = 'mock_transaction_assoc';
- $Post->Author->useDbConfig = 'mock_transaction_assoc';
+ $Post->useDbConfig = 'mock_assoc_transaction';
+ $Post->Author->useDbConfig = 'mock_assoc_transaction';
$Post->Author->validate = array(
'user' => array('rule' => array('notEmpty'))
@@ -3048,32 +4370,32 @@ public function testSaveAllAssociatedTransactionNoRollback() {
'password' => "sekret"
)
);
- $Post->saveAll($data);
+ $Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
}
/**
- * test saveAll with nested saveAll call.
+ * test saveMany with nested saveMany call.
*
* @return void
*/
- public function testSaveAllNestedSaveAll() {
+ public function testSaveManyNestedSaveMany() {
$this->loadFixtures('Sample');
- $TransactionTestModel = new TransactionTestModel();
+ $TransactionManyTestModel = new TransactionManyTestModel();
$data = array(
array('apple_id' => 1, 'name' => 'sample5'),
);
- $this->assertTrue($TransactionTestModel->saveAll($data, array('atomic' => true)));
+ $this->assertTrue($TransactionManyTestModel->saveMany($data, array('atomic' => true)));
}
/**
- * testSaveAllTransaction method
+ * testSaveManyTransaction method
*
* @access public
* @return void
*/
- public function testSaveAllTransaction() {
+ public function testSaveManyTransaction() {
$this->loadFixtures('Post', 'Author', 'Comment', 'Attachment');
$TestModel = new Post();
@@ -3084,7 +4406,7 @@ public function testSaveAllTransaction() {
array('author_id' => 1, 'title' => '')
);
$ts = date('Y-m-d H:i:s');
- $this->assertFalse($TestModel->saveAll($data));
+ $this->assertFalse($TestModel->saveMany($data));
$result = $TestModel->find('all', array('recursive' => -1));
$expected = array(
@@ -3153,7 +4475,7 @@ public function testSaveAllTransaction() {
array('author_id' => 1, 'title' => 'New Sixth Post')
);
$ts = date('Y-m-d H:i:s');
- $this->assertFalse($TestModel->saveAll($data));
+ $this->assertFalse($TestModel->saveMany($data));
$result = $TestModel->find('all', array('recursive' => -1));
$expected = array(
@@ -3217,7 +4539,7 @@ public function testSaveAllTransaction() {
array('author_id' => 1, 'title' => 'New Fifth Post'),
array('author_id' => 1, 'title' => 'New Sixth Post')
);
- $this->assertTrue($TestModel->saveAll($data));
+ $this->assertTrue($TestModel->saveMany($data));
$result = $TestModel->find('all', array(
'recursive' => -1,
@@ -3265,12 +4587,12 @@ public function testSaveAllTransaction() {
}
/**
- * testSaveAllValidation method
+ * testSaveManyValidation method
*
* @access public
* @return void
*/
- public function testSaveAllValidation() {
+ public function testSaveManyValidation() {
$this->loadFixtures('Post', 'Author', 'Comment', 'Attachment');
$TestModel = new Post();
@@ -3291,7 +4613,7 @@ public function testSaveAllValidation() {
'author_id' => 2
));
- $this->assertTrue($TestModel->saveAll($data));
+ $this->assertTrue($TestModel->saveMany($data));
$result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC'));
$ts = date('Y-m-d H:i:s');
@@ -3350,7 +4672,7 @@ public function testSaveAllValidation() {
'title' => '',
'body' => 'Trying to get away with an empty title'
));
- $result = $TestModel->saveAll($data);
+ $result = $TestModel->saveMany($data);
$this->assertFalse($result);
$result = $TestModel->find('all', array('recursive' => -1, 'order' => 'Post.id ASC'));
@@ -3376,7 +4698,7 @@ public function testSaveAllValidation() {
'title' => '',
'body' => 'Trying to get away with an empty title'
));
- $result = $TestModel->saveAll($data, array('validate' => true, 'atomic' => false));
+ $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')));
@@ -3437,73 +4759,40 @@ public function testSaveAllValidation() {
'title' => '',
'body' => 'Trying to get away with an empty title'
));
- $this->assertFalse($TestModel->saveAll($data, array('validate' => 'first')));
+ $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);
-
- $data = array(
- array(
- 'title' => 'First new post',
- 'body' => 'Woohoo!',
- 'published' => 'Y'
- ),
- array(
- 'title' => 'Empty body',
- 'body' => ''
- ));
-
- $TestModel->validate['body'] = 'notEmpty';
}
/**
- * testSaveAllValidationOnly method
+ * testValidateMany method
*
* @access public
* @return void
*/
- public function testSaveAllValidationOnly() {
- $this->loadFixtures('Comment', 'Attachment');
- $TestModel = new Comment();
- $TestModel->Attachment->validate = array('attachment' => 'notEmpty');
-
- $data = array(
- 'Comment' => array(
- 'comment' => 'This is the comment'
- ),
- 'Attachment' => array(
- 'attachment' => ''
- )
- );
-
- $result = $TestModel->saveAll($data, array('validate' => 'only'));
- $this->assertFalse($result);
-
+ public function testValidateMany() {
$TestModel = new Article();
$TestModel->validate = array('title' => 'notEmpty');
- $result = $TestModel->saveAll(
+ $result = $TestModel->validateMany(
array(
0 => array('title' => ''),
1 => array('title' => 'title 1'),
2 => array('title' => 'title 2'),
- ),
- array('validate'=>'only')
- );
+ ));
$this->assertFalse($result);
$expected = array(
0 => array('title' => array('This field cannot be left blank')),
);
$this->assertEqual($TestModel->validationErrors, $expected);
- $result = $TestModel->saveAll(
+ $result = $TestModel->validateMany(
array(
0 => array('title' => 'title 0'),
1 => array('title' => ''),