Pagination with recursion while using loadModel - cakephp

In my "Reports" controller, which is just a dummy controller without any actual database, I'm trying to generate a paginated view of other models. For example, to generate paginated view of "Transactions" model I'm doing the following:
$this->loadModel('Transactions');
$this->Transactions->bindModel(array('belongsTo'=>array('Item'=>array('className'=>'Item'),'Member'=>array('className'=>'Member'))));
$results = $this->paginate('Transactions',null,array('recursive'=>1));
But this is not giving me associated data from Items and Members. If I do a
$this->Transactions->find('all',array('recursive'=>1))
I get the associated data, but not paginated. How will I get paginated view which includes the associated data too?

Two things: First, even when plural model names can work for some odd reason, the convention is that model names are singular, like $this->loadModel('Transaction');. See the manual on naming conventions.
Second, forget about recursive and go for the Containable behavior. Frankly, it's so useful that I wonder why it isn't the default process (perhaps because Containable got created when the framework was very mature). Matt has a good book explaining why Containable is good (download it, really, it's almost mandatory :D ). But to help even more, I'm going to tell you exactly how you solve your issue with containable:
1) Define the associations in the models, like:
In Transaction model:
var $belongsTo = array(
'Item' => array(
'className' => 'Item',
'foreignKey' => 'item_id',
)
);
In Item model:
var $hasMany = array(
'Transaction' => array(
'className' => 'Transaction',
'foreignKey' => 'item_id',
'dependent' => true,
'exclusive' => true,
)
);
Do the same for the Member model.
2) Create an app_model.php file in /app/ with this code:
(The $actsAs variable here within the AppModel class tells all models to use Containable)
<?php
class AppModel extends Model {
var $recursive = -1;
var $actsAs = array('Containable');
}
?>
3) In the Reports Controller, change the code to something like this:
(The contain parameter is an array of all the associated models that you want to include. You can include only one assoc. model, or all, or whatever you want).
$this->loadModel('Transaction');
$this->paginate = array('Transaction' => array('contain' => array('Item', 'Member')));
$results = $this->paginate('Transaction');
And that's it!

Related

CakePHP Setting recursive for select joins

I create a search on models like this:
$options = array(
'conditions' => array(
'CompletedSurvey.' . $this->CompletedSurvey->primaryKey => $id
),
'recursive' => 5
);
$survey = $this->CompletedSurvey->find('first', $options);
The way I have my models set up, this will return five models (do to their various joins) each recursed up to 5 times (if available). The problem is that I only want one of these models to be recursed X5. The others don't need to be.
Is there a way to tell the find function which tables to recurse and at what level to recurse them to? So, telling cake which models to recurse and at what level for each one?
Please read the CakePHP cookbook regarding the recursive property on models, as it does not work the way you are thinking. http://book.cakephp.org/2.0/en/models/model-attributes.html#recursive
What you're looking for is the containable behavior, where you can specify exactly which models to return. Please see the cookbook on how to use containable. http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Well, this is not exactly recursive, but the only way to access deep relations and avoid to use recursive on a main model is to use containable behavior:
$options = array(
'conditions' => array(
'CompletedSurvey.' . $this->CompletedSurvey->primaryKey => $id
),
'contain' => array(
'SomeModel.SomeOtherModel.AnotherModel.AnotherModel'
)
);
$survey = $this->CompletedSurvey->find('first', $options);
And don't forget to set containable behavior for CompletedSurvey model!
class CompletedSurvey extends AppModel {
public $actsAs = array('Containable');
}

CakePHP conditions clause on associations when using Model::find()

I just confused because of a find() result. This is my configurations:
First, users can have different User.role values: student, admin, and some others.
// Book
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id'
'conditions' => array('User.role' => 'student')
);
);
When I chain Models like $this->Book->Student->find('list'); I was expecting to get only users whose role are 'student', but instead, it gets all users. What is going on here, what is conditions for on association definition, where can it and cannot be used. Any lead would help, thanks.
PS: I am aware that I could put conditions on find(), that's not the issue
There is a difference between associated data and accessing an associated model object. If you access $this->Book->Student you're accessing the Student model and work in it's scope. The conditions in the defined associations work only in the context of the accessed object.
So if you do a find on the Book and list the students for that book:
$this->Book->find('first', array('contain' => array('Student'));
Your code will work correctly. It will find the book plus the first user who has the role stundent. BUT your association is wrong then: It should be hasMany. because why would you filter a book by role if the book just belongsTo one student?
If you want to filter users by their role you can implement a query param that is checked in beforeFind(), pseudocode: if isset roleFilter then add contention to filter by that role from roleFilter.
Or, if you don't need to paginate just create a getStudents() method in the user model that will return a find('list') that has the conditions.
Or Student extends User and put the filter in the beforeFind() and use that model instead of the User model in your Book association.
If you want to filter on model level or per model I think the last one is a good option. Don't forget to set $useTable and $name or the inherited model will cause problems.
you have miss , inside your model.
try this:
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id', //<------ miss
'conditions' => array('User.role' => 'student')
);
);
Yoi can debug your query to check what is the real query that you make.
Personally I have never use this approach, I prefer to use foreign key with another table for examples Rolesand User.role_id.
Is better for me to use this approach to have more flexibility inside your app.
After I prefer to use a conditions where inside controller to check well the query, because in your way every query you search always for student role not for the other and can be a problem for the rest of role, because inside controller you see a find without conditions but it doesn't take right value because in your model there is a particular conditions.
For me the good way is to create a new table, use foreign key and where conditions inside action of the controller to view well what are you doing.
For default all relations are "left join", you must set the parameter "type" with "inner" value
// Book
public $belongsTo = array(
'Student' => array(
'className' => 'User',
'foreignKey' => 'student_id'
'conditions' => array('Student.role' => 'student'), // <-- Fix That (field name)
'type' => 'inner', // <-- add that
);
);

Can a virtualfield be based upon linked data in cakephp?

Is it possible for a virtualFields var to be the sum of a field from a linked table?
For example, in, say, an Invoice model, could you have
public $virtualFields = array(
'invoiceNett' => 'SUM(InvoiceLine.nett)'
);
but obviously only SUMming the lines that belong to that invoice?
Thanks.
== Using CakePHP 2.0
You could use the afterFind callback to get the sum. This avoids storing the calculated values​​, which should be avoided when possible.
function afterFind($results)
{
foreach($results as &$result)
{
/*
Use something like:
$this->InvoiceLine->find('all', array('fields' => array('SUM(InvoiceLine.nett) as total'),
'conditions' => array('invoice_id' => $result['Invoice']['id'])));
*/
}
unset($result);
}
As far as I know, the best way to do that would be to have an actual total field, and update it anytime the data is saved (likely with a afterSave callback method).
So - anytime an InvoiceLine is saved, you run some code to update it's associated Invoice with a new total.
//InvoiceLine model
public function beforeSave() {
//code to update Invoice's "total" field
}
Theoretically, yes, if that linked table is an associate that is joined (belongsTo and hasOne).
However this would be a poor idea because if you decide not to include that table you would generate a SQL error.
You'd be better off having a separate function grab the data or creating a virtual field that was a nested SQL query.
Defining your virtual field in the respective Model would make more sense.
If not done so, you will be breaking the MVC pattern.
You can use that virtual field from other related models.
If you don't want using them in all related models, you can always use the field
attribute whilst defining relations.
public $hasMany = array(
'IwantVirtualField' => array(
'className' => 'MyModel',
...
)
);
In a model where you don't want virtual field
public $belongsTo = array(
'IwantVirtualField' => array(
'className' => 'MyModel1',
'fields' => array('MyModel1.id', 'MyModel1.name')
...
)
);

How to get the model that initiates a saveAll call in the related model

Is it possible to get the Model that is trying to save data to a related model in a saveAll transaction. If forexample, User hasMany Goal, and I make a call;
$this->User->saveAll($data);
Can I get the Model User in the Goal Model class using some predefined attritbute, method or mechanism?
Thanks for your help in advance,
Roland.
Edit:
Assume a hypothetical situation where a user has pictures, with a Picture model and Posts( blogs) with a corresponding model. These both can be commented upon. So rather than creating a separate comment model for each of these models, I create one central Comment model. The association might look like this;
//In the Picture model
var $hasMany = array(
`` 'Comment' => array(
'className' => 'Comment',
'foreignKey' => 'picture_id',
'conditions' => array('Comment.objectType' => 'picture')
)`
);
The association for the Post model will be similar, the only difference being;
'conditions' => array('Comment.objectType' => 'post')
With this structure, if I were to query any of these Models, their corresponding comments will be retrieved from the DB table using the objectType field.
If I were to do something like;
$this->Picture->saveAll($data);
, or
$this->Post->saveAll($data);
with the $data array well structured and containing a Comments part to be saved, at some point in time during the saving transaction, data will be saved to the comments table through the Comment model.
What I want is to be able to know that it is the Picture model trying to save comment in the Comment model's beforeSave method, i.e;
//In the Comment mode
beforeSave() {
//Post model is trying to save a comment here
}
With CakePHP, if you associate your models using the hasMany, while you are in the controller you can access that model.
In UserModel
var $hasMany = array('Goal');
In GoalModel
function myCrazyFunc() {
//crazy stuff happens here
}
In UsersController
$this->Goal->myCrazyFunc();
//more stuff
$this->User->saveAll($data);

Cakephp complex hasMany relationship query

I'm very new to CakePHP. I want to do a query in my database like this
SELECT m.id, l.*, lp.picture_path
FROM member m INNER JOIN listing l ON m.member_id = l.member_id
INNER JOIN listingPicture lp ON l.listing_id = lp.listing_id
WHERE lp.picture_default='1'
I have 3 models in my cakephp : Member, Listing, and ListingPicture each with the following relationship
Member hasMany Listing
Listing hasMany ListingPicture
Listing belongsTo Member
ListingPicture belongsTo Listing
From my Member controller how do I execute the query above ?
I've tried
$this->Member->Listing->find("all")
... which works well but when I added a conditions like this:
$this->Member->Listing->find('all', array(
'conditions' => array('ListingPicture.picture_default'=>'1')));
... I get an error.
Because I'm new to CakePHP, I don't know how to see the error.
Can anyone advise me how I can perform this query?
Make sure to set your model as:
public $actsAs = array('Containable');
Then use CakePHP's containable behavior to include only the associated data you want, with specified fields and conditions.
$this->Member->Listing->find('all', array(
'fields' => array('*'),
'contain' => array(
'Member' => array(
'fields' => array('id')
)
'ListingPicture' => array(
'conditions' => array('ListingPicture.picture_default' => '1')
'fields' => array('picture_path')
)
)
));
To follow with the MVC concept, it's suggested to keep your finds in a Model as opposed to a controller. It's not required, but - it makes it much easier to know exactly where all finds are, and keeps with the "Fat model / Skinny controler" mantra. In this case, it'd be something like:
//in the Member Controller
$listings = $this->Member->Listing->getListings();
//in the Listing Model
function getListings() {
$listings = $this->find('all', ...
return $listings;
}
You should give the condition at the time of binding ListPicture to Listing.
$this->Member->Listing->bindModel(array(
'ListPicture'=>array(
'condtions'=>array('ListingPicture.picture_default'=>'1')
))
);

Resources