Skip to content

Commit

Permalink
Merge pull request #842 from CakeDC/issue/839-fix-belongs-to
Browse files Browse the repository at this point in the history
Avoid generating association if table object or database table exist
  • Loading branch information
markstory committed Jul 22, 2022
2 parents 2078952 + dc59fbc commit 808608b
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 61 deletions.
13 changes: 10 additions & 3 deletions src/Command/ModelCommand.php
Expand Up @@ -346,11 +346,18 @@ public function findBelongsTo(Table $model, array $associations): array
];
} else {
$tmpModelName = $this->_modelNameFromKey($fieldName);
if (!in_array(Inflector::tableize($tmpModelName), $this->_tables, true)) {
$associationTable = $this->getTableLocator()->get($tmpModelName);
$tables = $this->listAll();
// Check if association model could not be instantiated as a subclass but a generic Table instance instead
if (
get_class($associationTable) === Table::class &&
!in_array(Inflector::tableize($tmpModelName), $tables, true)
) {
$found = $this->findTableReferencedBy($schema, $fieldName);
if ($found) {
$tmpModelName = Inflector::camelize($found);
if (!$found) {
continue;
}
$tmpModelName = Inflector::camelize($found);
}
$assoc = [
'alias' => $tmpModelName,
Expand Down
96 changes: 94 additions & 2 deletions tests/TestCase/Command/ModelCommandTest.php
Expand Up @@ -375,6 +375,96 @@ public function testGetAssociationsPlugin()
$this->assertEquals($expected, $result);
}

/**
* Test that association generation ignores `anything_id` fields if
* AnythingsTable object nor `anythings` database table exist
*
* @return void
*/
public function testGetAssociationsIgnoreUnderscoreIdIfNoDbTable()
{
$items = $this->getTableLocator()->get('TodoItems');

$items->setSchema($items->getSchema()->addColumn('anything_id', ['type' => 'integer']));
$command = new ModelCommand();
$command->connection = 'test';

$args = new Arguments([], [], []);
$io = $this->createMock(ConsoleIo::class);
$result = $command->getAssociations($items, $args, $io);
$expected = [
'belongsTo' => [
[
'alias' => 'Users',
'foreignKey' => 'user_id',
'joinType' => 'INNER',
],
],
'hasMany' => [
[
'alias' => 'TodoTasks',
'foreignKey' => 'todo_item_id',
],
],
'belongsToMany' => [
[
'alias' => 'TodoLabels',
'foreignKey' => 'todo_item_id',
'joinTable' => 'todo_items_todo_labels',
'targetForeignKey' => 'todo_label_id',
],
],
];
$this->assertEquals($expected, $result);
}

/**
* Test that association generation adds association when `anything_id` fields and
* AnythingsTable object exist even if no db table
*
* @return void
*/
public function testGetAssociationsAddAssociationIfTableExist()
{
$items = $this->getTableLocator()->get('TodoItems');

$items->setSchema($items->getSchema()->addColumn('template_task_comment_id', ['type' => 'integer']));
$command = new ModelCommand();
$command->connection = 'test';

$args = new Arguments([], [], []);
$io = $this->createMock(ConsoleIo::class);
$result = $command->getAssociations($items, $args, $io);
$expected = [
'belongsTo' => [
[
'alias' => 'Users',
'foreignKey' => 'user_id',
'joinType' => 'INNER',
],
[
'alias' => 'TemplateTaskComments',
'foreignKey' => 'template_task_comment_id',
],
],
'hasMany' => [
[
'alias' => 'TodoTasks',
'foreignKey' => 'todo_item_id',
],
],
'belongsToMany' => [
[
'alias' => 'TodoLabels',
'foreignKey' => 'todo_item_id',
'joinTable' => 'todo_items_todo_labels',
'targetForeignKey' => 'todo_label_id',
],
],
];
$this->assertEquals($expected, $result);
}

/**
* Test that association generation ignores `_id` fields
*
Expand Down Expand Up @@ -491,6 +581,7 @@ public function testBelongsToGenerationConstraints()
{
$model = $this->getTableLocator()->get('Invitations');
$command = new ModelCommand();
$command->connection = 'test';
$result = $command->findBelongsTo($model, []);
$expected = [
'belongsTo' => [
Expand Down Expand Up @@ -519,6 +610,7 @@ public function testBelongsToGenerationCompositeKey()
{
$model = $this->getTableLocator()->get('TodoItemsTodoLabels');
$command = new ModelCommand();
$command->connection = 'test';
$result = $command->findBelongsTo($model, []);
$expected = [
'belongsTo' => [
Expand Down Expand Up @@ -1673,9 +1765,9 @@ public function testBakeEntityWithPlugin()
$this->_loadTestPlugin('BakeTest');
$path = Plugin::path('BakeTest');

$this->generatedFile = $path . 'src/Model/Table/UsersTable.php';
$this->generatedFile = $path . 'src/Model/Entity/User.php';

$this->exec('bake model --no-validation --no-test --no-fixture --no-entity -p BakeTest Users');
$this->exec('bake model --no-validation --no-test --no-fixture --no-table BakeTest.Users');

$this->assertExitCode(CommandInterface::CODE_SUCCESS);
$this->assertFileExists($this->generatedFile);
Expand Down
86 changes: 30 additions & 56 deletions tests/comparisons/Model/testBakeEntityWithPlugin.php
@@ -1,74 +1,48 @@
<?php
declare(strict_types=1);

namespace BakeTest\Model\Table;
namespace BakeTest\Model\Entity;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\Entity;

/**
* Users Model
* User Entity
*
* @property \BakeTest\Model\Table\CommentsTable&\Cake\ORM\Association\HasMany $Comments
* @property \BakeTest\Model\Table\TodoItemsTable&\Cake\ORM\Association\HasMany $TodoItems
* @property int $id
* @property string|null $username
* @property string|null $password
* @property \Cake\I18n\FrozenTime|null $created
* @property \Cake\I18n\FrozenTime|null $updated
*
* @method \BakeTest\Model\Entity\User newEmptyEntity()
* @method \BakeTest\Model\Entity\User newEntity(array $data, array $options = [])
* @method \BakeTest\Model\Entity\User[] newEntities(array $data, array $options = [])
* @method \BakeTest\Model\Entity\User get($primaryKey, $options = [])
* @method \BakeTest\Model\Entity\User findOrCreate($search, ?callable $callback = null, $options = [])
* @method \BakeTest\Model\Entity\User patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
* @method \BakeTest\Model\Entity\User[] patchEntities(iterable $entities, array $data, array $options = [])
* @method \BakeTest\Model\Entity\User|false save(\Cake\Datasource\EntityInterface $entity, $options = [])
* @method \BakeTest\Model\Entity\User saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
* @method \BakeTest\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface|false saveMany(iterable $entities, $options = [])
* @method \BakeTest\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface saveManyOrFail(iterable $entities, $options = [])
* @method \BakeTest\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface|false deleteMany(iterable $entities, $options = [])
* @method \BakeTest\Model\Entity\User[]|\Cake\Datasource\ResultSetInterface deleteManyOrFail(iterable $entities, $options = [])
*
* @mixin \Cake\ORM\Behavior\TimestampBehavior
* @property \BakeTest\Model\Entity\Comment[] $comments
* @property \BakeTest\Model\Entity\TodoItem[] $todo_items
*/
class UsersTable extends Table
class User extends Entity
{
/**
* Initialize method
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* @param array $config The configuration for the Table.
* @return void
* @var array<string, bool>
*/
public function initialize(array $config): void
{
parent::initialize($config);

$this->setTable('users');
$this->setDisplayField('id');
$this->setPrimaryKey('id');

$this->addBehavior('Timestamp');

$this->hasMany('Comments', [
'foreignKey' => 'user_id',
'className' => 'BakeTest.Comments',
]);
$this->hasMany('TodoItems', [
'foreignKey' => 'user_id',
'className' => 'BakeTest.TodoItems',
]);
}
protected $_accessible = [
'username' => true,
'password' => true,
'created' => true,
'updated' => true,
'comments' => true,
'todo_items' => true,
];

/**
* Returns a rules checker object that will be used for validating
* application integrity.
* Fields that are excluded from JSON versions of the entity.
*
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* @return \Cake\ORM\RulesChecker
* @var array<string>
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['username']), ['errorField' => 'username']);

return $rules;
}
protected $_hidden = [
'password',
];
}

0 comments on commit 808608b

Please sign in to comment.