CakePHP - multiple paginated datasets on same page from same model - cakephp

I have an admin page that pulls data from my ExpenseClaim model. I want to have 3 different paginated tables, each table displaying data based on a different condition (a status). This is what I have (which doesn't work - nothing is paginated in the view) in my controller:
// Pending
$this->set('claims', $this->paginate('ExpenseClaim', array('ExpenseClaim.claim_status_id' => '2')));
// Get approved
$this->set('approvedClaims', $this->paginate('ExpenseClaim', array('ExpenseClaim.claim_status_id' => '3')));
// Get Declined
$this->set('declinedClaims', $this->paginate('ExpenseClaim', array('ExpenseClaim.claim_status_id' => '4')));
Does anyone know how I can achieve this, I've do a fair amount of searching but only found things relating to different models or hacks using jquery plugins. Surely this can be done in cake alone?
Thanks in advance

Did you wrote the above code inside ExpenseClaimsController? But in case if you in another controller, make sure you have a paginate variable as the following
public $paginate = array(
'ExpenseClaim' => array(
.......
)
);

Related

Associating multiple models in CakePHP

I am currently working on an application in CakePHP to display (but not modify) the contents of a legacy database. I am not able to change the database structure in any way.
There are six tables I am working with and the relationships are outlined in this awful paint diagram:
I have currently defined the models as follows:
http://pastebin.com/4aEaSnQp
(I decided to put it in a PasteBin as it is a bit of a wall of text, I am happy to edit it into the post if preferred).
What I would like to do is use one controller to pass relevant linked data from my controller to the view from essentially all of these tables. However, even if I set recursion to 2 the links only seem to go so far - for example if I try to find data from the 'Ctit' table with code like this:
$this->set('contracts',$this->Ctit->find('all',
array(
'recursion' => 2,
'conditions' => array('Title.TITNO' => $id),
'fields' => array('Contract.CONNO','Title.TITLE','Contract.CONDATE','Territory.DESCRIPTION')
)
));
The resulting query does not include the "Territory" table and so throws back an error.
What am I doing wrong here? Is it possible to retrieve the data in this way or might I have to run multiple finds or write my own queries (something I am hoping to avoid)?
Thanks in advance,
Kez
So... condensing the comments.
Basically it's a Containable problem. To get everything related to a single model (meaning, the 5 other models), a way to do it would be like
$this->Ctit->find('all', array(
'contain' => array(
'Royalty',
'Title',
'Contract' => array('Publisher', 'Territory')
)
You can also add options to that array, like any other query, for example order, fields, conditions, etc.
Example:
$this->Ctit->find('all', array(
'contain' => array(
'Royalty' => array('order' => 'id DESC'),
'Title' => array('fields' => array('id', 'name'),
'Contract' => array('Publisher',
'Territory' => array('conditions' => array('name' => 'terr'))
)
One thing to have in mind is to always use
public $actsAs = array('Containable');
(mind that s in actsAs, some people have had trouble because of that) otherwise your models won't behave containabl-y and you'd think this whole thing doesn't work.
Containable behaviour is useful for this, because you don't have to do a lot of finds, but keep in mind that cake does a lot of queries behind scenes to use containable. If you want this to be just one big query, use joins (found it!)
Also, be aware that this query might get big real quick. You are basically asking for all records with all 5 associations, when you reach a memory limit notice you are going to remember me. I doubt you want to show all data for every record on one view, so reconsider, maybe a simple paginate first and then a single view for each single record (in that case, the find will be a find('first') and that's lot better than find('all').

setSource not taken into account by Cakephp tree behavior

Tree behavior and associations is working well in my application.
I'm trying to switch tree table on runtime, which works well with following code in beforeFind method:
$this->setSource($table);
However, when I'm reordoning the tree using the recover method, the table name is not taken into account for all queries: I'm getting SHOW COLUMN queries on the new table, and SELECT queries on the default one. I tried to disable and clear cache of my application without success.
I'm also changing the table of associated Models, but problem arrises without it too.
Any piece of advice will be appreciated.
Edit:
Here is the model used:
https://github.com/croogo/croogo/blob/1.3/models/taxonomy.php
I disabled (permanently, not at runtime) the Cache behavior.
I traced the problem into recover method, the bindModel does not take into account the useTable.
$Model->bindModel(array('belongsTo' => array('VerifyParent' => array(
'className' => $Model->name,
'foreignKey' => $parent,
'fields' => array($Model->primaryKey, $left, $right, $parent),
))));
I tried without success:
$Model->VerifyParent->useTable = $Model->useTable;
I opened a lighthouse ticket:
https://cakephp.lighthouseapp.com/projects/42648/tickets/3820-cannot-change-table-name-using-treebehaviorrecover-in-shell
belongsTo associations cannot define custom tables for the linked model. You may need to seed ClassRegistry with a properly configured model with the VerifyParent key.
Problem can be fixed by modifying the Tree behavior:
$Model->VerifyParent->setSource($Model->useTable)
This a bug accepted and referenced here:
https://cakephp.lighthouseapp.com/projects/42648/tickets/3820-cannot-change-table-name-using-treebehaviorrecover-in-shell
However, not sure it will be fixed in cakephp source as it is very specific.

elements or view extensions or something else? reference app?

In cakephp [2.2] I have baked everything and my "people" view is quite busy with relations and phones and addresses and other related data. I do want all of that information visible in the people view, though not quite in the baked layout.
How should I handle those portions of the related data? I'm not sure if I should use elements or extended views or plugins or what, I'm kinda new to this and the documentation wasn't clear to me (at my level) which should be used when. The baked code seemed to be a monolithic approach, so I didn't get much help looking there.
Once the user chooses to edit a phone number (for instance) from the listing on the person view, it takes them to the phone edit view and then returns them to the phone listing (index view) and not the person view that they were on. How do I get them back to the person view instead?
The blog example they provide is nice, but is there a "reference" application somewhere for cakephp that demonstrates best practices on a wide variety of their features? I couldn't find one, or anything more than just a simple app example.
Thanks, I appreciate the guidance.
This is a rather broad question, but I'm going to try and answer it. I'm not sure how advanced you're programming knowledge is, so forgive me if I'm rehashing things you already know. First, this article was a great help when I started to use the framework for the first time as it explains what code should go where and why. It's the closest I've seen to a "reference application", which would actually be a great learning tool. You could try and have a look at some of the higher profile Cake applications, like Croogo (a Cake-based CMS). But the codebase is bound to be a little bit complex.
Personally I would use elements when you want to actually reuse them in different views. The problem however, is feeding the element its data. There's a method called requestAction, but even the manual states that this should be used with moderation and in combination with caching. The problem is that using a lot of requestAction calls in different elements litters your Controllers with methods and doesn't adhere to the "Skinny Controllers, Fat Models" mantra.
I would put most of the related data calls in their respective Models and call those Model methods from the Controller and feed them to the View. So let's say you want the 10 latest PhoneNumbers and related Users.
You would have a method in your PhoneNumber model which returns an array of users and their phonenumbers. Use the Containable behaviour to limit the number of related models which are returned. The code below is an example, so the practical implementation might vary:
public function getRecentPhoneNumbers($limit=10) {
$phoneNumbers = array();
$phoneNumbers = $this->find('all', array(
'limit' => $limit,
'contain' => array('User'),
'order' => 'PhoneNumber.id DESC'
));
return $phoneNumbers;
}
If the PhoneNumber and User model are properly related you would be able to call getRecentPhoneNumbers() from the User model:
$this->PhoneNumber->getRecentPhoneNumbers(10)
Or from the Users Controller:
$this->User->PhoneNumber->getRecentPhoneNumbers(10)
Say you have an element which shows a list of those 10 numbers and it accepts a variable called $recentPhonenumbers, you then set the variable in the relevant UsersController method with the returned array from the getRecentPhoneNumbers call:
$this->set('recentPhonenumbers', $this->User->PhoneNumber->getRecentPhoneNumbers(10));
This will make it available to the View that contains the element.
The extended views are relatively new (from Cake 2.1 and onwards) and I haven't used them, but seem a great way to create conditional markup.
As for the second question, redirecting the user to the person view, rather than the index view. This is a matter of adjusting the redirect (see the manual for more details) in the edit() method of the Controller. Standard baked edit() methods accept an $id parameter you can use this to redirect to the view() (which probably also accepts an $id paramater).
So the redirect probably looks something like this:
$this->redirect(array('controller' => 'users', 'action' => 'index'));
Change it to:
$this->redirect(array('controller' => 'users', 'action' => 'view', $id));

How can I minimize the 'contain' queries in CakePHP?

I have three models, Users, Comments and Pages.
Users has many Comments, and Comments belong to Pages.
All models use the containable behavior, and default to recursive -1.
If I call a find() query on Comments, with the contain request including the Page model's field, this correctly returns the results using a single query, automagically joining the Page table to the user.
If I call a similar query from the User model (containing Comment and Comment.Page), the result is a query to source the Comments, followed by a query per comment to source the relevant Page.
Is there a way to configure the models to maintain the JOIN optimisation? I assumed the belongsTo declaration on the related model (Comments) would follow through to the host model (Users).
UPDATE
I should clarify, my question used a simplified version of my actual case study. Although the minimal solution I require would include this initial Model hasMany Model belongsTo Model structure, I am also looking for the solution at one or more additional belongsTo Models down the chain (which, I though, would automagically use LEFT JOINs, as this would be feasible).
Hmm that's interesting. That's a sort of optimization that should be implemented in the core :)
At any rate, I think you could get the same results (perhaps formatted differently) by building the query a little differently:
$this->User->Comment->find('all', array(
'conditions' => array(
'Comment.user_id' => $userId
),
'contain' => array(
'User',
'Page'
)
));
By searching from the Comment model, it should use two left joins to join the data since they are both 1:1 relationships. Note: The results array may look a little different than from when you search from the User model.
So are you asking if there is an easier way to just contain all your queries? If you want to contain everything within the current controller. You could do the contain in the beforeFilter() callback and it would apply to all your queries within that controller.
I am not quite sure if I understand your question, but I think you have a problem with the many sql-calls for the Comment -> Page linkage? If that is correct, then
try linkable behaviour which reduces sql calls and works almost as contain does
or if its pretty much the same data you want, then create a function in a specific model from where you are happy with hte sql calls (for example the Comment-Model) and call it from the user model by $this->Comment->myFindFct($params);
hope that helps
EDIT: one thing that comes to my mind. You were able to change the join type in the association array to inner, which made cake to single call the associated model as well
I find a good way to do this is to create a custom find method.
As a for instance I'd create a method inside your User model say called _findUserComments(). You'd then do all the joins, contains, etc.. inside this method. Then in your controllers, wherever you need to get all of your user's comments you would call it thusly:
$this->User->find('UserComments', array(
"conditions" => array(
'User.id' => $userId
)
));
I hope this helps.
If model definition like bellow:
Comment model belongs to Page and User.
Page belongs to User and has many Comment.
User has many Page and Comment
code bellow will return one joined query:
$this->loadModel('Comment');
$this->Comment->Behaviors->attach('Containable');
$queryResult = $this->Comment->find('all', array(
'contain' => array(
'User',
'Page'
)
));
The code bellow will return two query. Page and User joined into one query and all comment in another query
$this->loadModel('Page');
$this->Page->Behaviors->attach('Containable');
$queryResult = $this->Page->find('all', array(
'contain' => array(
'User',
'Comment'
)
));
and also bellow code will return three query, one for each model:
$this->loadModel('User');
$this->User->Behaviors->attach('Containable');
$queryResult = $this->User->find('all', array(
'contain' => array(
'Page',
'Comment'
)
));

cakePHP model associations lost after model imported into controller

Due to a client's desire to have URLs that defy Cake logic I have decided to use the pages_controller.php copied from cake/libs to organize my app.
Most of the site's functionality occurs from one url making calls with ajax to different controllers so I chose Pages as the home base.
Pages has no model but I still need to access some of the relevant models so I import them.
I have tried all three methods:
$this->loadModel('Inventories');
----
$Inventories =& ClassRegistry::init('Inventories');
----
App::import('Controller', 'Inventories');
$Inventories = new InventoriesController;
The Inventories Model seems to load fine with each but when I find some records:
$(...)->find("all", array(
'conditions' => array('id' => '1'),
'recursive'=>2)
);
I only get results as if recursive had been set to -1 or as if there were no associated models.
This happens no matter what I set recursive to.
When I load the model in another controller I get the appropriate recursive response.
Any ideas how i can get full access to a model from pages_controller.php?
A messy but instant solution would be to simply populate the $uses array in your controller:
var $uses = array('Inventories', 'User'); // or whatever models need to be loaded
This is considered poor practice and could quickly spiral out of control, however. Mostly handy for quick testing.
You could place the functionality in appropriately named controllers, and then route your client's desired URLs to the corresponding controller / action:
http://book.cakephp.org/view/945/Routes-Configuration
A little-known controller method (it's not in the cookbook, only the API) called setAction() might be the solution for you. See:
http://api13.cakephp.org/class/controller#method-ControllersetAction
also in Cake 2.0
http://api20.cakephp.org/class/controller#method-ControllersetAction

Resources