From 737c287fca1afab2125e0d7e0c4ffa63f99a90ad Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 8 Dec 2011 20:16:20 +0000 Subject: [PATCH 01/10] rename to cake 2 dirs --- {models/behaviors => Model/Behavior}/linkable.php | 0 {tests/cases => Test/Case}/models/behaviors/linkable.test.php | 0 {tests/fixtures => Test/Fixture}/comment_fixture.php | 0 {tests/fixtures => Test/Fixture}/generic_fixture.php | 0 {tests/fixtures => Test/Fixture}/legacy_company_fixture.php | 0 {tests/fixtures => Test/Fixture}/legacy_product_fixture.php | 0 {tests/fixtures => Test/Fixture}/order_item_fixture.php | 0 {tests/fixtures => Test/Fixture}/post_fixture.php | 0 {tests/fixtures => Test/Fixture}/posts_tag_fixture.php | 0 {tests/fixtures => Test/Fixture}/profile_fixture.php | 0 {tests/fixtures => Test/Fixture}/shipment_fixture.php | 0 {tests/fixtures => Test/Fixture}/tag_fixture.php | 0 {tests/fixtures => Test/Fixture}/user_fixture.php | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename {models/behaviors => Model/Behavior}/linkable.php (100%) rename {tests/cases => Test/Case}/models/behaviors/linkable.test.php (100%) rename {tests/fixtures => Test/Fixture}/comment_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/generic_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/legacy_company_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/legacy_product_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/order_item_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/post_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/posts_tag_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/profile_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/shipment_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/tag_fixture.php (100%) rename {tests/fixtures => Test/Fixture}/user_fixture.php (100%) diff --git a/models/behaviors/linkable.php b/Model/Behavior/linkable.php similarity index 100% rename from models/behaviors/linkable.php rename to Model/Behavior/linkable.php diff --git a/tests/cases/models/behaviors/linkable.test.php b/Test/Case/models/behaviors/linkable.test.php similarity index 100% rename from tests/cases/models/behaviors/linkable.test.php rename to Test/Case/models/behaviors/linkable.test.php diff --git a/tests/fixtures/comment_fixture.php b/Test/Fixture/comment_fixture.php similarity index 100% rename from tests/fixtures/comment_fixture.php rename to Test/Fixture/comment_fixture.php diff --git a/tests/fixtures/generic_fixture.php b/Test/Fixture/generic_fixture.php similarity index 100% rename from tests/fixtures/generic_fixture.php rename to Test/Fixture/generic_fixture.php diff --git a/tests/fixtures/legacy_company_fixture.php b/Test/Fixture/legacy_company_fixture.php similarity index 100% rename from tests/fixtures/legacy_company_fixture.php rename to Test/Fixture/legacy_company_fixture.php diff --git a/tests/fixtures/legacy_product_fixture.php b/Test/Fixture/legacy_product_fixture.php similarity index 100% rename from tests/fixtures/legacy_product_fixture.php rename to Test/Fixture/legacy_product_fixture.php diff --git a/tests/fixtures/order_item_fixture.php b/Test/Fixture/order_item_fixture.php similarity index 100% rename from tests/fixtures/order_item_fixture.php rename to Test/Fixture/order_item_fixture.php diff --git a/tests/fixtures/post_fixture.php b/Test/Fixture/post_fixture.php similarity index 100% rename from tests/fixtures/post_fixture.php rename to Test/Fixture/post_fixture.php diff --git a/tests/fixtures/posts_tag_fixture.php b/Test/Fixture/posts_tag_fixture.php similarity index 100% rename from tests/fixtures/posts_tag_fixture.php rename to Test/Fixture/posts_tag_fixture.php diff --git a/tests/fixtures/profile_fixture.php b/Test/Fixture/profile_fixture.php similarity index 100% rename from tests/fixtures/profile_fixture.php rename to Test/Fixture/profile_fixture.php diff --git a/tests/fixtures/shipment_fixture.php b/Test/Fixture/shipment_fixture.php similarity index 100% rename from tests/fixtures/shipment_fixture.php rename to Test/Fixture/shipment_fixture.php diff --git a/tests/fixtures/tag_fixture.php b/Test/Fixture/tag_fixture.php similarity index 100% rename from tests/fixtures/tag_fixture.php rename to Test/Fixture/tag_fixture.php diff --git a/tests/fixtures/user_fixture.php b/Test/Fixture/user_fixture.php similarity index 100% rename from tests/fixtures/user_fixture.php rename to Test/Fixture/user_fixture.php From f05f6898d53ffe1f6c475c1c7fabee761e6efdca Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 8 Dec 2011 20:17:32 +0000 Subject: [PATCH 02/10] rename linkable to cake2.0 --- Model/Behavior/{linkable.php => LinkableBehavior.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Model/Behavior/{linkable.php => LinkableBehavior.php} (100%) diff --git a/Model/Behavior/linkable.php b/Model/Behavior/LinkableBehavior.php similarity index 100% rename from Model/Behavior/linkable.php rename to Model/Behavior/LinkableBehavior.php From 6276cf196c5632b6aaebb9c0c76646c182a668a0 Mon Sep 17 00:00:00 2001 From: sam Date: Sat, 10 Dec 2011 23:47:16 +0000 Subject: [PATCH 03/10] add a missing coma in README --- Model/Behavior/LinkableBehavior.php | 56 ++++++++++++++++++++++++----- README.md | 2 +- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/Model/Behavior/LinkableBehavior.php b/Model/Behavior/LinkableBehavior.php index 280b335..5f71db6 100644 --- a/Model/Behavior/LinkableBehavior.php +++ b/Model/Behavior/LinkableBehavior.php @@ -12,6 +12,7 @@ * @version 1.0; */ +App::uses('ModelBehavior', 'Model'); class LinkableBehavior extends ModelBehavior { protected $_key = 'link'; @@ -24,7 +25,25 @@ class LinkableBehavior extends ModelBehavior { protected $_defaults = array('type' => 'LEFT'); - public function beforeFind(&$Model, $query) { +/** + * Initializes this behavior for the model $Model + * + * @param Model $Model + * @param array $settigs list of settings to be used for this model + * @return void + */ + public function setup(Model $Model, $settings = array()) { + if (!isset($this->settings[$Model->alias])) { + $this->settings[$Model->alias] = array( + 'delimiter' => ';', + 'enclosure' => '"', + 'hasHeader' => true + ); + } + $this->settings[$Model->alias] = array_merge($this->settings[$Model->alias], $settings); + } + + public function beforeFind(Model $Model, $query) { if (isset($query[$this->_key])) { $optionsDefaults = $this->_defaults + array('reference' => $Model->alias, $this->_key => array()); @@ -67,16 +86,27 @@ public function beforeFind(&$Model, $query) { } 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(); - $associations = $_Model->getAssociated(); - + App::uses('ConnectionManager', 'Model'); + $sources = ConnectionManager::sourceList(); + //diebug($options); + + $_Model = ClassRegistry::init($options['class']); // the incoming model to be linked in query + $Reference = ClassRegistry::init($options['reference']); + //debug($_Model); + //diebug($Reference); // the already in query model that links to $_Model + //$db =& $_Model->getDataSource(); + $db = ConnectionManager::getDataSource($_Model->useDbConfig); + $associations = ConnectionManager::getDataSource($Reference->useDbConfig); + //debug($_Model); + // + // debug($Reference); + // debug($associations); + // debug($associations[$Reference->alias]); + // debug($Reference->belongsTo[$_Model->alias]); if (isset($Reference->belongsTo[$_Model->alias])) { $type = 'hasOne'; $association = $Reference->belongsTo[$_Model->alias]; - } else if (isset($associations[$Reference->alias])) { + } else if (!empty($associations[$Reference->alias])) { $type = $associations[$Reference->alias]; $association = $_Model->{$type}[$Reference->alias]; } else { @@ -125,6 +155,16 @@ public function beforeFind(&$Model, $query) { $modelKey = $_Model->escapeField(); $options['conditions'] = "{$modelLink} = {$modelKey}"; } else { + //try { + // $_Model->getDataSource()->fullTableName($_Model); + //} catch(MissingTableException $e) { + // if(array_key_exists($_Model->alias, array_flip(array_keys($Reference->belongsTo)))) { + // // debug($Reference->belongsTo[$_Model->alias]); + // //die('self join'); + // } + // //debug($_Model->alias);debug($Reference->belongsTo); + // //die('self join'); + //} $referenceKey = $Reference->escapeField($association['foreignKey']); $modelKey = $_Model->escapeField($_Model->primaryKey); $options['conditions'] = "{$modelKey} = {$referenceKey}"; diff --git a/README.md b/README.md index 839aac9..643b6a5 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ As a last example, pagination. This will find and paginate all posts with the ta ), 'link' => array( 'Tag' - ) + ), 'limit' => 10 ); From 358db2e214ea0b1c331817e36ba3a1f213f7f82f Mon Sep 17 00:00:00 2001 From: sam Date: Sat, 10 Dec 2011 23:47:37 +0000 Subject: [PATCH 04/10] Update Behavior --- Model/Behavior/LinkableBehavior.php | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/Model/Behavior/LinkableBehavior.php b/Model/Behavior/LinkableBehavior.php index 5f71db6..b372ea9 100644 --- a/Model/Behavior/LinkableBehavior.php +++ b/Model/Behavior/LinkableBehavior.php @@ -25,6 +25,8 @@ class LinkableBehavior extends ModelBehavior { protected $_defaults = array('type' => 'LEFT'); + public $settings = array(); + /** * Initializes this behavior for the model $Model * @@ -35,9 +37,9 @@ class LinkableBehavior extends ModelBehavior { public function setup(Model $Model, $settings = array()) { if (!isset($this->settings[$Model->alias])) { $this->settings[$Model->alias] = array( - 'delimiter' => ';', - 'enclosure' => '"', - 'hasHeader' => true + 'type' => true, 'table' => true, 'alias' => true, + 'conditions' => true, 'fields' => true, 'reference' => true, + 'class' => true, 'defaults' => true ); } $this->settings[$Model->alias] = array_merge($this->settings[$Model->alias], $settings); @@ -88,21 +90,12 @@ public function beforeFind(Model $Model, $query) { } App::uses('ConnectionManager', 'Model'); $sources = ConnectionManager::sourceList(); - //diebug($options); $_Model = ClassRegistry::init($options['class']); // the incoming model to be linked in query $Reference = ClassRegistry::init($options['reference']); - //debug($_Model); - //diebug($Reference); // the already in query model that links to $_Model - //$db =& $_Model->getDataSource(); + // the already in query model that links to $_Model $db = ConnectionManager::getDataSource($_Model->useDbConfig); $associations = ConnectionManager::getDataSource($Reference->useDbConfig); - //debug($_Model); - // - // debug($Reference); - // debug($associations); - // debug($associations[$Reference->alias]); - // debug($Reference->belongsTo[$_Model->alias]); if (isset($Reference->belongsTo[$_Model->alias])) { $type = 'hasOne'; $association = $Reference->belongsTo[$_Model->alias]; @@ -155,16 +148,6 @@ public function beforeFind(Model $Model, $query) { $modelKey = $_Model->escapeField(); $options['conditions'] = "{$modelLink} = {$modelKey}"; } else { - //try { - // $_Model->getDataSource()->fullTableName($_Model); - //} catch(MissingTableException $e) { - // if(array_key_exists($_Model->alias, array_flip(array_keys($Reference->belongsTo)))) { - // // debug($Reference->belongsTo[$_Model->alias]); - // //die('self join'); - // } - // //debug($_Model->alias);debug($Reference->belongsTo); - // //die('self join'); - //} $referenceKey = $Reference->escapeField($association['foreignKey']); $modelKey = $_Model->escapeField($_Model->primaryKey); $options['conditions'] = "{$modelKey} = {$referenceKey}"; From 0b6f011cbb87c57ecd5900dff7f7518ac8c16c3b Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 15 Dec 2011 15:13:35 +0000 Subject: [PATCH 05/10] revert tests but use test cfg db --- .../Model/Behavior/LinkableBehaviorTest.php | 564 ++++++++++++++++++ Test/Fixture/CommentFixture.php | 19 + Test/Fixture/GenericFixture.php | 17 + Test/Fixture/LegacyCompanyFixture.php | 16 + Test/Fixture/LegacyProductFixture.php | 17 + Test/Fixture/OrderItemFixture.php | 14 + Test/Fixture/PostFixture.php | 16 + Test/Fixture/PostsTagFixture.php | 19 + Test/Fixture/ProfileFixture.php | 18 + Test/Fixture/ShipmentFixture.php | 17 + Test/Fixture/TagFixture.php | 18 + Test/Fixture/UserFixture.php | 17 + 12 files changed, 752 insertions(+) create mode 100644 Test/Case/Model/Behavior/LinkableBehaviorTest.php create mode 100644 Test/Fixture/CommentFixture.php create mode 100644 Test/Fixture/GenericFixture.php create mode 100644 Test/Fixture/LegacyCompanyFixture.php create mode 100644 Test/Fixture/LegacyProductFixture.php create mode 100644 Test/Fixture/OrderItemFixture.php create mode 100644 Test/Fixture/PostFixture.php create mode 100644 Test/Fixture/PostsTagFixture.php create mode 100644 Test/Fixture/ProfileFixture.php create mode 100644 Test/Fixture/ShipmentFixture.php create mode 100644 Test/Fixture/TagFixture.php create mode 100644 Test/Fixture/UserFixture.php diff --git a/Test/Case/Model/Behavior/LinkableBehaviorTest.php b/Test/Case/Model/Behavior/LinkableBehaviorTest.php new file mode 100644 index 0000000..0536f70 --- /dev/null +++ b/Test/Case/Model/Behavior/LinkableBehaviorTest.php @@ -0,0 +1,564 @@ +User = ClassRegistry::init('User'); + } + + public function endTest() { + + unset($this->User); + } + + public function testBelongsTo() + { + $arrayExpected = array( + 'User' => array('id' => 1, 'username' => 'CakePHP'), + 'Profile' => 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.') + ); + + $arrayResult = $this->User->find('first', array( + 'contain' => array( + 'Profile' + ) + )); + $this->assertTrue(isset($arrayResult['Profile']), '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( + 'fields' => array( + 'id', + 'username' + ), + 'contain' => false, + 'link' => array( + 'Profile' => array( + 'fields' => array( + 'id', + 'user_id', + 'biography' + ) + ) + ) + )); + + $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Linkable: %s'); + $this->assertTrue(!empty($arrayResult['Profile']), '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( + 'contain' => false, + 'link' => array( + 'Profile' + ) + )); + + $this->assertTrue(isset($arrayResult['Profile']), '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( + 'User' => array('id' => 1, 'username' => 'CakePHP'), + 'Generic' => array('id' => 1, 'text' => '') + ); + + $arrayResult = $this->User->find('first', array( + 'contain' => false, + 'link' => array( + 'Generic' => array( + 'class' => 'Generic', + 'conditions' => array('exactly' => 'User.id = Generic.id'), + 'fields' => array( + 'id', + 'text' + ) + ) + ) + )); + + $this->assertTrue(isset($arrayResult['Generic']), '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 and using array conditions instead of plain string + $arrayExpected = array( + 'User' => array('id' => 4, 'username' => 'CodeIgniter'), + 'Generic' => array('id' => 4, 'text' => '') + ); + + $arrayResult = $this->User->find('first', array( + 'contain' => false, + 'link' => array( + 'Generic' => array( + 'class' => 'Generic', + 'conditions' => array('exactly' => array('User.id = Generic.id')), + 'fields' => array( + 'id', + 'text' + ) + ) + ), + 'order' => 'Generic.id DESC' + )); + + $this->assertEquals($arrayResult, $arrayExpected, 'On-the-fly belongsTo association via Linkable, with order: %s'); + } + + public function testHasMany() + { + // hasMany association via Containable. Should still work when Linkable is loaded + $arrayExpected = array( + 'User' => array('id' => 1, 'username' => 'CakePHP'), + 'Comment' => array( + 0 => array( + 'id' => 1, + 'user_id' => 1, + 'body' => 'Text' + ), + 1 => array( + 'id' => 2, + 'user_id' => 1, + 'body' => 'Text' + ), + ) + ); + + $arrayResult = $this->User->find('first', array( + 'contain' => array( + 'Comment' + ), + 'order' => 'User.id ASC' + )); + $this->assertTrue(isset($arrayResult['Comment']), 'hasMany association via Containable: %s'); + $this->assertEquals($arrayResult, $arrayExpected, 'hasMany association via Containable: %s'); + + // Same association, but this time with Linkable + $arrayExpected = array( + 'User' => array('id' => 1, 'username' => 'CakePHP'), + 'Comment' => array( + 'id' => 1, + 'user_id' => 1, + 'body' => 'Text' + ) + ); + + $arrayResult = $this->User->find('first', array( + 'fields' => array( + 'id', + 'username' + ), + 'contain' => false, + 'link' => array( + 'Comment' => array( + 'fields' => array( + 'id', + 'user_id', + 'body' + ) + ) + ), + 'order' => 'User.id ASC', + 'group' => 'User.id' + )); + + $this->assertEquals($arrayResult, $arrayExpected, 'hasMany association via Linkable: %s'); + } + + public function testComplexAssociations() + { + $this->Post = ClassRegistry::init('Post'); + + $arrayExpected = array( + 'Post' => array('id' => 1, 'title' => 'Post 1', 'user_id' => 1), + 'Tag' => array('name' => 'General'), + 'Profile' => array('biography' => 'CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying applications.'), + 'MainTag' => array('name' => 'General'), + 'Generic' => array('id' => 1,'text' => ''), + 'User' => array('id' => 1, 'username' => 'CakePHP') + ); + + $arrayResult = $this->Post->find('first', array( + 'conditions' => array( + 'MainTag.id' => 1 + ), + 'link' => array( + 'User' => array( + 'Profile' => array( + 'fields' => array( + 'biography' + ), + 'Generic' => array( + 'class' => 'Generic', + 'conditions' => array('exactly' => 'User.id = Generic.id'), + ) + ) + ), + 'Tag' => array( + 'table' => 'tags', + 'fields' => array( + 'name' + ) + ), + 'MainTag' => array( + 'class' => 'Tag', + 'conditions' => array('exactly' => 'PostsTag.post_id = Post.id'), + 'fields' => array( + 'MainTag.name' // @fixme Wants to use class name (Tag) instead of alias (MainTag) + ) + ) + ) + )); + + $this->assertEquals($arrayExpected, $arrayResult, 'Complex find: %s'); + + // Linkable and Containable combined + $arrayExpected = array( + 'Post' => array('id' => 1, 'title' => 'Post 1', 'user_id' => 1), + 'Tag' => array( + array('id' => 1, 'name' => 'General', 'parent_id' => null, 'PostsTag' => array('id' => 1, 'post_id' => 1, 'tag_id' => 1, 'main' => 0)), + array('id' => 2, 'name' => 'Test I', 'parent_id' => 1, 'PostsTag' => array('id' => 2, 'post_id' => 1, 'tag_id' => 2, 'main' => 1)) + ), + 'User' => array('id' => 1, 'username' => 'CakePHP') + ); + + $arrayResult = $this->Post->find('first', array( + 'contain' => array( + 'Tag' + ), + 'link' => array( + 'User' + ) + )); + + $this->assertEquals($arrayResult, $arrayExpected, 'Linkable and Containable combined: %s'); + } + + public function _testPagination() + { + $objController = new Controller(new CakeRequest('/'), new CakeResponse()); + $objController->layout = 'ajax'; + $objController->uses = array('User'); + $objController->constructClasses(); + $objController->request->url = '/'; + + $objController->paginate = array( + 'fields' => array( + 'username' + ), + 'contain' => false, + 'link' => array( + 'Profile' => array( + 'fields' => array( + 'biography' + ) + ) + ), + 'limit' => 2 + ); + + $arrayResult = $objController->paginate('User'); + + $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( + 'fields' => array( + 'id' + ), + 'contain' => false, + 'link' => array( + 'Profile' => array( + 'fields' => array( + 'user_id' + ) + ) + ), + 'limit' => 2, + 'order' => 'Profile.user_id DESC' + ); + + $arrayResult = $objController->paginate('User'); + + $arrayExpected = array( + 0 => array( + 'User' => array( + 'id' => 4 + ), + 'Profile' => array ('user_id' => 4) + ), + 1 => array( + 'User' => array( + 'id' => 3 + ), + 'Profile' => array ('user_id' => 3) + ) + ); + + $this->assertEquals($arrayResult, $arrayExpected, 'Paging with order on join table row: %s'); + + // Pagination without specifying any fields + $objController->paginate = array( + 'contain' => false, + 'link' => array( + 'Profile' + ), + 'limit' => 2, + 'order' => 'Profile.user_id DESC' + ); + + $arrayResult = $objController->paginate('User'); + $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() + { + $this->Tag = ClassRegistry::init('Tag'); + + $arrayExpected = array( + 'Tag' => array( + 'name' => 'Test I' + ), + 'Parent' => array( + 'name' => 'General' + ) + ); + + $arrayResult = $this->Tag->find('first', array( + 'fields' => array( + 'name' + ), + 'conditions' => array( + 'Tag.id' => 2 + ), + 'link' => array( + 'Parent' => array( + 'fields' => array( + 'name' + ) + ) + ) + )); + + $this->assertEquals($arrayExpected, $arrayResult, 'Association with non-standard name: %s'); + + + $this->LegacyProduct = ClassRegistry::init('LegacyProduct'); + + $arrayExpected = array( + 'LegacyProduct' => array( + 'name' => 'Velocipede' + ), + 'Maker' => array( + 'company_name' => 'Vintage Stuff Manufactory' + ), + 'Transporter' => array( + 'company_name' => 'Joe & Co Crate Shipping Company' + ) + ); + + $arrayResult = $this->LegacyProduct->find('first', array( + 'fields' => array( + 'name' + ), + 'conditions' => array( + 'LegacyProduct.product_id' => 1 + ), + 'link' => array( + 'Maker' => array( + 'fields' => array( + 'company_name' + ) + ), + 'Transporter' => array( + 'fields' => array( + 'company_name' + ) + ) + ) + )); + + $this->assertEquals($arrayExpected, $arrayResult, 'belongsTo associations with custom foreignKey: %s'); + + $arrayExpected = array( + 'ProductsMade' => array( + 'name' => 'Velocipede' + ), + 'Maker' => array( + 'company_name' => 'Vintage Stuff Manufactory' + ) + ); + + $arrayResult = $this->LegacyProduct->Maker->find('first', array( + 'fields' => array( + 'company_name' + ), + 'conditions' => array( + 'Maker.company_id' => 1 + ), + 'link' => array( + 'ProductsMade' => array( + 'fields' => array( + 'name' + ) + ) + ) + )); + + $this->assertEquals($arrayExpected, $arrayResult, 'hasMany association with custom foreignKey: %s'); + } + + public function _testAliasedBelongsToWithSameModelAsHasMany() + { + $this->OrderItem = ClassRegistry::init('OrderItem'); + + $arrayExpected = array( + 0 => array( + 'OrderItem' => array( + 'id' => 50, + 'active_shipment_id' => 320 + ), + 'ActiveShipment' => array( + 'id' => 320, + 'ship_date' => '2011-01-07', + 'order_item_id' => 50 + ) + ) + ); + + $arrayResult = $this->OrderItem->find('all', array( + 'recursive' => -1, + 'conditions' => array( + 'ActiveShipment.ship_date' => date('2011-01-07'), + ), + 'link' => array('ActiveShipment'), + )); + + $this->assertEquals($arrayExpected, $arrayResult, 'belongsTo association with alias (requested), with hasMany to the same model without alias: %s'); + } +} + + +class TestModel extends CakeTestModel { + public $useDbConfig = 'test'; + + 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/Test/Fixture/CommentFixture.php b/Test/Fixture/CommentFixture.php new file mode 100644 index 0000000..c4dd9bf --- /dev/null +++ b/Test/Fixture/CommentFixture.php @@ -0,0 +1,19 @@ + array('type' => 'integer', 'key' => 'primary'), + 'user_id' => array('type' => 'integer'), + 'body' => array('type' => 'string', 'length' => 255, 'null' => false) + ); + + 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'), + array('id' => 4, 'user_id' => 3, 'body' => 'Text'), + array('id' => 5, 'user_id' => 4, 'body' => 'Text') + ); +} diff --git a/Test/Fixture/GenericFixture.php b/Test/Fixture/GenericFixture.php new file mode 100644 index 0000000..129d44c --- /dev/null +++ b/Test/Fixture/GenericFixture.php @@ -0,0 +1,17 @@ + array('type' => 'integer', 'key' => 'primary'), + 'text' => array('type' => 'string', 'length' => 255, 'null' => false) + ); + + public $records = array( + array ('id' => 1, 'text' => ''), + array ('id' => 2, 'text' => ''), + array ('id' => 3, 'text' => ''), + array ('id' => 4, 'text' => '') + ); +} diff --git a/Test/Fixture/LegacyCompanyFixture.php b/Test/Fixture/LegacyCompanyFixture.php new file mode 100644 index 0000000..624fc0b --- /dev/null +++ b/Test/Fixture/LegacyCompanyFixture.php @@ -0,0 +1,16 @@ + array('type' => 'integer', 'key' => 'primary'), + 'company_name' => array('type' => 'string', 'length' => 255, 'null' => false), + ); + + 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/Test/Fixture/LegacyProductFixture.php b/Test/Fixture/LegacyProductFixture.php new file mode 100644 index 0000000..6e0474a --- /dev/null +++ b/Test/Fixture/LegacyProductFixture.php @@ -0,0 +1,17 @@ + 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') + ); + + 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/Test/Fixture/OrderItemFixture.php b/Test/Fixture/OrderItemFixture.php new file mode 100644 index 0000000..414d87d --- /dev/null +++ b/Test/Fixture/OrderItemFixture.php @@ -0,0 +1,14 @@ + array('type' => 'integer', 'key' => 'primary'), + 'active_shipment_id' => array('type' => 'integer'), + ); + + public $records = array( + array ('id' => 50, 'active_shipment_id' => 320) + ); +} diff --git a/Test/Fixture/PostFixture.php b/Test/Fixture/PostFixture.php new file mode 100644 index 0000000..50aac19 --- /dev/null +++ b/Test/Fixture/PostFixture.php @@ -0,0 +1,16 @@ + array('type' => 'integer', 'key' => 'primary'), + 'title' => array('type' => 'string', 'length' => 255, 'null' => false), + 'user_id' => array('type' => 'integer'), + ); + + public $records = array( + array ('id' => 1, 'title' => 'Post 1', 'user_id' => 1), + array ('id' => 2, 'title' => 'Post 2', 'user_id' => 2) + ); +} diff --git a/Test/Fixture/PostsTagFixture.php b/Test/Fixture/PostsTagFixture.php new file mode 100644 index 0000000..8ba2ed3 --- /dev/null +++ b/Test/Fixture/PostsTagFixture.php @@ -0,0 +1,19 @@ + array('type' => 'integer', 'key' => 'primary'), + 'post_id' => array('type' => 'integer'), + 'tag_id' => array('type' => 'integer'), + 'main' => array('type' => 'integer') + ); + + 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), + array ('id' => 4, 'post_id' => 2, 'tag_id' => 4, 'main' => 0), + ); +} \ No newline at end of file diff --git a/Test/Fixture/ProfileFixture.php b/Test/Fixture/ProfileFixture.php new file mode 100644 index 0000000..6fbc4dd --- /dev/null +++ b/Test/Fixture/ProfileFixture.php @@ -0,0 +1,18 @@ + array('type' => 'integer', 'key' => 'primary'), + 'user_id' => array('type' => 'integer'), + 'biography' => array('type' => 'string', 'length' => 255, 'null' => false) + ); + + 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' => ''), + array ('id' => 4, 'user_id' => 4, 'biography' => '') + ); +} diff --git a/Test/Fixture/ShipmentFixture.php b/Test/Fixture/ShipmentFixture.php new file mode 100644 index 0000000..ab356ef --- /dev/null +++ b/Test/Fixture/ShipmentFixture.php @@ -0,0 +1,17 @@ + array('type' => 'integer', 'key' => 'primary'), + 'ship_date' => array('type' => 'date'), + 'order_item_id' => array('type' => 'integer') + ); + + 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/Test/Fixture/TagFixture.php b/Test/Fixture/TagFixture.php new file mode 100644 index 0000000..44a5587 --- /dev/null +++ b/Test/Fixture/TagFixture.php @@ -0,0 +1,18 @@ + array('type' => 'integer', 'key' => 'primary'), + 'name' => array('type' => 'string', 'length' => 255, 'null' => false), + 'parent_id' => array('type' => 'integer') + ); + + 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), + array ('id' => 4, 'name' => 'Test III', 'parent_id' => null) + ); +} diff --git a/Test/Fixture/UserFixture.php b/Test/Fixture/UserFixture.php new file mode 100644 index 0000000..aeece22 --- /dev/null +++ b/Test/Fixture/UserFixture.php @@ -0,0 +1,17 @@ + array('type' => 'integer', 'key' => 'primary'), + 'username' => array('type' => 'string', 'length' => 255, 'null' => false) + ); + + public $records = array( + array('id' => 1, 'username' => 'CakePHP'), + array('id' => 2, 'username' => 'Zend'), + array('id' => 3, 'username' => 'Symfony'), + array('id' => 4, 'username' => 'CodeIgniter') + ); +} From 1a9af1bc7b9bba7121f2d0d15b941cc06fd2bead Mon Sep 17 00:00:00 2001 From: euromark Date: Sat, 10 Dec 2011 02:06:11 +0100 Subject: [PATCH 06/10] Merge in 2.0 init commits from Dereuromark --- Model/Behavior/LinkableBehavior.php | 216 +++---- README.markdown | 66 ++ README.md | 114 ---- .../Model/Behavior/LinkableBehaviorTest.php | 2 +- Test/Case/models/behaviors/linkable.test.php | 568 ------------------ 5 files changed, 166 insertions(+), 800 deletions(-) create mode 100644 README.markdown delete mode 100644 README.md delete mode 100644 Test/Case/models/behaviors/linkable.test.php diff --git a/Model/Behavior/LinkableBehavior.php b/Model/Behavior/LinkableBehavior.php index b372ea9..dc39b83 100644 --- a/Model/Behavior/LinkableBehavior.php +++ b/Model/Behavior/LinkableBehavior.php @@ -1,22 +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 (not fully confirmed - test cases wont work) */ - -App::uses('ModelBehavior', 'Model'); class LinkableBehavior extends ModelBehavior { - + protected $_key = 'link'; - + protected $_options = array( 'type' => true, 'table' => true, 'alias' => true, 'conditions' => true, 'fields' => true, 'reference' => true, @@ -24,195 +36,165 @@ class LinkableBehavior extends ModelBehavior { ); protected $_defaults = array('type' => 'LEFT'); - - public $settings = array(); - -/** - * Initializes this behavior for the model $Model - * - * @param Model $Model - * @param array $settigs list of settings to be used for this model - * @return void - */ - public function setup(Model $Model, $settings = array()) { - if (!isset($this->settings[$Model->alias])) { - $this->settings[$Model->alias] = array( - 'type' => true, 'table' => true, 'alias' => true, - 'conditions' => true, 'fields' => true, 'reference' => true, - 'class' => true, 'defaults' => true - ); - } - $this->settings[$Model->alias] = array_merge($this->settings[$Model->alias], $settings); - } - + public function beforeFind(Model $Model, $query) { - if (isset($query[$this->_key])) { - + 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']); } - App::uses('ConnectionManager', 'Model'); - $sources = ConnectionManager::sourceList(); - - $_Model = ClassRegistry::init($options['class']); // the incoming model to be linked in query - $Reference = ClassRegistry::init($options['reference']); + + // the incoming model to be linked in query + $_Model = ClassRegistry::init($options['class']); // the already in query model that links to $_Model - $db = ConnectionManager::getDataSource($_Model->useDbConfig); - $associations = ConnectionManager::getDataSource($Reference->useDbConfig); + $Reference = ClassRegistry::init($options['reference']); + $db = $_Model->getDataSource(); + $associations = $_Model->getAssociated(); if (isset($Reference->belongsTo[$_Model->alias])) { $type = 'hasOne'; - $association = $Reference->belongsTo[$_Model->alias]; - } else if (!empty($associations[$Reference->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']); $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}"; + $options['conditions'][] = "{$modelLink} = {$modelKey}"; } else { $referenceKey = $Reference->escapeField($association['foreignKey']); $modelKey = $_Model->escapeField($_Model->primaryKey); - $options['conditions'] = "{$modelKey} = {$referenceKey}"; - } + $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']); + } + + if (is_array($query['fields'])) + $query['fields'] = array_merge($query['fields'], $options['fields']); + else + $query['fields'] = array_merge($db->fields($Model), $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']); - } - } - else - { - if (!empty($association['fields'])) { - $options['fields'] = $db->fields($_Model, null, $association['fields']); - } else { - $options['fields'] = $db->fields($_Model); - } - - if (is_array($query['fields'])) { - $query['fields'] = array_merge($query['fields'], $options['fields']); - } // Leave COUNT() queries alone - elseif($query['fields'] != 'COUNT(*) AS `count`') { - $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); + } + + if (is_array($query['fields'])) { + $query['fields'] = array_merge($query['fields'], $options['fields']); + } 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 643b6a5..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/Test/Case/Model/Behavior/LinkableBehaviorTest.php b/Test/Case/Model/Behavior/LinkableBehaviorTest.php index 0536f70..97c6fd6 100644 --- a/Test/Case/Model/Behavior/LinkableBehaviorTest.php +++ b/Test/Case/Model/Behavior/LinkableBehaviorTest.php @@ -465,7 +465,7 @@ public function _testAliasedBelongsToWithSameModelAsHasMany() class TestModel extends CakeTestModel { - public $useDbConfig = 'test'; + //public $useDbConfig = 'test'; public $recursive = 0; diff --git a/Test/Case/models/behaviors/linkable.test.php b/Test/Case/models/behaviors/linkable.test.php deleted file mode 100644 index e9fae43..0000000 --- a/Test/Case/models/behaviors/linkable.test.php +++ /dev/null @@ -1,568 +0,0 @@ - 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 -{ - public $fixtures = array( - 'plugin.linkable.user', - 'plugin.linkable.profile', - 'plugin.linkable.generic', - 'plugin.linkable.comment', - '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 function startTest() - { - $this->User =& ClassRegistry::init('User'); - } - - public function testBelongsTo() - { - $arrayExpected = array( - 'User' => array('id' => 1, 'username' => 'CakePHP'), - 'Profile' => 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.') - ); - - $arrayResult = $this->User->find('first', array( - 'contain' => array( - 'Profile' - ) - )); - $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Containable: %s'); - $this->assertEqual($arrayResult, $arrayExpected, 'belongsTo association via Containable: %s'); - - // Same association, but this time with Linkable - $arrayResult = $this->User->find('first', array( - 'fields' => array( - 'id', - 'username' - ), - 'contain' => false, - 'link' => array( - 'Profile' => array( - 'fields' => array( - 'id', - 'user_id', - 'biography' - ) - ) - ) - )); - - $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'); - - // Linkable association, no field lists - $arrayResult = $this->User->find('first', array( - 'contain' => false, - 'link' => array( - 'Profile' - ) - )); - - $this->assertTrue(isset($arrayResult['Profile']), 'belongsTo association via Linkable (automatic fields): %s'); - $this->assertEqual($arrayResult, $arrayExpected, 'belongsTo association via Linkable (automatic fields): %s'); - - // On-the-fly association via Linkable - $arrayExpected = array( - 'User' => array('id' => 1, 'username' => 'CakePHP'), - 'Generic' => array('id' => 1, 'text' => '') - ); - - $arrayResult = $this->User->find('first', array( - 'contain' => false, - 'link' => array( - 'Generic' => array( - 'class' => 'Generic', - 'conditions' => 'User.id = Generic.id', - 'fields' => array( - 'id', - 'text' - ) - ) - ) - )); - - $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'); - - // On-the-fly association via Linkable, with order on the associations' row - $arrayExpected = array( - 'User' => array('id' => 4, 'username' => 'CodeIgniter'), - 'Generic' => array('id' => 4, 'text' => '') - ); - - $arrayResult = $this->User->find('first', array( - 'contain' => false, - 'link' => array( - 'Generic' => array( - 'class' => 'Generic', - 'conditions' => 'User.id = Generic.id', - 'fields' => array( - 'id', - 'text' - ) - ) - ), - 'order' => 'Generic.id DESC' - )); - - $this->assertEqual($arrayResult, $arrayExpected, 'On-the-fly belongsTo association via Linkable, with order: %s'); - } - - public function testHasMany() - { - // hasMany association via Containable. Should still work when Linkable is loaded - $arrayExpected = array( - 'User' => array('id' => 1, 'username' => 'CakePHP'), - 'Comment' => array( - 0 => array( - 'id' => 1, - 'user_id' => 1, - 'body' => 'Text' - ), - 1 => array( - 'id' => 2, - 'user_id' => 1, - 'body' => 'Text' - ), - ) - ); - - $arrayResult = $this->User->find('first', array( - 'contain' => array( - 'Comment' - ), - 'order' => 'User.id ASC' - )); - $this->assertTrue(isset($arrayResult['Comment']), 'hasMany association via Containable: %s'); - $this->assertEqual($arrayResult, $arrayExpected, 'hasMany association via Containable: %s'); - - // Same association, but this time with Linkable - $arrayExpected = array( - 'User' => array('id' => 1, 'username' => 'CakePHP'), - 'Comment' => array( - 'id' => 1, - 'user_id' => 1, - 'body' => 'Text' - ) - ); - - $arrayResult = $this->User->find('first', array( - 'fields' => array( - 'id', - 'username' - ), - 'contain' => false, - 'link' => array( - 'Comment' => array( - 'fields' => array( - 'id', - 'user_id', - 'body' - ) - ) - ), - 'order' => 'User.id ASC', - 'group' => 'User.id' - )); - - $this->assertEqual($arrayResult, $arrayExpected, 'hasMany association via Linkable: %s'); - } - - public function testComplexAssociations() - { - $this->Post =& ClassRegistry::init('Post'); - - $arrayExpected = array( - 'Post' => array('id' => 1, 'title' => 'Post 1', 'user_id' => 1), - 'Tag' => array('name' => 'General'), - 'Profile' => array('biography' => 'CakePHP is a rapid development framework for PHP that provides an extensible architecture for developing, maintaining, and deploying applications.'), - 'MainTag' => array('name' => 'General'), - 'Generic' => array('id' => 1,'text' => ''), - 'User' => array('id' => 1, 'username' => 'CakePHP') - ); - - $arrayResult = $this->Post->find('first', array( - 'conditions' => array( - 'MainTag.id' => 1 - ), - 'link' => array( - 'User' => array( - 'conditions' => 'Post.user_id = User.id', - 'Profile' => array( - 'fields' => array( - 'biography' - ), - 'Generic' => array( - 'class' => 'Generic', - 'conditions' => 'User.id = Generic.id' - ) - ) - ), - 'Tag' => array( - 'table' => 'tags', - 'fields' => array( - 'name' - ) - ), - 'MainTag' => array( - 'class' => 'Tag', - 'conditions' => 'PostsTag.post_id = Post.id', - 'fields' => array( - 'MainTag.name' // @fixme Wants to use class name (Tag) instead of alias (MainTag) - ) - ) - ) - )); - - $this->assertEqual($arrayExpected, $arrayResult, 'Complex find: %s'); - - // Linkable and Containable combined - $arrayExpected = array( - 'Post' => array('id' => 1, 'title' => 'Post 1', 'user_id' => 1), - 'Tag' => array( - array('id' => 1, 'name' => 'General', 'parent_id' => null, 'PostsTag' => array('id' => 1, 'post_id' => 1, 'tag_id' => 1, 'main' => 0)), - array('id' => 2, 'name' => 'Test I', 'parent_id' => 1, 'PostsTag' => array('id' => 2, 'post_id' => 1, 'tag_id' => 2, 'main' => 1)) - ), - 'User' => array('id' => 1, 'username' => 'CakePHP') - ); - - $arrayResult = $this->Post->find('first', array( - 'contain' => array( - 'Tag' - ), - 'link' => array( - 'User' => array( - 'conditions' => 'User.id = Post.user_id' - ) - ) - )); - - $this->assertEqual($arrayResult, $arrayExpected, 'Linkable and Containable combined: %s'); - } - - public function testPagination() - { - $objController = new Controller(); - $objController->uses = array('User'); - $objController->constructClasses(); - $objController->params['url']['url'] = '/'; - - $objController->paginate = array( - 'fields' => array( - 'username' - ), - 'contain' => false, - 'link' => array( - 'Profile' => array( - 'fields' => array( - 'biography' - ) - ) - ), - 'limit' => 2 - ); - - $arrayResult = $objController->paginate('User'); - - $this->assertEqual($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( - 'fields' => array( - 'id' - ), - 'contain' => false, - 'link' => array( - 'Profile' => array( - 'fields' => array( - 'user_id' - ) - ) - ), - 'limit' => 2, - 'order' => 'Profile.user_id DESC' - ); - - $arrayResult = $objController->paginate('User'); - - $arrayExpected = array( - 0 => array( - 'User' => array( - 'id' => 4 - ), - 'Profile' => array ('user_id' => 4) - ), - 1 => array( - 'User' => array( - 'id' => 3 - ), - 'Profile' => array ('user_id' => 3) - ) - ); - - $this->assertEqual($arrayResult, $arrayExpected, 'Paging with order on join table row: %s'); - - // Pagination without specifying any fields - $objController->paginate = array( - 'contain' => false, - 'link' => array( - 'Profile' - ), - 'limit' => 2, - 'order' => 'Profile.user_id DESC' - ); - - $arrayResult = $objController->paginate('User'); - $this->assertEqual($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() - { - $this->Tag =& ClassRegistry::init('Tag'); - - $arrayExpected = array( - 'Tag' => array( - 'name' => 'Test I' - ), - 'Parent' => array( - 'name' => 'General' - ) - ); - - $arrayResult = $this->Tag->find('first', array( - 'fields' => array( - 'name' - ), - 'conditions' => array( - 'Tag.id' => 2 - ), - 'link' => array( - 'Parent' => array( - 'fields' => array( - 'name' - ) - ) - ) - )); - - $this->assertEqual($arrayExpected, $arrayResult, 'Association with non-standard name: %s'); - - - $this->LegacyProduct =& ClassRegistry::init('LegacyProduct'); - - $arrayExpected = array( - 'LegacyProduct' => array( - 'name' => 'Velocipede' - ), - 'Maker' => array( - 'company_name' => 'Vintage Stuff Manufactory' - ), - 'Transporter' => array( - 'company_name' => 'Joe & Co Crate Shipping Company' - ) - ); - - $arrayResult = $this->LegacyProduct->find('first', array( - 'fields' => array( - 'name' - ), - 'conditions' => array( - 'LegacyProduct.product_id' => 1 - ), - 'link' => array( - 'Maker' => array( - 'fields' => array( - 'company_name' - ) - ), - 'Transporter' => array( - 'fields' => array( - 'company_name' - ) - ) - ) - )); - - $this->assertEqual($arrayExpected, $arrayResult, 'belongsTo associations with custom foreignKey: %s'); - - $arrayExpected = array( - 'ProductsMade' => array( - 'name' => 'Velocipede' - ), - 'Maker' => array( - 'company_name' => 'Vintage Stuff Manufactory' - ) - ); - - $arrayResult = $this->LegacyProduct->Maker->find('first', array( - 'fields' => array( - 'company_name' - ), - 'conditions' => array( - 'Maker.company_id' => 1 - ), - 'link' => array( - 'ProductsMade' => array( - 'fields' => array( - 'name' - ) - ) - ) - )); - - $this->assertEqual($arrayExpected, $arrayResult, 'hasMany association with custom foreignKey: %s'); - } - - public function testAliasedBelongsToWithSameModelAsHasMany() - { - $this->OrderItem =& ClassRegistry::init('OrderItem'); - - $arrayExpected = array( - 0 => array( - 'OrderItem' => array( - 'id' => 50, - 'active_shipment_id' => 320 - ), - 'ActiveShipment' => array( - 'id' => 320, - 'ship_date' => '2011-01-07', - 'order_item_id' => 50 - ) - ) - ); - - $arrayResult = $this->OrderItem->find('all', array( - 'recursive' => -1, - 'conditions' => array( - 'ActiveShipment.ship_date' => date('2011-01-07'), - ), - 'link' => array('ActiveShipment'), - )); - - $this->assertEqual($arrayExpected, $arrayResult, 'belongsTo association with alias (requested), with hasMany to the same model without alias: %s'); - } -} From 08809f7c6b5f0cf62073d2633f0a8c217462f698 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 15 Dec 2011 15:39:53 +0000 Subject: [PATCH 07/10] make test cases run - test model was using test_suite connection now uses default conection for tests --- Model/Behavior/LinkableBehavior.php | 2 +- Test/Case/Model/Behavior/LinkableBehaviorTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Model/Behavior/LinkableBehavior.php b/Model/Behavior/LinkableBehavior.php index dc39b83..8be1ba5 100644 --- a/Model/Behavior/LinkableBehavior.php +++ b/Model/Behavior/LinkableBehavior.php @@ -23,7 +23,7 @@ * * @version 1.2: * @modified Mark Scherer - * - works with cakephp2.0 (not fully confirmed - test cases wont work) + * - works with cakephp2.0 (89.84 test coverage) */ class LinkableBehavior extends ModelBehavior { diff --git a/Test/Case/Model/Behavior/LinkableBehaviorTest.php b/Test/Case/Model/Behavior/LinkableBehaviorTest.php index 97c6fd6..3007340 100644 --- a/Test/Case/Model/Behavior/LinkableBehaviorTest.php +++ b/Test/Case/Model/Behavior/LinkableBehaviorTest.php @@ -465,7 +465,6 @@ public function _testAliasedBelongsToWithSameModelAsHasMany() class TestModel extends CakeTestModel { - //public $useDbConfig = 'test'; public $recursive = 0; From b1b9b299a1e7700b5680db22bc90353b57e91887 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 15 Dec 2011 15:44:01 +0000 Subject: [PATCH 08/10] remove old format fixtures --- Test/Fixture/comment_fixture.php | 20 -------------------- Test/Fixture/generic_fixture.php | 18 ------------------ Test/Fixture/legacy_company_fixture.php | 17 ----------------- Test/Fixture/legacy_product_fixture.php | 18 ------------------ Test/Fixture/order_item_fixture.php | 15 --------------- Test/Fixture/post_fixture.php | 17 ----------------- Test/Fixture/posts_tag_fixture.php | 20 -------------------- Test/Fixture/profile_fixture.php | 19 ------------------- Test/Fixture/shipment_fixture.php | 18 ------------------ Test/Fixture/tag_fixture.php | 19 ------------------- Test/Fixture/user_fixture.php | 18 ------------------ 11 files changed, 199 deletions(-) delete mode 100644 Test/Fixture/comment_fixture.php delete mode 100644 Test/Fixture/generic_fixture.php delete mode 100644 Test/Fixture/legacy_company_fixture.php delete mode 100644 Test/Fixture/legacy_product_fixture.php delete mode 100644 Test/Fixture/order_item_fixture.php delete mode 100644 Test/Fixture/post_fixture.php delete mode 100644 Test/Fixture/posts_tag_fixture.php delete mode 100644 Test/Fixture/profile_fixture.php delete mode 100644 Test/Fixture/shipment_fixture.php delete mode 100644 Test/Fixture/tag_fixture.php delete mode 100644 Test/Fixture/user_fixture.php diff --git a/Test/Fixture/comment_fixture.php b/Test/Fixture/comment_fixture.php deleted file mode 100644 index ea17cd9..0000000 --- a/Test/Fixture/comment_fixture.php +++ /dev/null @@ -1,20 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'user_id' => array('type' => 'integer'), - 'body' => array('type' => 'string', 'length' => 255, 'null' => false) - ); - - var $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'), - array('id' => 4, 'user_id' => 3, 'body' => 'Text'), - array('id' => 5, 'user_id' => 4, 'body' => 'Text') - ); -} diff --git a/Test/Fixture/generic_fixture.php b/Test/Fixture/generic_fixture.php deleted file mode 100644 index 3d062a3..0000000 --- a/Test/Fixture/generic_fixture.php +++ /dev/null @@ -1,18 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'text' => array('type' => 'string', 'length' => 255, 'null' => false) - ); - - var $records = array( - array ('id' => 1, 'text' => ''), - array ('id' => 2, 'text' => ''), - array ('id' => 3, 'text' => ''), - array ('id' => 4, 'text' => '') - ); -} diff --git a/Test/Fixture/legacy_company_fixture.php b/Test/Fixture/legacy_company_fixture.php deleted file mode 100644 index d5cd598..0000000 --- a/Test/Fixture/legacy_company_fixture.php +++ /dev/null @@ -1,17 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'company_name' => array('type' => 'string', 'length' => 255, 'null' => false), - ); - - var $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/Test/Fixture/legacy_product_fixture.php b/Test/Fixture/legacy_product_fixture.php deleted file mode 100644 index 2207878..0000000 --- a/Test/Fixture/legacy_product_fixture.php +++ /dev/null @@ -1,18 +0,0 @@ - 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( - 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/Test/Fixture/order_item_fixture.php b/Test/Fixture/order_item_fixture.php deleted file mode 100644 index 2fcf97f..0000000 --- a/Test/Fixture/order_item_fixture.php +++ /dev/null @@ -1,15 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'active_shipment_id' => array('type' => 'integer'), - ); - - var $records = array( - array ('id' => 50, 'active_shipment_id' => 320) - ); -} diff --git a/Test/Fixture/post_fixture.php b/Test/Fixture/post_fixture.php deleted file mode 100644 index 19c132d..0000000 --- a/Test/Fixture/post_fixture.php +++ /dev/null @@ -1,17 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'title' => array('type' => 'string', 'length' => 255, 'null' => false), - 'user_id' => array('type' => 'integer'), - ); - - var $records = array( - array ('id' => 1, 'title' => 'Post 1', 'user_id' => 1), - array ('id' => 2, 'title' => 'Post 2', 'user_id' => 2) - ); -} diff --git a/Test/Fixture/posts_tag_fixture.php b/Test/Fixture/posts_tag_fixture.php deleted file mode 100644 index c041019..0000000 --- a/Test/Fixture/posts_tag_fixture.php +++ /dev/null @@ -1,20 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'post_id' => array('type' => 'integer'), - 'tag_id' => array('type' => 'integer'), - 'main' => array('type' => 'integer') - ); - - var $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), - array ('id' => 4, 'post_id' => 2, 'tag_id' => 4, 'main' => 0), - ); -} \ No newline at end of file diff --git a/Test/Fixture/profile_fixture.php b/Test/Fixture/profile_fixture.php deleted file mode 100644 index 8e50e98..0000000 --- a/Test/Fixture/profile_fixture.php +++ /dev/null @@ -1,19 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'user_id' => array('type' => 'integer'), - 'biography' => array('type' => 'string', 'length' => 255, 'null' => false) - ); - - var $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' => ''), - array ('id' => 4, 'user_id' => 4, 'biography' => '') - ); -} diff --git a/Test/Fixture/shipment_fixture.php b/Test/Fixture/shipment_fixture.php deleted file mode 100644 index ef2d9ef..0000000 --- a/Test/Fixture/shipment_fixture.php +++ /dev/null @@ -1,18 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'ship_date' => array('type' => 'date'), - 'order_item_id' => array('type' => 'integer') - ); - - var $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/Test/Fixture/tag_fixture.php b/Test/Fixture/tag_fixture.php deleted file mode 100644 index eca4f6d..0000000 --- a/Test/Fixture/tag_fixture.php +++ /dev/null @@ -1,19 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'name' => array('type' => 'string', 'length' => 255, 'null' => false), - 'parent_id' => array('type' => 'integer') - ); - - var $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), - array ('id' => 4, 'name' => 'Test III', 'parent_id' => null) - ); -} diff --git a/Test/Fixture/user_fixture.php b/Test/Fixture/user_fixture.php deleted file mode 100644 index 33eb994..0000000 --- a/Test/Fixture/user_fixture.php +++ /dev/null @@ -1,18 +0,0 @@ - array('type' => 'integer', 'key' => 'primary'), - 'username' => array('type' => 'string', 'length' => 255, 'null' => false) - ); - - var $records = array( - array('id' => 1, 'username' => 'CakePHP'), - array('id' => 2, 'username' => 'Zend'), - array('id' => 3, 'username' => 'Symfony'), - array('id' => 4, 'username' => 'CodeIgniter') - ); -} From fbfe43ab26c7efab42493d25a1333370406dd576 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Mon, 27 Feb 2012 04:28:57 -0800 Subject: [PATCH 09/10] Fixed the @fixme. Aliased models now have the Alias in fields --- Model/Behavior/LinkableBehavior.php | 24 +++++++++---------- .../Model/Behavior/LinkableBehaviorTest.php | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Model/Behavior/LinkableBehavior.php b/Model/Behavior/LinkableBehavior.php index 8be1ba5..d16c4d3 100644 --- a/Model/Behavior/LinkableBehavior.php +++ b/Model/Behavior/LinkableBehavior.php @@ -156,12 +156,7 @@ public function beforeFind(Model $Model, $query) { $options['fields'] = $db->fields($_Model); } else { $options['fields'] = $db->fields($_Model, null, $options['fields']); - } - - if (is_array($query['fields'])) - $query['fields'] = array_merge($query['fields'], $options['fields']); - else - $query['fields'] = array_merge($db->fields($Model), $options['fields']); + } } else if (!isset($options['fields']) || (isset($options['fields']) && !is_array($options['fields']))) { @@ -170,13 +165,16 @@ public function beforeFind(Model $Model, $query) { } else { $options['fields'] = $db->fields($_Model); } - - if (is_array($query['fields'])) { - $query['fields'] = array_merge($query['fields'], $options['fields']); - } 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']); - } + } + + 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']); + } 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']); } } diff --git a/Test/Case/Model/Behavior/LinkableBehaviorTest.php b/Test/Case/Model/Behavior/LinkableBehaviorTest.php index 3007340..220951d 100644 --- a/Test/Case/Model/Behavior/LinkableBehaviorTest.php +++ b/Test/Case/Model/Behavior/LinkableBehaviorTest.php @@ -225,7 +225,7 @@ public function testComplexAssociations() 'class' => 'Tag', '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' ) ) ) From 2d6f635c6037fa3e39f8b211a79248a65718edd0 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Sat, 3 Mar 2012 03:54:24 -0800 Subject: [PATCH 10/10] Fixed aliasing of join conditions when passing a 'class' param --- Model/Behavior/LinkableBehavior.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Model/Behavior/LinkableBehavior.php b/Model/Behavior/LinkableBehavior.php index d16c4d3..823be4e 100644 --- a/Model/Behavior/LinkableBehavior.php +++ b/Model/Behavior/LinkableBehavior.php @@ -106,6 +106,7 @@ public function beforeFind(Model $Model, $query) { } 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}"; } elseif ($type === 'hasAndBelongsToMany') { @@ -134,14 +135,16 @@ public function beforeFind(Model $Model, $query) { '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); + $modelKey = str_replace($_Model->alias, $options['alias'], $modelKey); $options['conditions'][] = "{$modelKey} = {$referenceKey}"; - } + } } - + if (empty($options['table'])) { $options['table'] = $_Model->table; }