I have model relations like this:
Project hasMany SubProject hasMany Item
I want to set up a containable array so that I can find all of the Items which belong to a particular Project, and paginate the results. So, in my ItemsController I have:
public $paginate = array(
'Item' => array(
'limit' => 10,
'order' => array('
'Item.create_time' => 'desc'
),
'contain' => array(
'SubProject' => array(
'Project'
)
)
)
);
Somewhere, obviously, I need to place a condition like "SubProject.project_id = $pid", but nothing I've tried yields the correct results. The best I can manage is results that look like this:
Array
(
[0] => Array
(
[Item] => Array
(
[id] => 13
[file_name] => foo.tar.gz
.... other keys ...
[create_time] => 2013-01-23 14:59:49
[subProject_id] => 4
)
[SubProject] => Array
(
[id] => 4
[name] => foo
[project_id] => 2
..... other keys ....
[Project] => Array
(
)
)
)
[1] => Array
.....
Edit: It is quite correctly omitting the Project record that doesn't match; I want to skip any Item records with out a matching Project record.
It has crossed my mind to manually specify my joins, but I feel like that shouldn't be necessary.
It seems like this should be obvious, but alas, the solution escapes me.
I did eventually solve this problem, so I thought I'd explain what I did in the hope it might help someone else.
After reading this blog post by Mark Story (which is from the days of 1.2 but still relevant) I decided that the thing to do was create a custom find type in my Item model that binds the Project model directly. This gives a first-level association that Containable can filter correctly.
So, in the Items model, I have something like the following (see the documentation on custom find types).
public $findMethods = array('byProject' => true);
public function _findByProject($state, $query, $results=array()) {
if ($state == 'before') {
$this->bindModel(array(
'hasOne' => array(
'Project' => array(
'foreignKey' => false,
'conditions' => array('Project.id = SubProject.project_id')
)
)
));
return $query;
}
return $results;
}
Note that setting foreignKey to false is necessary to prevent CakePHP from trying to automatically use a non-existent database key. In the ItemsController, the pagination options now look like this:
public $paginate = array(
'Item' => array(
'findType' => 'byProject',
'limit' => 10,
'order' => array(
'Item.create_time' => 'desc'
),
'contain' => array(
'SubProject',
'Project'
),
'conditions' => array('Project.id' = $pid)
),
);
...where $pid is the id of the project to display. Some minor tweaks in the view code to accomodate the slightly different results array structure, and I was all set.
EDIT ===============
public $paginate = array(
'limit' => 10,
'order' => 'Item.create_time DESC', //'order' => array(''Item.create_time' => 'desc'),
'contain' => array(
'SubProject' => array(
'Project' => array(
'conditions' => array(
'id' => $project_id // Passed parameter
)
)
)
)
);
=================================================
Have you tried using conditions as in the following? Also, I did not write the 'order' section of the code the way you have it.
public $paginate = array(
'Item' => array(
'limit' => 10,
'order' => 'Item.create_time DESC', //'order' => array(''Item.create_time' => 'desc'),
'contain' => array(
'SubProject' => array(
'Project' => array(
'conditions' => array(
'id' => $project_id // Passed parameter
)
)
)
)
)
);
Related
I have a model Ranking which holds a contact_id and belongsTo Model Contact.
Model Contact has a costumer_id and belongsTo Model Costumer.
And hasMany Rankings.
There is also a Model Product which hasMany Ranking.
On a statistics page I select
$this->Product->recursive = 1;
$this->set('products', $this->Paginator->paginate())
;
and I get the array of
array(
'Product' => array(
'id' => '69',
),
'Ranking' => array(
(int) 0 => array(
'id' => '29',
'contact_id' => '9',
'product_id' => '69',
'ranking' => '9',
),
I would like to bind now the Contact and Costumer to the ranking based on the contact_id.
Is this manually possible via bindModel?
If yes, how can I do that?
I tried to set $this->Product->recursive = 1; to 2 and 3, but that select so many other things which I would need to clear with unbindModel... So I hope there is a smarter way of bind those model to get to the data...?
What you basically want to use is containable behavior. With this behavior you are able to filter and limit model find operations. You have the possibility to add this behavior on model level or at the controller to avoid side effects if the application has already grown to a complicated level.
Example from Cake-Book:
// Activate Containable Behavior on the fly in a controller
$this->User->Behaviors->load('Containable');
$this->User->contain();
$this->User->find('all', array(
'contain' => array(
'Profile',
'Account' => array(
'AccountSummary'
),
'Post' => array(
'PostAttachment' => array(
'fields' => array('id', 'name'),
'PostAttachmentHistory' => array(
'HistoryNotes' => array(
'fields' => array('id', 'note')
)
)
),
'Tag' => array(
'conditions' => array('Tag.name LIKE' => '%happy%')
)
)
)
));
Hope this gives you a push into the right direction.
using find it will get me the right data with this:
$this->set('products', $this->Product->find('all', array(
'contain' => array(
'Ranking' => array(
'Contact' => array(
'foreignKey' => 'contact_id',
'Customer' => array(
'foreignKey' => 'customer_id',
)
)
)
)
)));
When using the Paginator it looks like
$this->Paginator->settings['contain'] = array(
'Ranking' => array(
'Contact' => array(
'foreignKey' => 'contact_id',
'Customer' => array(
'foreignKey' => 'customer_id',
)
)
)
);
$this->Product->Behaviors->load('Containable');
$this->set('products', $this->Paginator->paginate());
Thanks so much!!
I am working on a cakephp 2.x .. I have two tables in my database both have the userid I am using bindModel.. my query is working fine ... I just have one problem .. I want to add the condition in where clause that
**where userid = $userid**
function getMessages($userid){
$this->bindModel(array(
'belongsTo' => array(
'Contact' => array(
'className' => 'Contact',
'foreignKey' => false,
'conditions' => array(
'Message.user_id = Contact.user_id',
'AND' =>
array(
array('OR' => array(
array('Message.mobileNo = Contact.mobileNo'),
array('Message.mobileNo = Contact.workNo'),
)),
)
),
'type' => 'LEFT',
)
)
), false);
return $this->find('all', array('conditions' => array(),
'fields' => array('Message.mobileNo'
),
'group' => 'Message.mobileNo',
'limit' => 6));
}
I am getting user id in parameter ... so I want to add the condition that get the following result where
message.userid and contact.userid = $userid ...
Just split the conditions in two lines like:
'conditions' => array(
'Contact.user_id' => $userid,
'Message.user_id = Contact.user_id',
[...]
)
But the approach that makes more sense is to leave the bind as is - after all the bind is generally between message users and contact users with the same id - and add the condition for the specific case in your find('all') with 'Message.user_id' => $userid.
I have the following Code to get a special model related to another model related to actual object: (I want the Entry-model - in the Rubric-model-view)
$this->paginate = array(
'Entrieslocation' => array(
'conditions' => array('Entrieslocation.rubric_id' => $this->Rubric->id, 'Entrieslocation.subrubric_id' => '0'),
'joins' => array(
array(
'alias' => 'Entry',
'table' => 'entries',
'type' => 'INNER',
'conditions' => 'Entry.id = Entrieslocation.entry_id'
)
)
)
);
Then I thought I could get it by something like this:
$this->set('entries', $this->paginate('Entry'));
But the variable is empty, how can I access the Entries inside the Pagination in the view?
The Query generated by the code above is correct, I checked it by entering the query into phpmyadmin. So how can I access the result (only the Entries)?
Thanks for your help :)
First of all you must define $paginate variable below controller class. For example
class UsersController extends AppController {
public $paginate = array();
}
Then in your action follow following patter to get the paginated result.
$this->paginate = array(
'recursive' => -1,
'limit' => 10,
'conditions' => array('Your conditions'),
'fields' => array('Entry.*, Entrieslocations.*'),
'joins' => array(
array(
'table' => 'Entrieslocations',
'alias' => 'Entrieslocation',
'type' => 'inner',
'conditions' => 'Entry.id = Entrieslocation.entry_id'
),
),
'order' => array(
'Entry.id' => 'desc'
)
);
$entries = $this->paginate('Entry');
To pass result set to the view use following:
$this->set('entries', $entries);
I am trying to get an array structure of a database and a few of its fields from my controller.
The fields I want are: Document.id, Document.name, Document.submission_date, and the Requester.name which is joined with: Requester.id = Document.requester_id
In my Controller:
Method A:
$documents = $this->Document->find('list', array('fields' => array('Document.name', 'Requester.name', 'Document.submission_date')));
Can't seem to find 'Requester.name'
Method B:
$documents = $this->Document->find('list', array('fields' => array('Document.name', 'Requester.name', 'Document.submission_date'), 'recursive' => 0));
Gives me:
array(
'2012-08-17' => array(
'Document_A' => 'Requester_Z'
),
'2012-08-05' => array(
'Document_B' => 'Requester_Y'
),
'2012-07-09' => array(
'Document_C' => 'Requester_X'
)
)
But I need it to be in format:
array(
(int) 0 => array(
'id' => '16'
'submission_date' => '2012-08-17'
'name' => 'Document_A',
'requester_name' => 'Requester_Z'
),
(int) 1 => array(
'id' => '41'
'submission_date' => '2012-08-05'
'name' => 'Document_B',
'requester_name' => 'Requester_Y'
),
(int) 2 => array(
'id' => '213'
'submission_date' => '2012-07-09'
'name' => 'Document_C',
'requester_name' => 'Requester_X'
),
)
I can't seem to figure it out after going through the 2.0 CakeBook and on StackOverflow...
Any help would be appreciated? Sorry - I'm still a n00b with CakePHP (but REALLY loving it so far!) Thanks!
You need to do a find('all') instead of a find('list') and you can use the fields key like you already are to limit the amount of info that's returned.
It won't come back exactly as you need the data, each field will be in a key of the model it belongs to - if you really need it in that format you can get away with using a virtual field, a custom find, or an afterFind callback to modify the data.
Find list is generally for an id => label formatted array.
You can try this:
$documents = $this
->Document-
>find('all',
array(
'fields' => array('Document.name', 'Requester.name', 'Document.submission_date'),
'joins' => array(
'table' => 'requesters',
'alias' => 'Requester',
'type' => 'left',
'conditions' => array('Requester.id = Document.requester_id')
)
)
);
I have this NPO table which has one template. A template in turn has a theme. I want to retrieve which theme was selected by Npo.
I have following relation setup:
NPO - template on npo.id = template.npoid Template - theme on theme.id
= template.template_theme_id
And I am using:
$this->Npo->bindmodel(array(
'hasOne' => array(
'NpoTemplate' => array(
'className' => 'NpoTemplate'
)
)
), false);
$this->NpoTemplate->bindmodel(array(
'hasOne' => array(
'TemplateTheme' => array(
'className' => 'TemplateTheme',
'fields' => 'TemplateTheme.html'
)
)
), false);
$arrUserSite = $this->Npo->find('first', array(
'conditions'=> array(
'Npo.address' => $user
)
));
But it does not fetch anything from TemplateTheme. Instead it writes a seperate query for this table and does not consider it in the join.
I have set recursive level to 3
Please help. I don't really understand how the cake association works.
Regards
Himanshu Sharma
Try setting up your relationships in a single bindModel() call.
$this->Npo->bindmodel(array(
'hasOne' => array(
'NpoTemplate' => array(
'foreignKey' => false,
'conditions' => array(
'Npo.id = NpoTemplate.npoid'
)
),
'TemplateTheme' => array(
'foreignKey' => false,
'conditions' => array(
'NpoTemplate.template_theme_id = TemplateTheme.id'
)
)
)
));
$arrUserSite = $this->Npo->find('first', array(
'conditions' => array(
'Npo.address' => $user
)
));
You'll probably need to tweak the bindModel setup because I'm not 100% sure of your database structure. Good luck.