Skip to content

Commit

Permalink
Implemented eager loading for HasMany and multiple foreignKeys
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzo committed Jan 12, 2014
1 parent cd086e8 commit 75ab777
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 4 deletions.
40 changes: 38 additions & 2 deletions Cake/ORM/Association/ExternalAssociationTrait.php
Expand Up @@ -17,6 +17,7 @@
namespace Cake\ORM\Association;

use Cake\Database\Expression\IdentifierExpression;
use Cake\Database\Expression\TupleComparison;
use Cake\ORM\Query;
use Cake\Utility\Inflector;

Expand Down Expand Up @@ -164,8 +165,13 @@ protected function _resultInjector($fetchQuery, $resultMap) {
$sourceKeys[] = key($fetchQuery->aliasField($key, $sAlias));
}

$sourceKey = implode(';', $sourceKeys);
$nestKey = $tAlias . '__' . $tAlias;

if (count($sourceKeys) > 1) {
return $this->_multiKeysInjector($resultMap, $sourceKeys, $nestKey);
}

$sourceKey = $sourceKeys[0];
return function($row) use ($resultMap, $sourceKey, $nestKey) {
if (isset($resultMap[$row[$sourceKey]])) {
$row[$nestKey] = $resultMap[$row[$sourceKey]];
Expand All @@ -174,6 +180,21 @@ protected function _resultInjector($fetchQuery, $resultMap) {
};
}

protected function _multiKeysInjector($resultMap, $sourceKeys, $nestKey) {
return function($row) use ($resultMap, $sourceKeys, $nestKey) {
$values = [];
foreach ($sourceKeys as $key) {
$values[] = $row[$key];
}

$key = implode(';', $values);
if (isset($resultMap[$key])) {
$row[$nestKey] = $resultMap[$key];
}
return $row;
};
}

/**
* Auxiliary function to construct a new Query object to return all the records
* in the target table that are associated to those specified in $options from
Expand Down Expand Up @@ -234,6 +255,10 @@ protected function _buildQuery($options) {
* @return \Cake\ORM\Query
*/
protected function _addFilteringCondition($query, $key, $filter) {
if (is_array($key)) {
$tuple = new TupleComparison($key, $filter, [], 'IN');
return $query->andWhere($tuple);
}
return $query->andWhere([$key . ' IN' => $filter]);
}

Expand All @@ -245,7 +270,18 @@ protected function _addFilteringCondition($query, $key, $filter) {
* @return string
*/
protected function _linkField($options) {
return sprintf('%s.%s', $this->name(), $options['foreignKey']);
$links = [];
$name = $this->name();

foreach ((array)$options['foreignKey'] as $key) {
$links[] = sprintf('%s.%s', $name, $key);
}

if (count($links) === 1) {
return $links[0];
}

return $links;
}

/**
Expand Down
9 changes: 7 additions & 2 deletions Cake/ORM/Association/HasMany.php
Expand Up @@ -91,9 +91,14 @@ public function eagerLoader(array $options) {
}

$resultMap = [];
$key = $options['foreignKey'];
$key = (array)$options['foreignKey'];

foreach ($fetchQuery->all() as $result) {
$resultMap[$result[$key]][] = $result;
$values = [];
foreach ($key as $k) {
$values[] = $result[$k];
}
$resultMap[implode(';', $values)][] = $result;
}

return $this->_resultInjector($fetchQuery, $resultMap);
Expand Down
42 changes: 42 additions & 0 deletions Test/TestCase/ORM/Association/HasManyTest.php
Expand Up @@ -414,6 +414,48 @@ public function testEagerLoaderWithQueryBuilder() {
$association->eagerLoader(compact('keys', 'query', 'queryBuilder'));
}

/**
* Test the eager loader method with no extra options
*
* @return void
*/
public function testEagerLoaderMultipleKeys() {
$config = [
'sourceTable' => $this->author,
'targetTable' => $this->article,
'strategy' => 'select',
'foreignKey' => ['author_id', 'site_id']
];

$this->author->primaryKey(['id', 'site_id']);
$association = new HasMany('Articles', $config);
$keys = [[1, 10], [2, 20], [3, 30], [4, 40]];
$query = $this->getMock('Cake\ORM\Query', ['all'], [null, null]);
$this->article->expects($this->once())->method('find')->with('all')
->will($this->returnValue($query));
$results = [
['id' => 1, 'title' => 'article 1', 'author_id' => 2, 'site_id' => 10],
['id' => 2, 'title' => 'article 2', 'author_id' => 1, 'site_id' => 20]
];
$query->expects($this->once())->method('all')
->will($this->returnValue($results));

$callable = $association->eagerLoader(compact('keys', 'query'));
$row = ['Authors__id' => 2, 'Authors__site_id' => 10, 'username' => 'author 1'];
$result = $callable($row);
$row['Articles__Articles'] = [
['id' => 1, 'title' => 'article 1', 'author_id' => 2, 'site_id' => 10]
];
$this->assertEquals($row, $result);

$row = ['Authors__id' => 1, 'username' => 'author 2', 'Authors__site_id' => 20];
$result = $callable($row);
$row['Articles__Articles'] = [
['id' => 2, 'title' => 'article 2', 'author_id' => 1, 'site_id' => 20]
];
$this->assertEquals($row, $result);
}

/**
* Tests that the correct join and fields are attached to a query depending on
* the association config
Expand Down

0 comments on commit 75ab777

Please sign in to comment.