Skip to content

Commit

Permalink
Fix afterFind() called twice with hasMany relationship
Browse files Browse the repository at this point in the history
It occurs when a model and the children models are related to a same model.
For example, such as the following:

* User hasMany Comment
* User hasMany Article
* Article hasMany Comment
  • Loading branch information
chinpei215 committed Aug 10, 2014
1 parent cb45821 commit c227c14
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 4 deletions.
28 changes: 26 additions & 2 deletions lib/Cake/Model/Datasource/DboSource.php
Expand Up @@ -1205,6 +1205,30 @@ protected function _filterResults(&$resultSet, Model $Model, $filtered = array()
return $filtering;
}

/**
* Passes association results through afterFind filters of the corresponding model.
*
* Similar to DboSource::_filterResults(), but this filters only specified models.
* The primary model can not be specified, because this call DboSource::_filterResults() internally.
*
* @param array &$resultSet Reference of resultset to be filtered.
* @param Model $Model Instance of model to operate against.
* @param array $toBeFiltered List of classes to be filtered.
* @return array Array of results that have been filtered through $Model->afterFind.
*/
protected function _filterResultsInclusive(&$resultSet, Model $Model, $toBeFiltered = array()) {
$exclude = array();

if (is_array($resultSet)) {
$current = reset($resultSet);
if (is_array($current)) {
$exclude = array_diff(array_keys($current), $toBeFiltered);
}
}

return $this->_filterResults($resultSet, $Model, $exclude);
}

/**
* Queries associations.
*
Expand Down Expand Up @@ -1276,7 +1300,7 @@ public function queryAssociation(Model $Model, Model $LinkModel, $type, $associa

// Filter
if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
$this->_filterResults($assocResultSet, $Model);
$this->_filterResultsInclusive($assocResultSet, $Model, array($association));
}

// Merge
Expand Down Expand Up @@ -1305,7 +1329,7 @@ public function queryAssociation(Model $Model, Model $LinkModel, $type, $associa

// Filter
if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
$this->_filterResults($assocResultSet, $Model);
$this->_filterResultsInclusive($assocResultSet, $Model, array($association, $with));
}
}

Expand Down
33 changes: 31 additions & 2 deletions lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php
Expand Up @@ -1463,7 +1463,7 @@ public function testDefaultConditions() {
}

/**
* Test that count how many times is afterFind called
* Test that count how many times afterFind is called
*
* @return void
*/
Expand All @@ -1472,7 +1472,6 @@ public function testCountAfterFindCalls() {

// Use alias to make testing "primary = true" easy
$Primary = $this->getMock('Comment', array('afterFind'), array(array('alias' => 'Primary')), '', true);
$Primary->expects($this->any())->method('afterFind')->will($this->returnArgument(0));

$Article = $this->getMock('Article', array('afterFind'), array(), '', true);
$User = $this->getMock('User', array('afterFind'), array(), '', true);
Expand Down Expand Up @@ -1507,5 +1506,35 @@ public function testCountAfterFindCalls() {
$result = $Primary->find('first', array('conditions' => array('Primary.id' => 5), 'recursive' => 2));
$this->assertCount(2, $result['Article']['Tag']);
$this->assertCount(2, $result['Article']['Comment']);

// hasMany special case
// Both User and Article has many Comments
$User = $this->getMock('User', array('afterFind'), array(), '', true);
$Article = $this->getMock('Article', array('afterFind'), array(), '', true);
$Comment = $this->getMock('Comment', array('afterFind'), array(), '', true);

$User->bindModel(array('hasMany' => array('Comment', 'Article')));
$Article->unbindModel(array('belongsTo' => array('User'), 'hasAndBelongsToMany' => array('Tag')));
$Comment->unbindModel(array('belongsTo' => array('User', 'Article'), 'hasOne' => 'Attachment'));

$User->Comment = $Comment;
$User->Article = $Article;
$User->Article->Comment = $Comment;

// primary = true
$User->expects($this->once())
->method('afterFind')->with($this->anything(), $this->isTrue())->will($this->returnArgument(0));

$Article->expects($this->exactly(2)) // User has 2 Articles
->method('afterFind')->with($this->anything(), $this->isFalse())->will($this->returnArgument(0));

$Comment->expects($this->exactly(7)) // User1 has 3 Comments, Article[id=1] has 4 Comments and Article[id=3] has 0 Comments
->method('afterFind')->with($this->anything(), $this->isFalse())->will($this->returnArgument(0));

$result = $User->find('first', array('conditions' => array('User.id' => 1), 'recursive' => 2));
$this->assertCount(3, $result['Comment']);
$this->assertCount(2, $result['Article']);
$this->assertCount(4, $result['Article'][0]['Comment']);
$this->assertCount(0, $result['Article'][1]['Comment']);
}
}

0 comments on commit c227c14

Please sign in to comment.