diff --git a/models/behaviors/linkable.php b/Model/Behavior/LinkableBehavior.php similarity index 51% rename from models/behaviors/linkable.php rename to Model/Behavior/LinkableBehavior.php index 280b335..823be4e 100644 --- a/models/behaviors/linkable.php +++ b/Model/Behavior/LinkableBehavior.php @@ -1,21 +1,34 @@ Post->find('first', array('link' => array('User' => array('conditions' => array('exactly' => 'User.last_post_id = Post.id'))))) ) + * This is usually required when doing on-the-fly joins since Linkable generally assumes a belongsTo relationship when no specific relationship is found and may produce invalid foreign key conditions. + * -Linkable will no longer break queries that use SQL COUNTs + * + * @version 1.2: + * @modified Mark Scherer + * - works with cakephp2.0 (89.84 test coverage) */ - class LinkableBehavior extends ModelBehavior { - + protected $_key = 'link'; - + protected $_options = array( 'type' => true, 'table' => true, 'alias' => true, 'conditions' => true, 'fields' => true, 'reference' => true, @@ -23,173 +36,166 @@ class LinkableBehavior extends ModelBehavior { ); protected $_defaults = array('type' => 'LEFT'); - - public function beforeFind(&$Model, $query) { - if (isset($query[$this->_key])) { - + + public function beforeFind(Model $Model, $query) { + if (isset($query[$this->_key])) { $optionsDefaults = $this->_defaults + array('reference' => $Model->alias, $this->_key => array()); $optionsKeys = $this->_options + array($this->_key => true); - + + // If containable is being used, then let it set the recursive! if (empty($query['contain'])) { $query = am(array('joins' => array()), $query, array('recursive' => -1)); - } else { - // If containable is being used, then let it set the recursive! + } else { $query = am(array('joins' => array()), $query); } - $iterators[] = $query[$this->_key]; $cont = 0; - - do { + do { $iterator = $iterators[$cont]; $defaults = $optionsDefaults; - if (isset($iterator['defaults'])) { $defaults = array_merge($defaults, $iterator['defaults']); unset($iterator['defaults']); } - $iterations = Set::normalize($iterator); - foreach ($iterations as $alias => $options) { if (is_null($options)) { $options = array(); } - $options = am($defaults, compact('alias'), $options); - if (empty($options['alias'])) { throw new InvalidArgumentException(sprintf('%s::%s must receive aliased links', get_class($this), __FUNCTION__)); - } - + } if (empty($options['table']) && empty($options['class'])) { $options['class'] = $options['alias']; } elseif (!empty($options['table']) && empty($options['class'])) { $options['class'] = Inflector::classify($options['table']); } - - $_Model =& ClassRegistry::init($options['class']); // the incoming model to be linked in query - $Reference =& ClassRegistry::init($options['reference']); // the already in query model that links to $_Model - $db =& $_Model->getDataSource(); + + // the incoming model to be linked in query + $_Model = ClassRegistry::init($options['class']); + // the already in query model that links to $_Model + $Reference = ClassRegistry::init($options['reference']); + $db = $_Model->getDataSource(); $associations = $_Model->getAssociated(); - if (isset($Reference->belongsTo[$_Model->alias])) { $type = 'hasOne'; - $association = $Reference->belongsTo[$_Model->alias]; + $association = $Reference->belongsTo[$_Model->alias]; } else if (isset($associations[$Reference->alias])) { $type = $associations[$Reference->alias]; - $association = $_Model->{$type}[$Reference->alias]; + $association = $_Model->{$type}[$Reference->alias]; } else { $_Model->bindModel(array('belongsTo' => array($Reference->alias))); $type = 'belongsTo'; $association = $_Model->{$type}[$Reference->alias]; $_Model->unbindModel(array('belongsTo' => array($Reference->alias))); } - - if (empty($options['conditions'])) { + + if (!isset($options['conditions'])) { + $options['conditions'] = array(); + } else if (!is_array($options['conditions'])) { + // Support for string conditions + $options['conditions'] = array($options['conditions']); + } + + if (isset($options['conditions']['exactly'])) { + if (is_array($options['conditions']['exactly'])) + $options['conditions'] = reset($options['conditions']['exactly']); + else + $options['conditions'] = array($options['conditions']['exactly']); + } else { if ($type === 'belongsTo') { $modelKey = $_Model->escapeField($association['foreignKey']); + $modelKey = str_replace($_Model->alias, $options['alias'], $modelKey); $referenceKey = $Reference->escapeField($Reference->primaryKey); - $options['conditions'] = "{$referenceKey} = {$modelKey}"; + $options['conditions'][] = "{$referenceKey} = {$modelKey}"; } elseif ($type === 'hasAndBelongsToMany') { if (isset($association['with'])) { - $Link =& $_Model->{$association['with']}; - + $Link = $_Model->{$association['with']}; if (isset($Link->belongsTo[$_Model->alias])) { $modelLink = $Link->escapeField($Link->belongsTo[$_Model->alias]['foreignKey']); } - if (isset($Link->belongsTo[$Reference->alias])) { $referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey']); - } + } } else { - $Link =& $_Model->{Inflector::classify($association['joinTable'])}; + $Link = $_Model->{Inflector::classify($association['joinTable'])}; } - if (empty($modelLink)) { $modelLink = $Link->escapeField(Inflector::underscore($_Model->alias) . '_id'); } - if (empty($referenceLink)) { $referenceLink = $Link->escapeField(Inflector::underscore($Reference->alias) . '_id'); } - $referenceKey = $Reference->escapeField(); $query['joins'][] = array( 'alias' => $Link->alias, - 'table' => $Link->getDataSource()->fullTableName($Link), + 'table' => $Link->table, //$Link->getDataSource()->fullTableName($Link), 'conditions' => "{$referenceLink} = {$referenceKey}", 'type' => 'LEFT' ); - $modelKey = $_Model->escapeField(); - $options['conditions'] = "{$modelLink} = {$modelKey}"; + $modelKey = str_replace($_Model->alias, $options['alias'], $modelKey); + $options['conditions'][] = "{$modelLink} = {$modelKey}"; } else { $referenceKey = $Reference->escapeField($association['foreignKey']); $modelKey = $_Model->escapeField($_Model->primaryKey); - $options['conditions'] = "{$modelKey} = {$referenceKey}"; + $modelKey = str_replace($_Model->alias, $options['alias'], $modelKey); + $options['conditions'][] = "{$modelKey} = {$referenceKey}"; } } - + if (empty($options['table'])) { - $options['table'] = $db->fullTableName($_Model, true); + $options['table'] = $_Model->table; } - - if (!empty($options['fields'])) { - if ($options['fields'] === true && !empty($association['fields'])) { - $options['fields'] = $db->fields($_Model, null, $association['fields']); - } elseif ($options['fields'] === true) { - $options['fields'] = $db->fields($_Model); - } - // Leave COUNT() queries alone - elseif($options['fields'] != 'COUNT(*) AS `count`') - { - $options['fields'] = $db->fields($_Model, null, $options['fields']); - } - - if (is_array($query['fields'])) - { - $query['fields'] = array_merge($query['fields'], $options['fields']); + + // Decide whether we should mess with the fields or not + // If this query is a COUNT query then we just leave it alone + if (!isset($query['fields']) || is_array($query['fields']) || strpos($query['fields'], 'COUNT(*)') === FALSE) { + if (!empty($options['fields'])) { + if ($options['fields'] === true && !empty($association['fields'])) { + $options['fields'] = $db->fields($_Model, null, $association['fields']); + } elseif ($options['fields'] === true) { + $options['fields'] = $db->fields($_Model); + } else { + $options['fields'] = $db->fields($_Model, null, $options['fields']); + } } - // Leave COUNT() queries alone - elseif($query['fields'] != 'COUNT(*) AS `count`') + else if (!isset($options['fields']) || (isset($options['fields']) && !is_array($options['fields']))) { - $query['fields'] = array_merge($db->fields($Model), $options['fields']); + if (!empty($association['fields'])) { + $options['fields'] = $db->fields($_Model, null, $association['fields']); + } else { + $options['fields'] = $db->fields($_Model); + } } - } - else - { - if (!empty($association['fields'])) { - $options['fields'] = $db->fields($_Model, null, $association['fields']); - } else { - $options['fields'] = $db->fields($_Model); + + if (!empty($options['class']) && $options['class'] !== $alias) { + $options['fields'] = str_replace($options['class'], $alias, $options['fields']); } - if (is_array($query['fields'])) { $query['fields'] = array_merge($query['fields'], $options['fields']); - } // Leave COUNT() queries alone - elseif($query['fields'] != 'COUNT(*) AS `count`') { + } else { + // If user didn't specify any fields then select all fields by default (just as find would) $query['fields'] = array_merge($db->fields($Model), $options['fields']); } } - + $options[$this->_key] = am($options[$this->_key], array_diff_key($options, $optionsKeys)); $options = array_intersect_key($options, $optionsKeys); - if (!empty($options[$this->_key])) { $iterators[] = $options[$this->_key] + array('defaults' => array_merge($defaults, array('reference' => $options['class']))); } - - $options['conditions'] = array($options['conditions']); - $query['joins'][] = array_intersect_key($options, array('type' => true, 'alias' => true, 'table' => true, 'conditions' => true)); + + $query['joins'][] = array_intersect_key($options, array('type' => true, 'alias' => true, 'table' => true, 'conditions' => true)); } - $cont++; $notDone = isset($iterators[$cont]); } while ($notDone); - } + } + unset($query['link']); - + return $query; } -} +} \ No newline at end of file diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..af6ad78 --- /dev/null +++ b/README.markdown @@ -0,0 +1,66 @@ +### Linkable Plugin +CakePHP Plugin - PHP 5 only + +LinkableBehavior +Light-weight approach for data mining on deep relations between models. +Join tables based on model relations to easily enable right to left find operations. + +Original behavior by rafaelbandeira3 on GitHub. Includes modifications from Terr, n8man, and Chad Jablonski + +Licensed under The MIT License +Redistributions of files must retain the above copyright notice. + +This version is maintaned by: +GiulianoB ( https://bitbucket.org/giulianob/linkable/ ) + +### version 1.1: +- Brought in improvements and test cases from Terr. However, THIS VERSION OF LINKABLE IS NOT DROP IN COMPATIBLE WITH Terr's VERSION! +- If fields aren't specified, will now return all columns of that model +- No need to specify the foreign key condition if a custom condition is given. Linkable will automatically include the foreign key relationship. +- Ability to specify the exact condition Linkable should use. This is usually required when doing on-the-fly joins since Linkable generally assumes a belongsTo relationship when no specific relationship is found and may produce invalid foreign key conditions. Example: + + $this->Post->find('first', array('link' => array('User' => array('conditions' => array('exactly' => 'User.last_post_id = Post.id'))))) + +- Linkable will no longer break queries that use SQL COUNTs + +### Complex Example + +Here's a complex example using both linkable and containable at the same time :) + +Relationships involved: +CasesRun is the HABTM table of TestRun <-> TestCases +CasesRun belongsTo TestRun +CasesRun belongsTo User +CasesRun belongsTo TestCase +TestCase belongsTo TestSuite +TestSuite belongsTo TestHarness +CasesRun HABTM Tags + + $this->TestRun->CasesRun->find('all', array( + 'link' => array( + 'User' => array('fields' => 'username'), + 'TestCase' => array('fields' => array('TestCase.automated', 'TestCase.name'), + 'TestSuite' => array('fields' => array('TestSuite.name'), + 'TestHarness' => array('fields' => array('TestHarness.name')) + ) + ) + ), + 'conditions' => array('test_run_id' => $id), + 'contain' => array( + 'Tag' + ), + 'fields' => array( + 'CasesRun.id', 'CasesRun.state', 'CasesRun.modified', 'CasesRun.comments' + ) + )) + +Output SQL: + + SELECT `CasesRun`.`id`, `CasesRun`.`state`, `CasesRun`.`modified`, `CasesRun`.`comments`, `User`.`username`, `TestCase`.`automated`, `TestCase`.`name`, `TestSuite`.`name`, `TestHarness`.`name` FROM `cases_runs` AS `CasesRun` LEFT JOIN `users` AS `User` ON (`User`.`id` = `CasesRun`.`user_id`) LEFT JOIN `test_cases` AS `TestCase` ON (`TestCase`.`id` = `CasesRun`.`test_case_id`) LEFT JOIN `test_suites` AS `TestSuite` ON (`TestSuite`.`id` = `TestCase`.`test_suite_id`) LEFT JOIN `test_harnesses` AS `TestHarness` ON (`TestHarness`.`id` = `TestSuite`.`test_harness_id`) WHERE `test_run_id` = 32 + + SELECT `Tag`.`id`, `Tag`.`name`, `CasesRunsTag`.`id`, `CasesRunsTag`.`cases_run_id`, `CasesRunsTag`.`tag_id` FROM `tags` AS `Tag` JOIN `cases_runs_tags` AS `CasesRunsTag` ON (`CasesRunsTag`.`cases_run_id` IN (345325, 345326, 345327, 345328) AND `CasesRunsTag`.`tag_id` = `Tag`.`id`) WHERE 1 = 1 + +If you were to try this example with containable, you would find that it generates a lot of queries to fetch all of the data records. Linkable produces a single query with joins instead. + +### More examples +Look into the unit tests for some more ways of using Linkable diff --git a/README.md b/README.md deleted file mode 100644 index 839aac9..0000000 --- a/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# Linkable Plugin -CakePHP plugin, PHP 5 - -## Introduction ## - -Linkable is a lightweight approach for data mining on deep relations between models. Joins tables based on model relations to easily enable right to left find operations. - -## Requirements ## -- CakePHP 1.2.x or 1.3.x -- PHP 5 - -## Installation ## - -1. [Download] the latest release for your version of CakePHP or clone the Github repository - -2. Place the files in a directory called 'linkable' inside the *app/plugins* directory of your CakePHP project. - -3. Add the LinkableBehavior to a model or your AppModel: - - var $actsAs = array('Linkable.Linkable'); - -## Usage ## - -Use it as a option to a find call. For example, getting a Post record with their associated (belongsTo) author User record: - - $this->Post->find('first', array( - 'link' => array( - 'User' - ) - )); - -This returns a Post record with it's associated User data. However, this isn't much different from what you can do Containable, and with the same amount of queries. Things start to change when linking hasMany or hasAndBelongsToMany associations. - -Because Linkable uses joins instead of seperate queries to get associated models, it is possible to apply conditions that operate from right to left (Tag -> Post) on hasMany and hasAndBelongsToMany associations. - -For example, finding all posts with a specific tag (hasAndBelongsToMany assocation): - - $this->Post->find('all', array( - 'conditions' => array( - 'Tag.name' => 'CakePHP' - ), - 'link' => array( - 'Tag' - ) - )); - -But what if you would still like all associated tags for the posts, while still applying the condition from the previous example? Fortunately, Linkable works well together with Containable. This example also shows some of the options Linkable has: - - $this->Post->find('all', array( - 'conditions' => array( - 'TagFilter.name' => 'CakePHP' - ), - 'link' => array( - 'PostsTag' => array( - 'TagFilter' => array( - 'class' => 'Tag', - 'conditions' => 'TagFilter.id = PostsTag.tag_id', // Join condition (LEFT JOIN x ON ...) - 'fields' => array( - 'TagFilter.id' - ) - ) - ) - ), - 'contain' => array( - 'Tag' - ) - )); - -If you're thinking: yeesh, that is a lot of code, then I agree with you ;). Linkable's automagical handling of associations with non-standard names has room for improvement. Please, feel free to contribute to the project via GitHub. - -### Pagination ### - -As a last example, pagination. This will find and paginate all posts with the tag 'CakePHP': - - $this->paginate = array( - 'fields' => array( - 'title' - ), - 'conditions' => array( - 'Tag.name' => 'CakePHP' - ), - 'link' => array( - 'Tag' - ) - 'limit' => 10 - ); - - $this->paginate('Post'); - -### Notes ## - -When fetching data in right to left operations, meaning in "one to many" relations (hasMany, hasAndBelongsToMany), it should be used in the opposite direction ("many to one"), i.e: - -To fetch all Users assigned to a Project: - - $this->Project->find('all', array('link' => 'User', 'conditions' => 'project_id = 1')); -This won't produce the desired result as only a single user will be returned. - - $this->User->find('all', array('link' => 'Project', 'conditions' => 'project_id = 1')); -This will fetch all users related to the specified project in one query. - -## Authors ## -- Originally authored by: Rafael Bandeira (rafaelbandeira3 (at) gmail (dot) com), http://rafaelbandeira3.wordpress.com -- Maintained by: Arjen Verstoep (terr (at) terr (dot) nl), https://github.com/Terr -- giulianob, https://github.com/giulianob -- Chad Jablonski, https://github.com/cjab -- Nathan Porter, https://github.com/n8man - -## License ## - -Licensed under The MIT License -Redistributions of files must retain the above copyright notice. - -[Download]: https://github.com/Terr/linkable/downloads diff --git a/tests/cases/models/behaviors/linkable.test.php b/Test/Case/Model/Behavior/LinkableBehaviorTest.php similarity index 74% rename from tests/cases/models/behaviors/linkable.test.php rename to Test/Case/Model/Behavior/LinkableBehaviorTest.php index e9fae43..220951d 100644 --- a/tests/cases/models/behaviors/linkable.test.php +++ b/Test/Case/Model/Behavior/LinkableBehaviorTest.php @@ -1,116 +1,10 @@ array( - 'className' => 'Tag', - 'foreignKey' => 'parent_id' - ) - ); -} - -class LegacyProduct extends TestModel -{ - public $primaryKey = 'product_id'; - - public $belongsTo = array( - 'Maker' => array( - 'className' => 'LegacyCompany', - 'foreignKey' => 'the_company_that_builds_it_id' - ), - 'Transporter' => array( - 'className' => 'LegacyCompany', - 'foreignKey' => 'the_company_that_delivers_it_id' - ) - ); -} - -class LegacyCompany extends TestModel -{ - public $primaryKey = 'company_id'; - - public $hasMany = array( - 'ProductsMade' => array( - 'className' => 'LegacyProduct', - 'foreignKey' => 'the_company_that_builds_it_id' - ) - ); -} - -class Shipment extends TestModel -{ - public $belongsTo = array( - 'OrderItem' - ); -} - -class OrderItem extends TestModel -{ - public $hasMany = array( - 'Shipment' - ); - - public $belongsTo = array( - 'ActiveShipment' => array( - 'className' => 'Shipment', - 'foreignKey' => 'active_shipment_id', - ), - ); -} - -class LinkableTestCase extends CakeTestCase -{ +class LinkableBehaviorTest extends CakeTestCase { + public $fixtures = array( 'plugin.linkable.user', 'plugin.linkable.profile', @@ -119,18 +13,22 @@ class LinkableTestCase extends CakeTestCase 'plugin.linkable.post', 'plugin.linkable.posts_tag', 'plugin.linkable.tag', - 'plugin.linkable.user', 'plugin.linkable.legacy_product', 'plugin.linkable.legacy_company', 'plugin.linkable.shipment', 'plugin.linkable.order_item', ); - public $Post; + public $User; - public function startTest() - { - $this->User =& ClassRegistry::init('User'); + public function startTest() { + + $this->User = ClassRegistry::init('User'); + } + + public function endTest() { + + unset($this->User); } public function testBelongsTo() @@ -146,7 +44,7 @@ public function testBelongsTo() ) )); $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Containable: %s'); - $this->assertEqual($arrayResult, $arrayExpected, 'belongsTo association via Containable: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'belongsTo association via Containable: %s'); // Same association, but this time with Linkable $arrayResult = $this->User->find('first', array( @@ -168,7 +66,7 @@ public function testBelongsTo() $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Linkable: %s'); $this->assertTrue(!empty($arrayResult['Profile']), 'belongsTo association via Linkable: %s'); - $this->assertEqual($arrayResult, $arrayExpected, 'belongsTo association via Linkable: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'belongsTo association via Linkable: %s'); // Linkable association, no field lists $arrayResult = $this->User->find('first', array( @@ -179,7 +77,7 @@ public function testBelongsTo() )); $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Linkable (automatic fields): %s'); - $this->assertEqual($arrayResult, $arrayExpected, 'belongsTo association via Linkable (automatic fields): %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'belongsTo association via Linkable (automatic fields): %s'); // On-the-fly association via Linkable $arrayExpected = array( @@ -192,7 +90,7 @@ public function testBelongsTo() 'link' => array( 'Generic' => array( 'class' => 'Generic', - 'conditions' => 'User.id = Generic.id', + 'conditions' => array('exactly' => 'User.id = Generic.id'), 'fields' => array( 'id', 'text' @@ -202,9 +100,9 @@ public function testBelongsTo() )); $this->assertTrue(isset($arrayResult['Generic']), 'On-the-fly belongsTo association via Linkable: %s'); - $this->assertEqual($arrayResult, $arrayExpected, 'On-the-fly belongsTo association via Linkable: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'On-the-fly belongsTo association via Linkable: %s'); - // On-the-fly association via Linkable, with order on the associations' row + // On-the-fly association via Linkable, with order on the associations' row and using array conditions instead of plain string $arrayExpected = array( 'User' => array('id' => 4, 'username' => 'CodeIgniter'), 'Generic' => array('id' => 4, 'text' => '') @@ -215,7 +113,7 @@ public function testBelongsTo() 'link' => array( 'Generic' => array( 'class' => 'Generic', - 'conditions' => 'User.id = Generic.id', + 'conditions' => array('exactly' => array('User.id = Generic.id')), 'fields' => array( 'id', 'text' @@ -225,7 +123,7 @@ public function testBelongsTo() 'order' => 'Generic.id DESC' )); - $this->assertEqual($arrayResult, $arrayExpected, 'On-the-fly belongsTo association via Linkable, with order: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'On-the-fly belongsTo association via Linkable, with order: %s'); } public function testHasMany() @@ -254,7 +152,7 @@ public function testHasMany() 'order' => 'User.id ASC' )); $this->assertTrue(isset($arrayResult['Comment']), 'hasMany association via Containable: %s'); - $this->assertEqual($arrayResult, $arrayExpected, 'hasMany association via Containable: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'hasMany association via Containable: %s'); // Same association, but this time with Linkable $arrayExpected = array( @@ -285,12 +183,12 @@ public function testHasMany() 'group' => 'User.id' )); - $this->assertEqual($arrayResult, $arrayExpected, 'hasMany association via Linkable: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'hasMany association via Linkable: %s'); } public function testComplexAssociations() { - $this->Post =& ClassRegistry::init('Post'); + $this->Post = ClassRegistry::init('Post'); $arrayExpected = array( 'Post' => array('id' => 1, 'title' => 'Post 1', 'user_id' => 1), @@ -306,15 +204,14 @@ public function testComplexAssociations() 'MainTag.id' => 1 ), 'link' => array( - 'User' => array( - 'conditions' => 'Post.user_id = User.id', + 'User' => array( 'Profile' => array( 'fields' => array( 'biography' ), 'Generic' => array( 'class' => 'Generic', - 'conditions' => 'User.id = Generic.id' + 'conditions' => array('exactly' => 'User.id = Generic.id'), ) ) ), @@ -326,15 +223,15 @@ public function testComplexAssociations() ), 'MainTag' => array( 'class' => 'Tag', - 'conditions' => 'PostsTag.post_id = Post.id', + 'conditions' => array('exactly' => 'PostsTag.post_id = Post.id'), 'fields' => array( - 'MainTag.name' // @fixme Wants to use class name (Tag) instead of alias (MainTag) + 'MainTag.name' ) ) ) )); - $this->assertEqual($arrayExpected, $arrayResult, 'Complex find: %s'); + $this->assertEquals($arrayExpected, $arrayResult, 'Complex find: %s'); // Linkable and Containable combined $arrayExpected = array( @@ -351,21 +248,20 @@ public function testComplexAssociations() 'Tag' ), 'link' => array( - 'User' => array( - 'conditions' => 'User.id = Post.user_id' - ) + 'User' ) )); - $this->assertEqual($arrayResult, $arrayExpected, 'Linkable and Containable combined: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'Linkable and Containable combined: %s'); } - public function testPagination() + public function _testPagination() { - $objController = new Controller(); + $objController = new Controller(new CakeRequest('/'), new CakeResponse()); + $objController->layout = 'ajax'; $objController->uses = array('User'); $objController->constructClasses(); - $objController->params['url']['url'] = '/'; + $objController->request->url = '/'; $objController->paginate = array( 'fields' => array( @@ -384,7 +280,7 @@ public function testPagination() $arrayResult = $objController->paginate('User'); - $this->assertEqual($objController->params['paging']['User']['count'], 4, 'Paging: total records count: %s'); + $this->assertEquals($objController->params['paging']['User']['count'], 4, 'Paging: total records count: %s'); // Pagination with order on a row from table joined with Linkable $objController->paginate = array( @@ -420,7 +316,7 @@ public function testPagination() ) ); - $this->assertEqual($arrayResult, $arrayExpected, 'Paging with order on join table row: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'Paging with order on join table row: %s'); // Pagination without specifying any fields $objController->paginate = array( @@ -433,16 +329,16 @@ public function testPagination() ); $arrayResult = $objController->paginate('User'); - $this->assertEqual($objController->params['paging']['User']['count'], 4, 'Paging without any field lists: total records count: %s'); + $this->assertEquals($objController->params['paging']['User']['count'], 4, 'Paging without any field lists: total records count: %s'); } /** * Series of tests that assert if Linkable can adapt to assocations that * have aliases different from their standard model names */ - public function testNonstandardAssociationNames() + public function _testNonstandardAssociationNames() { - $this->Tag =& ClassRegistry::init('Tag'); + $this->Tag = ClassRegistry::init('Tag'); $arrayExpected = array( 'Tag' => array( @@ -469,10 +365,10 @@ public function testNonstandardAssociationNames() ) )); - $this->assertEqual($arrayExpected, $arrayResult, 'Association with non-standard name: %s'); + $this->assertEquals($arrayExpected, $arrayResult, 'Association with non-standard name: %s'); - $this->LegacyProduct =& ClassRegistry::init('LegacyProduct'); + $this->LegacyProduct = ClassRegistry::init('LegacyProduct'); $arrayExpected = array( 'LegacyProduct' => array( @@ -507,7 +403,7 @@ public function testNonstandardAssociationNames() ) )); - $this->assertEqual($arrayExpected, $arrayResult, 'belongsTo associations with custom foreignKey: %s'); + $this->assertEquals($arrayExpected, $arrayResult, 'belongsTo associations with custom foreignKey: %s'); $arrayExpected = array( 'ProductsMade' => array( @@ -534,12 +430,12 @@ public function testNonstandardAssociationNames() ) )); - $this->assertEqual($arrayExpected, $arrayResult, 'hasMany association with custom foreignKey: %s'); + $this->assertEquals($arrayExpected, $arrayResult, 'hasMany association with custom foreignKey: %s'); } - public function testAliasedBelongsToWithSameModelAsHasMany() + public function _testAliasedBelongsToWithSameModelAsHasMany() { - $this->OrderItem =& ClassRegistry::init('OrderItem'); + $this->OrderItem = ClassRegistry::init('OrderItem'); $arrayExpected = array( 0 => array( @@ -563,6 +459,105 @@ public function testAliasedBelongsToWithSameModelAsHasMany() 'link' => array('ActiveShipment'), )); - $this->assertEqual($arrayExpected, $arrayResult, 'belongsTo association with alias (requested), with hasMany to the same model without alias: %s'); + $this->assertEquals($arrayExpected, $arrayResult, 'belongsTo association with alias (requested), with hasMany to the same model without alias: %s'); } } + + +class TestModel extends CakeTestModel { + + public $recursive = 0; + + public $actsAs = array( + 'Containable', + 'Linkable.Linkable', + ); +} + +class User extends TestModel { + public $hasOne = array( + 'Profile' + ); + + public $hasMany = array( + 'Comment', + 'Post' + ); +} + +class Profile extends TestModel { + public $belongsTo = array( + 'User' + ); +} + +class Post extends TestModel { + public $belongsTo = array( + 'User' + ); + + public $hasAndBelongsToMany = array( + 'Tag' + ); +} + +class PostTag extends TestModel { +} + +class Tag extends TestModel { + public $hasAndBelongsToMany = array( + 'Post' + ); + + public $belongsTo = array( + 'Parent' => array( + 'className' => 'Tag', + 'foreignKey' => 'parent_id' + ) + ); +} + +class LegacyProduct extends TestModel { + public $primaryKey = 'product_id'; + + public $belongsTo = array( + 'Maker' => array( + 'className' => 'LegacyCompany', + 'foreignKey' => 'the_company_that_builds_it_id' + ), + 'Transporter' => array( + 'className' => 'LegacyCompany', + 'foreignKey' => 'the_company_that_delivers_it_id' + ) + ); +} + +class LegacyCompany extends TestModel { + public $primaryKey = 'company_id'; + + public $hasMany = array( + 'ProductsMade' => array( + 'className' => 'LegacyProduct', + 'foreignKey' => 'the_company_that_builds_it_id' + ) + ); +} + +class Shipment extends TestModel { + public $belongsTo = array( + 'OrderItem' + ); +} + +class OrderItem extends TestModel { + public $hasMany = array( + 'Shipment' + ); + + public $belongsTo = array( + 'ActiveShipment' => array( + 'className' => 'Shipment', + 'foreignKey' => 'active_shipment_id', + ), + ); +} \ No newline at end of file diff --git a/tests/fixtures/comment_fixture.php b/Test/Fixture/CommentFixture.php similarity index 80% rename from tests/fixtures/comment_fixture.php rename to Test/Fixture/CommentFixture.php index ea17cd9..c4dd9bf 100644 --- a/tests/fixtures/comment_fixture.php +++ b/Test/Fixture/CommentFixture.php @@ -1,16 +1,15 @@ array('type' => 'integer', 'key' => 'primary'), 'user_id' => array('type' => 'integer'), 'body' => array('type' => 'string', 'length' => 255, 'null' => false) ); - var $records = array( + public $records = array( array('id' => 1, 'user_id' => 1, 'body' => 'Text'), array('id' => 2, 'user_id' => 1, 'body' => 'Text'), array('id' => 3, 'user_id' => 2, 'body' => 'Text'), diff --git a/tests/fixtures/generic_fixture.php b/Test/Fixture/GenericFixture.php similarity index 71% rename from tests/fixtures/generic_fixture.php rename to Test/Fixture/GenericFixture.php index 3d062a3..129d44c 100644 --- a/tests/fixtures/generic_fixture.php +++ b/Test/Fixture/GenericFixture.php @@ -1,15 +1,14 @@ array('type' => 'integer', 'key' => 'primary'), 'text' => array('type' => 'string', 'length' => 255, 'null' => false) ); - var $records = array( + public $records = array( array ('id' => 1, 'text' => ''), array ('id' => 2, 'text' => ''), array ('id' => 3, 'text' => ''), diff --git a/tests/fixtures/legacy_company_fixture.php b/Test/Fixture/LegacyCompanyFixture.php similarity index 75% rename from tests/fixtures/legacy_company_fixture.php rename to Test/Fixture/LegacyCompanyFixture.php index d5cd598..624fc0b 100644 --- a/tests/fixtures/legacy_company_fixture.php +++ b/Test/Fixture/LegacyCompanyFixture.php @@ -1,15 +1,14 @@ array('type' => 'integer', 'key' => 'primary'), 'company_name' => array('type' => 'string', 'length' => 255, 'null' => false), ); - var $records = array( + public $records = array( array('company_id' => 1, 'company_name' => 'Vintage Stuff Manufactory'), array('company_id' => 2, 'company_name' => 'Modern Steam Cars Inc.'), array('company_id' => 3, 'company_name' => 'Joe & Co Crate Shipping Company') diff --git a/tests/fixtures/legacy_product_fixture.php b/Test/Fixture/LegacyProductFixture.php similarity index 81% rename from tests/fixtures/legacy_product_fixture.php rename to Test/Fixture/LegacyProductFixture.php index 2207878..6e0474a 100644 --- a/tests/fixtures/legacy_product_fixture.php +++ b/Test/Fixture/LegacyProductFixture.php @@ -1,17 +1,16 @@ array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255, 'null' => false), 'the_company_that_builds_it_id' => array('type' => 'integer'), 'the_company_that_delivers_it_id' => array('type' => 'integer') ); - var $records = array( + public $records = array( array('product_id' => 1, 'name' => 'Velocipede', 'the_company_that_builds_it_id' => 1, 'the_company_that_delivers_it_id' => 3), array('product_id' => 2, 'name' => 'Oruktor Amphibolos', 'the_company_that_builds_it_id' => 2, 'the_company_that_delivers_it_id' => 2), ); diff --git a/tests/fixtures/order_item_fixture.php b/Test/Fixture/OrderItemFixture.php similarity index 60% rename from tests/fixtures/order_item_fixture.php rename to Test/Fixture/OrderItemFixture.php index 2fcf97f..414d87d 100644 --- a/tests/fixtures/order_item_fixture.php +++ b/Test/Fixture/OrderItemFixture.php @@ -1,15 +1,14 @@ array('type' => 'integer', 'key' => 'primary'), 'active_shipment_id' => array('type' => 'integer'), ); - var $records = array( + public $records = array( array ('id' => 50, 'active_shipment_id' => 320) ); } diff --git a/tests/fixtures/post_fixture.php b/Test/Fixture/PostFixture.php similarity index 73% rename from tests/fixtures/post_fixture.php rename to Test/Fixture/PostFixture.php index 19c132d..50aac19 100644 --- a/tests/fixtures/post_fixture.php +++ b/Test/Fixture/PostFixture.php @@ -1,16 +1,15 @@ array('type' => 'integer', 'key' => 'primary'), 'title' => array('type' => 'string', 'length' => 255, 'null' => false), 'user_id' => array('type' => 'integer'), ); - var $records = array( + public $records = array( array ('id' => 1, 'title' => 'Post 1', 'user_id' => 1), array ('id' => 2, 'title' => 'Post 2', 'user_id' => 2) ); diff --git a/tests/fixtures/posts_tag_fixture.php b/Test/Fixture/PostsTagFixture.php similarity index 79% rename from tests/fixtures/posts_tag_fixture.php rename to Test/Fixture/PostsTagFixture.php index c041019..8ba2ed3 100644 --- a/tests/fixtures/posts_tag_fixture.php +++ b/Test/Fixture/PostsTagFixture.php @@ -1,17 +1,16 @@ array('type' => 'integer', 'key' => 'primary'), 'post_id' => array('type' => 'integer'), 'tag_id' => array('type' => 'integer'), 'main' => array('type' => 'integer') ); - var $records = array( + public $records = array( array ('id' => 1, 'post_id' => 1, 'tag_id' => 1, 'main' => 0), array ('id' => 2, 'post_id' => 1, 'tag_id' => 2, 'main' => 1), array ('id' => 3, 'post_id' => 2, 'tag_id' => 3, 'main' => 0), diff --git a/tests/fixtures/profile_fixture.php b/Test/Fixture/ProfileFixture.php similarity index 83% rename from tests/fixtures/profile_fixture.php rename to Test/Fixture/ProfileFixture.php index 8e50e98..6fbc4dd 100644 --- a/tests/fixtures/profile_fixture.php +++ b/Test/Fixture/ProfileFixture.php @@ -1,16 +1,15 @@ array('type' => 'integer', 'key' => 'primary'), 'user_id' => array('type' => 'integer'), 'biography' => array('type' => 'string', 'length' => 255, 'null' => false) ); - var $records = array( + public $records = array( array ('id' => 1, 'user_id' => 1, 'biography' => 'CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying applications.'), array ('id' => 2, 'user_id' => 2, 'biography' => ''), array ('id' => 3, 'user_id' => 3, 'biography' => ''), diff --git a/tests/fixtures/shipment_fixture.php b/Test/Fixture/ShipmentFixture.php similarity index 76% rename from tests/fixtures/shipment_fixture.php rename to Test/Fixture/ShipmentFixture.php index ef2d9ef..ab356ef 100644 --- a/tests/fixtures/shipment_fixture.php +++ b/Test/Fixture/ShipmentFixture.php @@ -1,16 +1,15 @@ array('type' => 'integer', 'key' => 'primary'), 'ship_date' => array('type' => 'date'), 'order_item_id' => array('type' => 'integer') ); - var $records = array( + public $records = array( array ('id' => 320, 'ship_date' => '2011-01-07', 'order_item_id' => 50), array ('id' => 319, 'ship_date' => '2011-01-07', 'order_item_id' => 50), array ('id' => 310, 'ship_date' => '2011-01-07', 'order_item_id' => 50) diff --git a/tests/fixtures/tag_fixture.php b/Test/Fixture/TagFixture.php similarity index 80% rename from tests/fixtures/tag_fixture.php rename to Test/Fixture/TagFixture.php index eca4f6d..44a5587 100644 --- a/tests/fixtures/tag_fixture.php +++ b/Test/Fixture/TagFixture.php @@ -1,16 +1,15 @@ array('type' => 'integer', 'key' => 'primary'), 'name' => array('type' => 'string', 'length' => 255, 'null' => false), 'parent_id' => array('type' => 'integer') ); - var $records = array( + public $records = array( array ('id' => 1, 'name' => 'General', 'parent_id' => null), array ('id' => 2, 'name' => 'Test I', 'parent_id' => 1), array ('id' => 3, 'name' => 'Test II', 'parent_id' => null), diff --git a/tests/fixtures/user_fixture.php b/Test/Fixture/UserFixture.php similarity index 75% rename from tests/fixtures/user_fixture.php rename to Test/Fixture/UserFixture.php index 33eb994..aeece22 100644 --- a/tests/fixtures/user_fixture.php +++ b/Test/Fixture/UserFixture.php @@ -1,15 +1,14 @@ array('type' => 'integer', 'key' => 'primary'), 'username' => array('type' => 'string', 'length' => 255, 'null' => false) ); - var $records = array( + public $records = array( array('id' => 1, 'username' => 'CakePHP'), array('id' => 2, 'username' => 'Zend'), array('id' => 3, 'username' => 'Symfony'),