I have the following code that gives an error from the moment I do a find condition on the associated Model (HABTM):
class Users extends Model{
public $useTable = 'users';
public $hasAndBelongsToMany = array(
'UserCategories' => array(
'className' => 'UserCategories',
'joinTable' => 'user_categories_link',
'foreignKey' => 'user_id',
'associationForeignKey' => 'category_id',
'unique' => false,
)
);
public function getData($page = 0, $category = '', $subcategory = ''){
return $this->find('all', array(
'limit' => 6,
'page' => $page,
'conditions'=> array(
'active'=> 1,
'UserCategories.category_name' => $category, // THIS GIVES ERROR
'UserCategories.category_subcategory' => $subcategory, // THIS GIVES ERROR
)
));
}
In my Controller:
$this->Users->getData(0, 'somemaincategory', 'somesubcategory');
I can't seem to do conditions on the related HABTM-Model (UserCategories in this case). I also tried to use 'contain' (with $actsAs), but then he stills gives me all the User data even if there is no Category linked with it. The Category array is in that case just blank.
Hope someone can help me.
Thanks in advance,
AƤron
Do a manual join. You can use this to do an actual inner join (contain will act as a left join). http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#joining-tables
$this->find('all',
array(
'conditions' => array(
'active' => 1,
),
'joins' => array(
array(
'table' => 'user_categories_link',
'alias' => 'UserCategoriesLink',
'type' => 'inner',
'conditions' => array(
'UserCategoriesLink.user_id = User.id'
),
),
array(
'table' => 'user_categories',
'alias' => 'UserCategories',
'type' => 'inner',
'conditions' => array(
'UserCategories.id = UserCategoriesLink.category_id',
'UserCategories.category_name' => $category,
'UserCategories.category_subcategory' => $subcategory,
),
)
),
)
);
Related
I need you help please!
My view didn't work with my INNER JOIN
public function view($bk_id = null)
{
$data = $this->Book->findBybk_id($bk_id, array(
'recursive' => -1,
'fields' => array('Book.*', 'Article.*'),
'joins' => array(
array(
'table' => 'Articles',
'alias' => 'Article',
'type' => 'INNER',
'conditions' => array('Article.bk_id = Book.bk_id')
)
)
));
$this->set('Book', $data);
}
Can anyone help me?
You appear to be using Cake's findBy magic functions which don't take a second parameter. Try using find('first') with the relevant conditions instead:-
$data = $this->Book->find('first', array(
'recursive' => -1,
'fields' => array('Book.*', 'Article.*'),
'joins' => array(
array(
'table' => 'Articles',
'alias' => 'Article',
'type' => 'INNER',
'conditions' => array('Article.bk_id = Book.bk_id')
)
),
'conditions' => array(
'Book.bk_id' => $bk_id
)
));
User has many User_Questionaries. I want paginate users that have particular questionnaire. I used following pagination for it.
$paginate = array(
'conditions' => array(
'User.role' => IWOA,
'UserQuestionary.questionary_id' => $id
),
'recursive' => 1,
'limit' => 10,
'order' => array(
'name' => 'asc'
),
'contain' => array('UserQuestionary')
);
But it is not create join query. It is showing Unknown column UserQuestionary.questionary_id' in 'where clause'
What is the issue? How can i do it?
Finally I used join query for do this.
$paginate = array(
'conditions' => array(
'Iwoa.role' => IWOA,
),
'joins' => array(
array(
'alias' => 'UserQuestionary',
'table' => 'user_questionaries',
'type' => 'LEFT',
'conditions' => 'UserQuestionary.user_id = Iwoa.id AND UserQuestionary.questionary_id = ' . $id
)
),
'limit' => 10,
'order' => array(
'name' => 'asc'
),
);
I have 4 model:
Item------hasMany---->Detail
Item------hasMany---->Favourite
Item------hasMany---->Category
How can I find all Item that has Favourite.member_id = '8' and Category.item_category_id = '1'
Here is my Item model relation:
public $hasMany = array(
'Detail' => array(
'className' => 'Detail',
'foreignKey' => 'item_id',
'dependent' => true,
'conditions' => '',
'order' => 'created DESC',
),
'Category' => array(
'className' => 'Category',
'foreignKey' => 'item_id',
'dependent' => true,
'conditions' => '',
'order' => 'created DESC',
),
'Favorite' => array(
'className' => 'Favorite',
'foreignKey' => 'item_id',
'dependent' => true,
'conditions' => '',
'order' => 'created DESC',
)
);
I used this query() and it's ok:
$this->set('test',$this->Item->query("SELECT items.*
FROM items, categories, favourites
WHERE items.id = items_item_categories.item_id
AND items.id = favorites.item_id
AND categories.item_category_id = 1 AND favourites.member_id = 8"));
But I don't like using query(), and I change it into find(), and it seems not good:
$this->set('test',$this->Item->find('all', array(
'contain'=>array(
'ItemDetail',
'Category'=>array('conditions'=>array('Category.item_category_id'=>1)),
'Favorite'=>array('conditions'=>array('Favorite.member_id'=>8)));
You need call the containableBehaivor on your model
<?php
class Item extends AppModel {
public $actsAs = array('Containable');
}
in your controller you can make the query
$items = $this->Item->find('all', array(
'contain'=>array(
'ItemDetail',
'Category'=>array(
'conditions'=>array('Category.item_category_id'=>1)
),
'Favorite'=>array(
'conditions'=>array('Favorite.member_id'=>8)
)
)
);
$this->set('test', $items);
try using Joins
$items = $this->Item->find('all', array(
'joins' => array(
array(
'table' => 'categories',
'alias' => 'Category',
'type' => 'inner',
'conditions'=> array('Category.item_category_id' => 1)
),
array(
'table' => 'favorites',
'alias' => 'Favorite',
'type' => 'inner',
'conditions'=> array('Favorite.member_id' => 8, 'Item.id = Favorite.item_id')
)
),
'contain'=>array('ItemDetail','Category','Favorite')
);
$this->set('test', $items);
You could also try something like this:
$this->Item->hasMany['Favorite']['conditions']['member_id'] = 8;
Which has the same effect as rebinding the model with the condition.
Just a possible issue. The above will add the condition for the rest of the request, if you want to rollback to the previous behavior, you need to unset the condition:
unset($this->Item->hasMany['Favorite']['conditions']['member_id']);
Remember to set the $this->Item->recursive property to the desired value (recursive attribute). The default it's 1, which it's the minimum you need for this to work.
I have the following relationships set up:
Teacher:
var $hasAndBelongsToMany = array(
'Classroom' => array(
'className' => 'Classroom',
'joinTable' => 'classrooms_teachers',
'foreignKey' => 'teacher_id',
'associationForeignKey' => 'classroom_id',
'unique' => true,
)
);
Classroom:
var $hasAndBelongsToMany = array(
'Teacher' => array(
'className' => 'Teacher',
'joinTable' => 'classrooms_teachers',
'foreignKey' => 'classroom_id',
'associationForeignKey' => 'teacher_id',
'unique' => true,
)
);
var $hasMany = array(
'Student' => array(
'className' => 'Student',
'foreignKey' => 'classroom_id',
'dependent' => false,
),
);
Student:
var $belongsTo = array(
'Classroom' => array(
'className' => 'Classroom',
'foreignKey' => 'classroom_id',
),
);
I am trying to create a dashboard for the teachers, where all students associated with the teacher through the classroom are displayed.
I am using the following find operation:
$students = $this->Teacher->Classroom->find('all', array(
'conditions' => array('Classroom.teacher_id' => $this->Access->getTeacherId()),
));
However, I am getting an error: Unknown column 'Classroom.teacher_id' in 'where clause'
I must be doing something wrong because cake is not doing the associations.
Any ideas?
This is the correct way of achieving this:
$this->Teacher->bindModel(array('hasOne' => array('ClassroomsTeachers')));
$students = $this->Teacher->find('all', array(
'conditions' => array('ClassroomsTeachers.teacher_id' => $this->Access->getTeacherId()),
'recursive' => 2
));
I hope it helps someone!
That's not the best way. Just use containable and find('first'), you don't need find all:
$students = $this->Teacher->find('first', array(
'conditions' => array('Teacher.id' => $this->Access->getTeacherId()),
'contain' => array('Classroom'=>array('Student'))
));
That will get you all the classrooms associated with the teacher, and all students in those classrooms.
I made a behavior to solve the HABTM problem.
Posted my answer here:
HABTM Find with CakePHP 2.0
I have the following tables: binders, docs, users, docs_users. Doc belongsTo Binder, Doc hasAndBelongsToMany User.
I want to get binders and their associated docs for the user that is currently logged in (the associated user_id in the docs_users table).
I have tried Containable and find('all') with joins, conditions, etc. but I can't figure out how to remove the Docs that are from Users who are not associated in the docs_users table.
This code does NOT work:
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
),
array(
'table' => 'docs',
'alias' => 'Doc',
'type' => 'left',
'foreignKey' => false,
'conditions'=> array(
'Doc.binder_id = Binder.id',
)
),
array(
'table' => 'docs_users',
'alias' => 'DocsUser',
'type' => 'left',
'foreignKey' => false,
'conditions'=> array(
'DocsUser.doc_id = Doc.id',
'DocsUser.user_id = ' . $this->Auth->user('id')
)
)
),
'recursive'=>0
)
);
$this->set('binders', $binders);
And neither does this:
$this->Binder->recursive = 2;
$this->Binder->Behaviors->attach('Containable');
$this->Binder->contain(array(
'Branch',
'Doc' => array(
'User' => array(
'DocsUser' => array(
'conditions' => array('id = "17"')
)
)
)
));
$binders = $this->Binder->find('all');
Any help from you seasoned pros would be great! Thanks!
Alternative/Simplified Solutions?
This works if I just want to get binders to which users have permissions. Short and sweet. However, it will still send ALL associated docs through, which is NOT the behavior I want. It needs to only pass on the docs to which the user has permissions (as described previously).
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
)
)
)
);
These are a few of the options available for doing deep finds on data in CakePHP:
https://github.com/Terr/linkable/wiki
http://mark-story.com/posts/view/using-bindmodel-to-get-to-deep-relations
http://bakery.cakephp.org/articles/view/quick-tip-doing-ad-hoc-joins-in-model-find
Here's the final solution I came up with based on all of the great feedback I got. I think this is an elegant solution that can be reused in any scenario where deep associations are required.
In the binder_controller I unbound the Doc model, and bound it back using the finderQuery to select only the Docs that a user has permission to see. Then in joined the binders_users table selecting only the binders that users have permissions to.
Thank you everyone for all your help!
$this->Binder->unbindModel(array('hasMany' => array('Doc')));
$this->Binder->bindModel(
array('hasMany' => array(
'Doc' => array(
'className' => 'Doc',
'foreignKey' => 'binder_id',
'dependent' => false,
'finderQuery' => 'SELECT Doc.* FROM docs AS Doc INNER JOIN docs_users AS DocsUser ON DocsUser.doc_id = Doc.id AND DocsUser.user_id = ' . $this->Auth->user('id')
)
)
)
);
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
)
)
)
);
More on binding/unbinding models
On this line, you need to tell Cake which Model's id you are talking about:
'conditions' => array('id = "17"')
e.g. DocsUser.id
...and you don't use recursive with containable. Get rid of it.
Have you tried coming in from a user perspective?
$this->Binder->Doc->User->Behaviors->attach('Containable');
$this->Binder->Doc->User->contain(array('Doc'=>'Binder'));
$user = $this->Binder->Doc->User->find('all',array('conditions'=>'User.id'=>$user_id));
The 'DocsUser' association should be detected anyway.
From a Binders perspective maybe
In your Binders MODEL add ( please check table, key and model names in case I made a typo )
function getBindersByUserSql($user_id)
{
$dbo = $this->getDataSource();
$subQuery = $dbo->buildStatement(
array(
'fields' => array('DISTINCT(Doc.binder_id)'),
'table' => "docs_users",
'joins' => array(
array('table' => 'users',
'alias' => 'User',
'type' => 'INNER',
'conditions' => array('DocsUser.user_id = User.id')
),
array('table' => 'docs',
'alias' => 'Doc',
'type' => 'INNER',
'conditions' => array('Doc.id = DocsUser.doc_id')
)
),
'alias'=>"DocsUser",
'conditions' => array("User.id"=>$user_id),
'order' => null,
'group' => "Doc.binder_id"
),
$this
);
return $dbo->expression($subQuery);
}
Then in your binders CONTROLLER try
$this->Binder->Behaviors->attach('Containable');
$this->Binder->contain(array('Doc'));
$conditions = array();
$conditions = $this->Binder->getBindersByUserSql($this->Auth->user('id'));
$binders = $this->Binder->find('all',array('conditions'=>$conditions)));
Any good?