My schema has the following relations:
User hasMany Transaction belongsTo User
Item hasMany Transaction belongsTo Item
User hasManyAndBelongsTo Item using Transaction as join table
I'd like to return all items of a given item symbol that belong to a given user id. I'm able to retrieve all the items that belong to the user with
$this->User->find('all', array( 'conditions' =>
array( 'User.id' => $userId ) ) )
and I'm able to retrieve all the Items with a given item name with
$this->Item->find( 'all', array( 'conditions =>
array( 'Item.symbol' => $itemSymbol ) );
but if I try to use the condition array( 'Item.symbol' => $itemSymbol, 'User.id' => $userId ) on $this->Item->find(), $this->Item->User->find(), $this->User->find(), or $this->User->Item->find(), I get either the error
SQL Error: 1054: Unknown column 'Item.id' in 'where clause'
or
SQL Error: 1054: Unknown column 'Item.symbol' in 'where clause'
I've devoured HABTM in all versions of the cookbook and read tens of forum / SO posts about it, but I haven't had any luck in forming this query.
Relevant Info:
// User.php associations
// a user hasMany transactions
var $hasMany = array('Transaction' =>
array('className' => 'Transaction',
'order' => 'Transaction.created DESC', // order by descending time of transaction
//'limit' => '1', // change this if we need more, for example, if we need a user transaction history
'foreignKey' => 'user_id',
)
);
// a user hasAndBelongsTo items through transactions
var $hasAndBelongsToMany = array('Item' =>
array('className' => 'Item',
'joinTable' => 'transactions',
'foreignKey' => 'user_id',
'associationForeignKey' => 'item_id',
'order' => '',
'limit' => '',
'unique' => true,
'finderQuery' => '',
'deleteQuery' => '',
)
);
// Item.php associations
var $hasMany = array('Transaction' =>
array('className' => 'Transaction',
'order' => 'Transaction.created DESC',
//'limit' => '1', // change this if we need more, for example, if we need a item transaction history
'foreignKey' => 'item_id',
)
);
// a item hasAndBelongsTo users through transactions
var $hasAndBelongsToMany = array('User' =>
array('className' => 'User',
'joinTable' => 'transactions',
'foreignKey' => 'item_id',
'associationForeignKey' => 'user_id',
'order' => '',
'limit' => '',
'unique' => true,
'finderQuery' => '',
'deleteQuery' => '',
)
);
// Transaction.php associations:
var $belongsTo = array('User', 'Item');
You're looking for the Containable behavior; e.g.
// app_model.php
var $actsAs = array(
'Containable'
);
I'm assuming User extends AppModel, so Containable will be set for the child (User). Now, in your controller, conditions for the related class can be placed in the 'contain' key of the second argv of find(); e.g.
// users_controller.php
$this->User->find('first', array(
'conditions' => array('User.id' => $userId),
'contain' => array(
'Item' => array(
'conditions' => array('Item.symbol' => $itemSymbol)
)
)
);
Cake will find the matching User and retrieve matching Items (with the same user_id and the Item.Symbol specified. But be very careful to use 'contain' => array() when it is not required, otherwise Cake will retrieve all of the defined associations, which will tax your database.
Related
I am creating an assessment database and using cakephp for the first time. I have gotten all the data entry and editing sorted but am having trouble retrieving some crucial data from a deep association:
The essence is
Course HABTM Units (table courses_units)
Unit hasMany Artefacts
Artefact hasOne AdminArtefact
Artefact belongsTo an Unit
The AdminArtefact model is used for each years dynamic data for an artefact (due_date, late_date and so on).
My problem is when I want to get all the due dates of artefacts for a particular course (which we need to check for hand in congestion)
If I do a findAll in the Artefacts controller with recursion set to '2' it gets all the data I need...but it overloads the page due to the amount of data returned. So I want to set the conditions for the findAll for the specific fields I need. However I cannot set the course in the query - From my understanding the course data is pulled from a 'separate' query as it appears in the results like this:
[Course] => Array
(
[0] => Array
(
[id] => 2
[course_code] => C0767S
[course_name] => BSc (hons) Entertainment Technology
[course_local_code] => ET
[course_leader] => 6
[colour] =>
[CoursesUnit] => Array
so as part of the query, I cannot set the course equal to a specific one, which I need to do so I can also specify the other data elements I want to pull and not the wealth of other data.
Ive tried loading the Unit model and changing the bind to a belongsTo instead of HABTM to the courses_units table but got the same dataset back again.
Ive tried going 'higher up the chain' to the CourseUnits controller and doing a '$uses' and specified all the relevant tables I want to pull data from but that didn't get anything useful.
Reading up on HABTM it doesn't automatically pull the data so I looked at joining the tables, but couldn't really see which model to make the join to get it to work.
I need to put this to bed now so hence I am asking the question: What is the most elegant way to get the due dates for all the artefacts for a specific course? (I need the course, the unit, the artefact and the due date...so something from all the tables)
QUERY String I want to use in Artefacts controller:
$data = $this->Artefact->find('all', array(
'fields' => array('Artefact.reference', 'AdminArtefact.due_date', 'ArtefactUnit.short_code', 'Course.course_code' )));
This gives the error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Course.course_code' in 'field list'
Thanks in advance!
Kevin
All relevant models:
class Course extends AppModel {
public $displayField = 'course_name';
public $hasAndBelongsToMany = array(
'Unit' =>
array(
'className' => 'Unit',
'joinTable' => 'courses_units',
'foreignKey' => 'course_id',
'associationForeignKey' => 'unit_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'with' => 'CoursesUnit'
)
);
public $belongsTo = array(
'CourseLeader' => array(
'className' => 'User',
'foreignKey' => 'course_leader'
)
);
class Unit extends AppModel {
public $displayField = 'short_code';
public $hasMany = array(
'UnitArtefacts' => array(
'className' => 'Artefacts',
'foreignKey' => 'unit_id'
));
public $hasAndBelongsToMany = array(
'Course' =>
array(
'className' => 'Course',
'joinTable' => 'courses_units',
'foreignKey' => 'unit_id',
'associationForeignKey' => 'course_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'with' => 'CoursesUnit'
)
);
public $belongsTo = array(
'External' => array(
'className' => 'User',
'foreignKey' => 'external_examiner'
),
'Coordinator' => array(
'className' => 'User',
'foreignKey' => 'coordinator'
),
'UnitLevel' => array(
'className' => 'Level',
'foreignKey' => 'level_id'
)
);
class Artefact extends AppModel {
public $displayField = 'reference';
public $hasOne = array(
'AdminArtefact' => array(
'className' => 'AdminArtefact',
'foreignKey' => 'artefact_id'
)
);
public $belongsTo = array(
'ArtefactUnit' => array(
'className' => 'Unit',
'foreignKey' => 'unit_id'
)
);
actually setting
'recursive' => 3,
should work.
I have a pretty typical Comment model where Comment belongsto many other models such as Article, Review, Photo, etc and in turn each of those models hasmany Comment. Here is how I built the relationship on the Comment model...
<?php
App::uses('AppModel', 'Model');
class Comment extends AppModel {
var $name = "Comment";
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Article' => array(
'className' => 'Article',
'foreignKey' => 'post_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Photo' => array(
'className' => 'Photo',
'foreignKey' => 'post_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Review' => array(
'className' => 'Review',
'foreignKey' => 'post_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
This works like a charm wherein if I'm viewing a particular article then I can retrieve all comments from that article and it works the same for the other models. What I'm trying to do is show ALL recent comments with the original post's title no matter what model the original post is coming from (Article, Review, Photo, etc) in the format of $comment['Original']['title']. I thought adding the below code within the belongsto of the Comment model would work but it does not....
'Original' => array(
'className' => 'Article',
'foreignKey' => 'post_id',
'conditions' => array('Comment.module_id' => 3),
'fields' => '',
'order' => ''
),
'Original' => array(
'className' => 'Review',
'foreignKey' => 'post_id',
'conditions' => array('Comment.module_id' => 2),
'fields' => '',
'order' => ''
),
'Original' => array(
'className' => 'Photo',
'foreignKey' => 'post_id',
'conditions' => array('Comment.module_id' => 8),
'fields' => '',
'order' => ''
),
Unfortunately this only shows the correct title if the recent comment was on a photo (Comment.module_id = 8).
You can use CakePHP's Containable Behavior and do this:
//within a method of the Comment model
$recentComments = $this->find('all', array(
'contain' => array(
'Article',
'Photo',
'Review',
'User'
),
'limit' => 10,
'order' => $this->alias . '.created DESC'
));
Then, you'll get back the comments and any of their parents, regardless of which model it's in. After that, as you repeat through each comment, you can either do a case statement, or an if(!empty()) kinda thing to determine which model has content that you want to display.
(side note: not a good idea to create multiple associations with the same name per your attempt with "Original")
It's not surprising that you got comments only with conditions Comment.module_id = 8. You have made a basic PHP mistake. By using the same key Original multiple times you are effectively overwriting it's value each time. Choose unique aliases (array keys) for each association.
I know that i can easily find related model-data through model associations, if I select a single entry. But what I want to do is to get the first image as a thumb for the galery, when I use find('all') for the index-page.
My relations (Galleries are named Albums) are:
//Album-Model
public $hasMany = array(
'Picture' => array(
'className' => 'Picture',
'foreignKey' => 'album_id',
'dependent' => true,
'conditions' => ''
)
);
and:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Album' => array(
'className' => 'Album',
'foreignKey' => 'album_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
EDIT :
This should worked I tested it on my own!
Before your query add the following lane to specify the recursivity.
$this->Albums->recursive = 1;
$this->set('Albums', $this->Album->find('all'));
This way, it will also send the Pictures data. Then in your model associations, you can specify to only return the first image with the Album if that's what you want!
Just add 'limit' => '1', in your hasMany => Comments array, in Album Model.
Then you can iterate in your view doing
foreach($Albums as $Album){
$Album['Album']; // ALBUMS info do whatever
$Album['Picture']; // The thumbnail
}
EDIT2: I've just see you can also do
$this->Album->find('all', array('recursive' => 1));
I'm making a Photo model, is it a good practice to make it belong to multiple models such as User, Place, etc?
Place also belongs to User
So here is my fields for Photos.
id
owner_id
type (an enum of the different models such as users and places)
Here is the belongsTo in PhotoModel that I have
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'owner_id',
'conditions' => array('Photo.type' => 'user'),
'fields' => '',
'order' => ''
),
'Place' => array(
'className' => 'place',
'foreignKey' => 'owner_id',
'conditions' => array('Photo.type' => 'place'),
'fields' => '',
'order' => ''
)
);
Or is it better to just create separate models such as UserPhoto, PlacePhoto etc?
Right now with this approach, I'm sometimes seeing dbo error when I set recursive to 2.
Thanks,
Tee
This approach can work, although if you set recursive to 2, the clearest solution is to create and/or destroy associations on the fly in such queries.
If in future you are going to use UserPhoto and PlacePhoto model, then it will be better to create separate models for them. So that you can customize those models later on. Or otherwise if you are using those models rarely then you can particularly bind those models in to your controller's method.
$this->User->bindModel(array('belongsTo' => array(
'User' => array(
'className' => 'User',
'foreignKey' => 'owner_id',
'conditions' => array('Photo.type' => 'user'),
'fields' => '',
'order' => ''
),
'Place' => array(
'className' => 'place',
'foreignKey' => 'owner_id',
'conditions' => array('Photo.type' => 'place'),
'fields' => '',
'order' => ''
)));
I select Blog entries by changing hasAndBelongsToMany conditions on the fly, it doesnt work anymore for me with cakephp 1.3.
Strange problem cause it was working fine with 1.2, in the model i test it by putting a condition with a static id to see what happen, (Tag.name => 'libros'). but it pass though the hasAndBelongsToMany condition. Bring me whatever results.
var $hasAndBelongsToMany = array('Tag' =>
array('className' => 'Tag',
'joinTable' => 'blogs_tags',
'foreignKey' => 'blog_id',
'associationForeignKey'=> 'tag_id',
'conditions' => '',
'order' => '',
'limit' => '',
'unique' => true,
'finderSql' => '',
'deleteQuery'=> ''
)
in the controller
$this->Blog->bindModel(array(
'hasAndBelongsToMany' => array(
'Tag' => array('conditions'=>array('Tag.name'=>'libros'))
)));
$this->Blog->find('all');
Now i dont have mysql error anymore , but i have others records with others results. Weird.
If you have a correct database structure, must use this=
$this->Blog->bindModel(array(
'hasOne' => array(
'BlogsTag',
'FilterTag' => array(
'className' => 'Tag',
'foreignKey' => false,
'conditions' => array('FilterTag.id = BlogsTag.tag_id')
))));
$data = $this->Blog->find('all', array(
'fields' => array('Blog.*'),
'conditions'=>array('FilterTag.name'=>'libros')
));
you can read more about this at :
http://book.cakephp.org/view/1044/hasAndBelongsToMany-HABTM