I have been banging my head on the wall over this. I have a model Sku that belongs to model Purchase. My AppModel has $actAs=array('Containable') and $recursive=-1
Inside SkuController, when I do $this->Sku->find('all', array('contain' => 'Purchase')); I don't get Purchase. I have searched many old questions here and elsewhere on Internet but just can't seem to resolve this. To check if Containable behavior is being loaded, I edited ContainableBehavior.php in lib\Cake\Model\Behavior to make it an invalid php file but that didn't produce any errors. What the heck is wrong!!
Here's the SQL from debug:
SELECT Sku.id, Sku.purchase_id, Sku.item_id, Sku.upc,
Sku.quantity_avail, Sku.per_unit_price_amt,
Sku.do_not_delete, Sku.created, Sku.modified,
(concat('SK',lpad(Sku.id,8,'0'))) AS Sku__idFormatted FROM
sellble.skus AS Sku WHERE 1 = 1 ORDER BY Sku.id desc
CakePHP ver: 2.4.4
Not sure if this is different across versions but I have always specified the contain within an array and that works fine for me.
$this->Sku->find('all', array('contain' => array('Purchase')));
Or for mapping only the fields or conditions you want:
$this->Sku->find('all',
array('contain' => array(
'Purchase' => array(
'fields' => Purchase.name
'conditions' => array(
Purchase.name = 'somename'
)
)
)
)
);
Related
Tables
User(id)
Profile(id, *user_id*, type)
Attribute(id, *attribute_name_id*, value)
AttributeName(id, name)
ProfileAttribute(id, *profile_id*, *attribute_id*)
Relationships
The relationships are set up correctly (and go both ways, hasMany/belongsTo).
User hasMany Profile
Profile hasMany ProfileAttribute
Attribute hasMany ProfileAttribute
(could be written Profile hasMany Attribute through ProfileAttribute)
AttributeName hasMany Attribute
Goal
For a specified User id, with a find() in the User model, I only want the following fields, laid out as such:
$results[Profile.type][AttributeName.name][Attribute.value]
Is it even possible to retrieve results arranged like this? I've been playing around with Find and Containable for hours, but, first time trying to do anything complicated like this with Cake, I can't get the hang of it.
Thanks!
EDIT
I'm getting these results now, all that I need, but nowhere near the desired format above -can it be done as part of the find, or does it need to be sorted after?
Yep, it's possible. You just have to specify fields on containable:
$this->User->find('all', array(
'conditions' => array('User.id' => $id),
'fields' => array('id'),
'contain' => array(
'Profile' => array(
'fields' => array('id','type'),
'ProfileAttribute' => array(
'fields' => array('id'),
'AttributeName' => array(
'fields' => array('id','name'),
'Attribute' => array(
'fields' => array('id','value')
)
)
)
)
)
);
Be wary that when you use contain and fields options, you have to specify the id so it can make the association (check the docs)
EDIT: I don't know if you can group contained data as the docs didn't say anything about that, but probably you can, as they accept some parameters as in the main query. You can try it, adding group to any contained data that you want to group
Why don't cakePHP include all my associations into one SQL query? The only way I have been able to do this is using "joins", but I hoped belongsTo and Containable behaviour was enough.
Here is an example:
Post->belongsTo->Category->belongsTo->category_type
(All models are setup correctly and work.
listing posts with pagination in index, I try this:
public function index() {
$this->paginate = array( 'contain' => array('Category' => array('CategoryType')));
$this->set('posts', $this->paginate());
}
This fetches the array correctly, but it does it in many SQLs like this:
SELECT `Post`.`id`, `Post`.`name`, `Post`.`content`, `Post`.`category_id`, `Category`.`id`, `Category`.`name`, `Category`.`category_type_id` FROM `unit_app`.`post` AS `Post` LEFT JOIN `unit_app`.`categories` AS `Category` ON (`Post`.`category_id` = `Category`.`id`) WHERE 1 = 1 LIMIT 20
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 1
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 2
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 2
This makes it difficult to order this query on CategoryType.name ASC.
Any suggestions?
If joins are only option, do I have to unbind the models before querying?
Will pagination work fine with joins?
Note! this is just a small part of all models, the resulting post->index need to fetch many other models through similar associations also.
(tested on cake 2.2.0 and v2.4.0-dev, php v5.4.11)
UPDATE! ---------
I just wanted to show my findings. I have now solved this without joins, but I had to re-bind in the model to get it working.
This is basically what I did to get it to work (also with paginations and sorts):
In Post model:
Added a bind function:
$this->unbindModel(array(
'belongsTo' => array('Category')
));
$this->bindModel(array(
'hasOne' => array(
'Category' => array(
'foreignKey' => false,
'conditions' => array('Category.id = Post.category_id')
),
'CategoryType' => array(
'foreignKey' => false,
'conditions' => array('CategoryType.id = Category.category_type_id')
))));
Then I added this to my index in Post controller:
$this->Post->bindCategory();
$this->paginate = array('contain' => array('Category' ,'CategoryType'));
$this->set('posts', $this->paginate());
I include table headers also just for documentation:
<th><?php echo $this->Paginator->sort('CategoryType.name', 'Type'); ?></th>
<th><?php echo $this->Paginator->sort('Category.name', 'Category'); ?></th>
I hope this post can help others as well :)
I am also going to test this Behaviour to see if I can omit all the bind-functions as well: https://github.com/siran/linkable/
There are lots of plugins to cake, but cake should have a "certification" of the plugins. It is quite difficult to find the fully working and tested ones on github :)
I also miss a site like railscasts.com just for cake :D
/MartOn
Yes, you must use JOINs to be able to order based on an associated models results.
Yes, you can paginate with JOINs. Just pass your options (including JOINs) to your paginate prior to actually calling $this->paginate();. (there are many resources online for how to paginate with JOINs)
This is a weird one.
I have a local server on which I develop apps. A product review app I developed works flawlessly on it, and utilizes Cake's associative modeling ($hasMany, $belongsTo, et. al.).
After pushing this app up to a production server, it fails. Gives me an error message:
Notice (8): Undefined property: AppModel::$Product [APP/controllers/reviews_controller.php, line 46]
ReviewsController::home() - APP/controllers/reviews_controller.php, line 46
Dispatcher::_invoke() - CORE/cake/dispatcher.php, line 204
Dispatcher::dispatch() - CORE/cake/dispatcher.php, line 171
[main] - APP/webroot/index.php, line 83
I've debug()'d $this and it shows, plain as day, that, while the local server is loading the associated models, the production server is not. The databases are mirror duplicates (literally, the production server was imported from the dev db), and I can manually load models, which tells me it's connecting to the DB just fine.
What on Earth is going on?
UPDATE
The sql query from the production server is this:
SELECT `Review`.`id`, `Review`.`title`, `Review`.`product_id`, `Review`.`score`, `Review`.`submitted`, `Review`.`reviewed`, `Review`.`right`, `Review`.`wrong`, `Review`.`user_id`, `Review`.`goals`
FROM `reviews`
AS `Review`
WHERE 1 = 1
ORDER BY `Review`.`submitted` desc LIMIT 10
The sql query from the dev server is this:
SELECT `Review`.`id`, `Review`.`title`, `Review`.`product_id`, `Review`.`score`, `Review`.`submitted`, `Review`.`reviewed`, `Review`.`right`, `Review`.`wrong`, `Review`.`user_id`, `Review`.`goals`, `User`.`id`, `User`.`username`, `Product`.`id`, `Product`.`name`
FROM `reviews`
AS `Review`
LEFT JOIN `users` AS `User` ON (`Review`.`user_id` = `User`.`id`)
LEFT JOIN `products` AS `Product` ON (`Review`.`product_id` = `Product`.`id`)
WHERE 1 = 1
ORDER BY `Review`.`submitted` desc LIMIT 10
UPDATE 2
Here's some of the code the errors point to:
$title = $this->Review->Product->find( 'first', array( 'fields' => array( 'Product.name' ), 'conditions' => array( 'Product.id' => $filter ) ) );
UPDATE 3
<?php
class Review extends AppModel {
var $name = 'Review';
var $displayField = 'title';
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
?>
I had this problem, and for me it was due to a missing field in one of the database tables. I'd triple-check to make sure both DB's are exactly the same (although you said they were...): feel free to use this 7-year-old app to check them :D http://www.mysqldiff.org/
Other people with this issue talked about filename issues and that all files should be lowercased, so that may be something to check as well...
Actually - from a quick glance it might be worth using containable to make sure your data calls are consistent.
If you don't want to go through the hassle of adding containable (but I would urge you to do so - it is one my favourite features of cakephp), you may want to set recursive in your find() call just to make sure the associated models are loaded.
Do you have a way of looking at the files on the server without going through ftp? I had a problem similar to this where the timestamps were messed up on the files and the server would not update the file. I had to delete the files, and then re upload them. You may have already tried this but I just thought I would suggest the possibility. Maybe some of those files are outdated on the server.
Have a great day!
Can you pastebin the Review model, Product model, AppModel, AppController, and the controller you are getting the error from.
The line:
Notice (8): Undefined property: AppModel::$Product [APP/controllers/reviews_controller.php, line 46]
Seems to indicate the Review Model is loading the AppModel and not the file you want it to. In that case the Review model won't have a Product association.
you can print the stacktrace out to see, here's some code I snatch from php.net
echo "<div>Stack<br /><table border='1'>";
$aCallstack=debug_backtrace();
echo "<thead><tr><th>file</th><th>line</th><th>function</th><th>args</th></tr></thead>";
foreach($aCallstack as $aCall)
{
if (!isset($aCall['file'])) $aCall['file'] = '[PHP Kernel]';
if (!isset($aCall['line'])) $aCall['line'] = '';
echo "<tr><td>{$aCall['file']}</td><td>{$aCall['line']}".
"</td><td>{$aCall['function']}</td><td>";
debug (($aCall['arg']));
echo "</td></tr>";
}
echo "</table></div>";
die();
It's gonna be hard looking through all that though.
Say I have a model Post and a model Comment related as follows:
Post hasMany Comment
Comment belongsTo Post
How do use find('all') to retrieve every Post with its associated latest Comment?
I have tried defining a hasOne relationship in Post as:
var $hasOne = array('LatestComment' => array('className' => 'Comment', 'order' => 'LatestComment.created DESC'));
But when I do a Post->find('all') it returns every Post multiple times, once per each Comment, with LatestComment set to the different Comments.
You can add 'limit' => 1 to your array of parameters to only return one comment.
Alternatively, instead of defining another relationship, you can simply limit the number of comments returned when you perform the find, using the Containable behaviour.
$this->Post->find('all',array(
'contain' => array(
'Comment' => array(
'order' => 'Comment.created DESC',
'limit' => 1
)
)
);
This is useful if you want to filter any related sets without defining relationships - by author, or within a date range, for example.
Make sure that you add the Containable behaviour to any model that you reference.
To remove the duplicates you want to use: GROUP BY in your find all query. I believe Cake has a 'group' => option as well.
I am having a very curious problem. I am trying to do a find with conditions that work across model relationships. To wit...
$this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2')));
...assuming that Model has a hasMany relationship to RelatedModel. This particular find bombs out with the following error message:
Warning (512): SQL Error: 1054: Unknown column 'RelatedModel.col2' in 'where clause' [CORE/cake/libs/model/datasources/dbo_source.php, line 525]
Looking at the SELECT being made, I quickly noticed that the comparison in the related model was in fact being placed in the WHERE clause, but for some reason, the only thing in the FROM clause was Model, with no sign of RelatedModel. If I remove the comparison that uses the relationship, related models ARE pulled in the result.
I'm using Cake 1.2.4. At first glance, there's nothing in the 1.2.4 -> 1.2.5 changelog that I see that covers this, and you would think that such an obvious bug would be hunted down and fixed a few days later, as opposed to waiting a full month and not mentioning anything in the release annoucement.
So, uh, what's going on?
If your models are using the Containable behavior, make sure you contain those models.
First, in your {model_name}.php file:
class {ModelName} extends AppModel {
var $actsAs = array('Containable');
}
Then in your find:
$results = $this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2',
),
'contain' => array('RelatedModel'),
));
If not using Containable behavior, then try explicitly increasing the recursion level:
$results = $this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2',
),
'recursive' => 1,
));
Note that the latter method will more than likely retrieve a lot of unnecessary data, slowing down your application's speed. As such, I highly recommend implementing the use of the Containable behavior.