Skip to content

Commit

Permalink
Implementing HasOne association
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzo committed May 12, 2013
1 parent d007649 commit e4c8668
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 0 deletions.
104 changes: 104 additions & 0 deletions lib/Cake/ORM/Association/HasOne.php
@@ -0,0 +1,104 @@
<?php
/**
* PHP Version 5.4
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since CakePHP(tm) v 3.0.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace Cake\ORM\Association;

use Cake\ORM\Association;
use Cake\ORM\Query;
use Cake\Utility\Inflector;

/**
* Represents an 1 - 1 relationship where the source side of the relation is
* related to only one record in the target table and vice versa.
*
*/
class HasOne extends Association {

/**
* Whether this association can be expressed directly in a query join
*
* @var boolean
*/
protected $_canBeJoined = true;

/**
* Sets the name of the field representing the foreign key to the target table.
* If no parameters are passed current field is returned
*
* @param string $key the key to be used to link both tables together
* @return string
*/
public function foreignKey($key = null) {
if ($key === null) {
if ($this->_foreignKey === null) {
$this->_foreignKey = Inflector::underscore($this->source()->alias()) . '_id';
}
return $this->_foreignKey;
}
return parent::foreignKey($key);
}

/**
* Alters a Query object to include the associated target table data in the final
* result
*
* The options array accept the following keys:
*
* - includeFields: Whether to include target model fields in the result or not
* - foreignKey: The name of the field to use as foreign key, if false none
* will be sued
* - conditions: array with a list of conditions to filter the join with
* - fields: a list of fields in the target table to include in the result
*
* @param Query $query the query to be altered to include the target table data
* @param array $options Any extra options or overrides to be taken in account
* @return void
*/
public function attachTo(Query $query, array $options = []) {
$target = $this->target();
$source = $this->source();
$options += [
'includeFields' => true,
'foreignKey' => $this->foreignKey(),
'conditions' => []
];
$options['conditions'] = array_merge($this->conditions(), $options['conditions']);

if (!empty($options['foreignKey'])) {
$options['conditions'][] = sprintf('%s.%s = %s.%s',
$source->alias(),
$source->primaryKey(),
$target->alias(),
$options['foreignKey']
);
}

$joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1];
$query->join([$target->alias() => array_intersect_key($options, $joinOptions)]);

if (empty($options['fields'])) {
$f = isset($options['fields']) ? $options['fields'] : null;
if ($options['includeFields'] && ($f === null || $f !== false)) {
$options['fields'] = array_keys($target->schema());
}
}

if (!empty($options['fields'])) {
$query->select($query->aliasFields($options['fields'], $target->alias()));
}
}

}
153 changes: 153 additions & 0 deletions lib/Cake/Test/TestCase/ORM/Association/HasOneTest.php
@@ -0,0 +1,153 @@
<?php
/**
* PHP Version 5.4
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since CakePHP(tm) v 3.0.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace Cake\Test\TestCase\ORM\Association;

use Cake\ORM\Association\HasOne;
use Cake\ORM\Table;
use Cake\ORM\Query;

/**
* Tests HasOne class
*
*/
class HasOneTest extends \Cake\TestSuite\TestCase {

/**
* Set up
*
* @return void
*/
public function setUp() {
$this->user = Table::build('User', [
'schema' => [
'id' => ['type' => 'integer'],
'username' => ['type' => 'string'],
]
]);
$this->profile = Table::build('Profile', [
'schema' => [
'id' => ['type' => 'integer'],
'first_name' => ['type' => 'string'],
'user_id' => ['type' => 'integer'],
]
]);
}

/**
* Tear down
*
* @return void
*/
public function tearDown() {
Table::clearRegistry();
}

/**
* Tests that the association reports it can be joined
*
* @return void
*/
public function testCanBeJoined() {
$assoc = new HasOne('Test');
$this->assertTrue($assoc->canBeJoined());
}

/**
* Tests that the correct join and fields are attached to a query depending on
* the association config
*
* @return void
*/
public function testAttachTo() {
$query = $this->getMock('\Cake\ORM\Query', ['join', 'select'], [null]);
$config = [
'foreignKey' => 'user_id',
'sourceTable' => $this->user,
'targetTable' => $this->profile,
'conditions' => ['Profile.is_active' => true]
];
$association = new HasOne('Profile', $config);
$query->expects($this->once())->method('join')->with([
'Profile' => ['conditions' => [
'Profile.is_active' => true,
'User.id = Profile.user_id',
]]
]);
$query->expects($this->once())->method('select')->with([
'Profile__id' => 'Profile.id',
'Profile__first_name' => 'Profile.first_name',
'Profile__user_id' => 'Profile.user_id'
]);
$association->attachTo($query);
}

/**
* Tests that default config defined in the association can be overridden
*
* @return void
*/
public function testAttachToConfigOverride() {
$query = $this->getMock('\Cake\ORM\Query', ['join', 'select'], [null]);
$config = [
'foreignKey' => 'user_id',
'sourceTable' => $this->user,
'targetTable' => $this->profile,
'conditions' => ['Profile.is_active' => true]
];
$association = new HasOne('Profile', $config);
$query->expects($this->once())->method('join')->with([
'Profile' => ['conditions' => [
'Profile.is_active' => false
]]
]);
$query->expects($this->once())->method('select')->with([
'Profile__first_name' => 'Profile.first_name'
]);

$override = [
'conditions' => ['Profile.is_active' => false],
'foreignKey' => false,
'fields' => ['first_name']
];
$association->attachTo($query, $override);
}

/**
* Tests that it is possible to avoid fields inclusion for the associated table
*
* @return void
*/
public function testAttachToNoFields() {
$query = $this->getMock('\Cake\ORM\Query', ['join', 'select'], [null]);
$config = [
'sourceTable' => $this->user,
'targetTable' => $this->profile,
'conditions' => ['Profile.is_active' => true]
];
$association = new HasOne('Profile', $config);
$query->expects($this->once())->method('join')->with([
'Profile' => ['conditions' => [
'Profile.is_active' => true,
'User.id = Profile.user_id',
]]
]);
$query->expects($this->never())->method('select');
$association->attachTo($query, ['includeFields' => false]);
}

}

0 comments on commit e4c8668

Please sign in to comment.