I've got this relationship between two models:
Snippet belongsTo SnippetCategory
SnippetCategory hasMany Snippet
I need a drop-down list of SnippetCategories when I add a new Snippet. For this, I've got the following code:
//Snippet.php
public $belongsTo = array(
'SnippetCategory' => array(
'className' => 'SnippetCategory',
'foreignKey' => 'snippet_category_id'));
//SnippetCategory.php
public $hasMany = array(
'Snippet' => array(
'className' => 'Snippet',
'foreignKey' => 'snippet_category_id',
'dependent' => true));
//SnippetsController.php
public function add() {
$categories = $this->Snippet->SnippetCategory->find('list');
$this->set(compact('categories'));
if ($this->request->is('post')) {
$this->request->data['Snippet']['user_id'] = $this->Auth->user('id');
if ($this->Snippet->saveAssociated($this->request->data)) {
$this->Session->setFlash('Snippet saved.');
$this->redirect(array('action' => 'index'));
}
}
}
//Snippets/add.ctp
// ...html...
echo $this->Form->input('categories', array(
'tabindex' => 2));
// ...html...
But I'm getting a "Call to a member function find() on a non-object" error. I know it has to be some stupid error, but I'm not able to find what I'm doing wrong...
Please, someone could help me with this? Thanks in advance!
First of all, you should post the whole code from the Snippet and SnippetCategory model. Maybe there's something that is causing this oddly behavior that is not shown in this snippet of code.
The error is quite straight forward, SnippetCategory is not initialized.
You can always use:
$this->loadModel('SnippetCategory');
$this->SnippetCategory->find('list');
But it's not the smarttest way because SnippetCategory model must be initialized with the Snippet model
Related
I have problem with HABTM models. When I try to fetch any related model f.e. like this:
$this->Tagi->find('first');
I dont get any results for associated model. Result looks like this:
array(
'Tagi' => array(
'id' => '1',
'nazwa' => 'sth'
),
'Instytucje' => array()
)
I am sure that there should be result, I've double checked it, even
$this->Tagi->getDataSource()->getLog(false, false)
shows correct query, that fetches right results.
If you have any idea whats wrong plz give me a hint.
Tagi model:
public $hasAndBelongsToMany = array(
'Instytucje' =>
array(
'className' => 'Instytucje.Instytucje',
'joinTable' => 'instytucje-tagi',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'instytucja_id',
'unique'=> true
)
);
Instytucje model:
public $hasAndBelongsToMany = array(
'Tagi' =>
array(
'className' => 'Instytucje.Tagi',
'joinTable' => 'instytucje-tagi',
'foreignKey' => 'instytucja_id',
'associationForeignKey' => 'tag_id',
'unique'=> true
)
);
EDIT:
Main problem is that HABTM refers to AppModel causing error:
Error: Table app_models for model AppModel was not found in datasource prod.
Which can be bypassed by adding $useTable in AppModel, which results in prior problem.
SOLVED
When using naming convention far beyond this Cake use, you have use third model with $useTable pointed on reference table.
Moreover its important to correctly point Cake to classes inside plugins.
I think you're problem relates to the fact that you are not using CakePHP's naming conventions. From the look of the query being generated Cake doesn't know how to correctly alias the join table, hence getting an AppModel alias in your query. I suspect that this is causing issues when Cake tries to build the results array from the query.
This might not work, but try creating a model for the joins table called InstytucjeTagi; then update your associations to use this using the with key:-
Tagi model:
public $hasAndBelongsToMany = array(
'Instytucje' =>
array(
'className' => 'Instytucje.Instytucje',
'joinTable' => 'instytucje-tagi',
'with' => 'InstytucjeTagi',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'instytucja_id',
'unique'=> true
)
);
Instytucje model:
public $hasAndBelongsToMany = array(
'Tagi' =>
array(
'className' => 'Instytucje.Tagi',
'joinTable' => 'instytucje-tagi',
'with' => 'InstytucjeTagi',
'foreignKey' => 'instytucja_id',
'associationForeignKey' => 'tag_id',
'unique'=> true
)
);
If this doesn't fix it try using the afterFind() callback in the Tagi model to check what Cake is returning:-
afterFind($results, $primary = false) {
parent::afterFind($results, $primary);
// Print out the $results to check what Cake is returning.
debug($results);
return $results;
}
I created a site for fun to learn CakePHP but for some reason I can't get a multiple select dropdown box to show my selected items. In this example 1 video game can have multiple difficulties (easy, normal, hard). On my Game edit page I have a multi select box to pick the difficulties. It shows all three difficulties and I can select them and it saves correctly. However when I return to the edit page the items I previously saved do not show highlighted as selected. I have verified that the records are saving correctly in the database.
Tables:
games
difficulties
difficulties_games
Models:
class Game extends AppModel {
public $actsAs = array('Containable');
public $hasAndBelongsToMany = array(
'Difficulty' =>
array(
'className' => 'Difficulty',
'joinTable' => 'difficulties_games',
'foreignKey' => 'game_id',
'associationForeignKey' => 'difficulty_id',
'unique' => 'true'
)
);
}
class Difficulty extends AppModel {
public $actsAs = array('Containable');
public $hasAndBelongsToMany = array(
'Game' =>
array(
'className' => 'Game',
'joinTable' => 'difficulties_games',
'foreignKey' => 'difficulty_id',
'associationForeignKey' => 'game_id',
'unique' => 'true'
)
);
}
Controller:
$game = $this->Game->findById($id);
$this->set('difficulties', $this->Game->Difficulty->find('list'));
View (edit.ctp):
echo $this->Form->input('Difficulty');
This has to be something simple I am missing but I've read through the book on HABTM and searched here and couldn't find much on multi-select boxes.
UPDATE:
Here is the entire edit function in the controller:
public function edit($id = null) {
if (!$id) {
throw new NotFoundException(__('Invalid post'));
}
$game = $this->Game->findById($id);
if (!$game) {
throw new NotFoundException(__('Invalid post'));
}
if ($this->request->is('post') || $this->request->is('put')) {
$this->Game->id = $id;
if ($this->Game->saveAll($this->request->data)) {
$this->Session->setFlash('Your game has been updated.');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash($this->Game->invalidFields());
}
}
if (!$this->request->data) {
$this->request->data = $game;
}
$this->set('systems', $this->Game->System->find('list'));
$this->set('genres', $this->Game->Genre->find('list'));
$this->set('difficulties', $this->Game->Difficulty->find('list'));
}
Also here is some more on the View:
echo $this->Form->create('Game');
echo $this->Form->input('name');
echo $this->Form->input('system_id');
echo $this->Form->input('genre_id');
echo $this->Form->input('Difficulty');
echo $this->Form->input('id', array('type' => 'hidden'));
echo $this->Form->end('Save Game');
What CakePHP version are you using? Releases 2.2.6 and 2.3.0 have a bug related to showing existing habtm selected. So updated to 2.2.7 if using 2.2.6 or if using 2.3.0 use the master branch from github until the next bugfix release is done.
For everyone who sets public $recursive = -1; in his AppModel due to performance reasons, or changes the recursion in his controller:
The automagic won't work without recursive 1! Make sure you change it in the options when retreiving your data for the edit.
$options = array(
'recursive' => 1,
'conditions' => array('YourModel.' . $this->YourModel->primaryKey => $id)
);
$this->request->data = $this->YourModel->find('first', $options);
I am trying to do a search, using pagination for posts which have a specific tag or tags (for example, if a user was to select two tags, then posts containing either tag would be returned).
I have the relationship defined in my Posts table
public $hasAndBelongsToMany = array('Tags' => array(
'className' => 'Tags',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
'unique' => 'keepExisting'));
How do I use Find to retrieve rows with a given tag (name or ID would be fine)
Trying:
// other pagination settings goes here
$this->paginate['conditions']['Tags.id'] = 13;
gives me an error that the relationship does not exist.
Looking at the debug info it appears that the tables are not joining the Posts_Tags and Tags table, however, when I debug the data making it to the view, the Posts objects contain the tags data.
Most of the documentation I can find for this seems to revolve around earlier versions of CakePHP, any help would be appreciated.
Could not find a satisfying solution myself.
I created a behavior to take care of this.
Create a file called HabtmBehavior.php and put it in your app/Model/Behavior folder.
Put the block of code in there and save file.
Add the behavior to your model: eg public $actsAs = array('Habtm');
Here is a usage example with find.
<?php $this->Entry->find('all', array('habtm'=>array('Tag'=>array('Tag.title'=>'value to find'))) ?>
Paginate would look something like this:
$this->paginate['Entry']['habtm']['Tag'] = array('Tag.title'=>'value to find');
You are free to add as many relations as you want by adding additional Model Names in the habtm array.
(Just be careful not to make it to complex since this could start slowing down your find results.)
<?php
class HabtmBehavior extends ModelBehavior {
public function beforeFind(Model $model, $options) {
if (!isset($options['joins'])) {
$options['joins'] = array();
}
if (!isset($options['habtm'])) {
return $options;
}
$habtm = $options['habtm'];
unset($options['habtm']);
foreach($habtm as $m => $scope){
$assoc = $model->hasAndBelongsToMany[$m];
$bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$model->alias}.{$model->primaryKey}";
$options['joins'][] = array(
'table' => $assoc['joinTable'],
'alias' => $assoc['with'],
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind)
);
$bind = $m.'.'.$model->{$m}->primaryKey.' = ';
$bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
$options['joins'][] = array(
'table' => $model->{$m}->table,
'alias' => $m,
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind) + (array)$scope,
);
}
return $options;
}
}
Hope this helps.
Happy baking.
I think the best solution is apply find function on join table Model. I try this before and it's work fine.
in your PostTag model :
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
),
'Tags' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
),
);
in your controller :
// $tagsId = tags ids
$posts = $this->PostTag->find('all', array('conditions' => array('PostTag.tag_id' => $tagsId),'contain' => array('Post')));
also is better follow cake naming convention, if you have tags(plural), post_tags(first singular second plural),posts(plural) tables you must have Tag,PostTag,Post Models.
For some reason i'm really having a hard time wrapping my head around HABTM associations. I learn best by watching someone do something and explaining why. Anyways, I have 2 tables I want associated, Drugs, and SideEffects. I've created the intermediate table drugs_side_effects(has no data right now). Does cake put the data in that automatically or do I need to do something? The 3.7.6.5 hasAndBelongsToMany (HABTM) from the book didn't specify.
I've set up the models correctly(I think) and am not sure how to proceed at this point. It seems pretty simple. I need to display side_effect from the SideEffects table in a Drug view. I think in the edit_french controller function i'll need something like
$side_effect = $this->Drug->SideEffect->read(
array('SideEffect.id','SideEffect.side_effect'), $id);
$this->set('side_effect',$side_effect);
but I feel like that won't work as expected. Or maybe there's a more efficient way? Any advice or help is appreciated.
Drug Model:
var $hasAndBelongsToMany = array(
'SideEffect' => array(
'className' => 'SideEffect',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'drug_id',
'associationForeignKey' => 'side_effect_id'
)
);
}
?>
SideEffect Model:
var $hasAndBelongsToMany = array(
'Drug' => array(
'className' => 'Drug',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'side_effect_id',
'associationForeignKey' => 'drug_id'
)
);
}
?>
Drugs Controller:
<?php
class DrugsController extends AppController {
var $name = 'Drugs';
var $helpers = array('Html','Form','Paginator');
var $paginate = array(
//'contain' => array('SideEffect'),
//'fields' => array('Drug.id', 'Drug.generic'),
'fields' => array('Drug.id', 'Drug.generic','Drug.date_altered'),
'limit' => 50,
'order' => array(
'Drug.generic' => 'asc'
)
);
function index() {
$data = $this->paginate('Drug');
$alldrugs = $this->set('drugs', $this->Drug->find('all'));
$this->set('drugs', $data);
$this->set('alldrugs', $data);
//$this->set('lessdrugs', $this->paginate());
$this->set('title_for_layout','List of all current drugs');
}
function edit_french($id = null) {
$this->Drug->id = $id;
$drug = $this->Drug->read(
array(
'Drug.id','Drug.generic','Drug.ahl','Drug.aap','Drug.rid','Drug.oral','Drug.mw','Drug.clinical_recommendations'
),
$id
);
$this->set('title_for_layout', 'Translate clinical recommendations - ' . $drug['Drug']['generic']);
$this->set('drug',$drug);
if (empty($this->data)) {
$this->data = $this->Drug->read();
} else {
if ($this->Drug->save($this->data)) {
$this->Session->setFlash('The drug has been updated.');
$this->redirect(array('action' => 'index'));
}
}
}
}
?>
You can pull the related data using ContainableBehavior. To do so, simply run a find on the Drug model and tell it to contain the associated SideEffect data.
$drug = $this->Drug->find('first', array(
'conditions' => array(
'Drug.id' => $id
),
'contain' => array(
'SideEffect'
)
));
You can also set the contain before using read() if you prefer.
$this->Drug->contain(array('SideEffect'));
$drug = $this->Drug->read(null, $id);
Using Containable allows you to gather all associated data in a single find() request.
I'm working on a small app to help learn cakephp 2.0. Its a simple task manager app.
Tables
users
tasks
tasktypes
I've a set up a foreign key in the tasks table called 'user_id'
As a task belongs to a user.
Now all I'm trying to do is display the tasks of a certain user and it wont work at all despite the sql query getting correct results when I tested it any help would be great.
In my taskscontroller I've the method;
//show the tasks of the current user
public function mytasks(){
$this->set('title_for_layout', 'Gerrys TaskManager: Display Tasks');
$this->Task->recursive = 0;
$tasks=$this->Task->find('all', array('conditions'=>array('username'=>'admin')));
$this->set('tasks', $this->paginate());
}
I'm trying to find the tasks of the user 'admin' or the "realted tasks" what am I doing wrong?
I suspect the problem maybe to with my models;
usermodel
/**
* hasMany associations
*
* #var array
*/
public $hasMany = array(
'Task' => array(
'className' => 'Task',
'foreignKey' => 'user_id'
)
);
task model;
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Tasktype' => array(
'className' => 'Tasktype',
'foreignKey' => 'tasktype_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
To get the tasks that belong to a user, it's much simpler to do that in the UsersController. Of course you have to set User hasMany Task in your UserModel.
Now you can create a function, for example to view the User and it's Tasks by id:
public function view($id = null) {
$this->User->id = $id;
$this->set('user', $this->User->read(null, $id));
}
The Tasks should now be available in your view by $user['tasks']. You have to pass the id in the url like: /path_to_your_app/users/view/1
You can always use debug() to see contents of an array or variable. To see what you've got add <?php debug($user);?> to your view.
You most likely want to list any tasks with a foreach in your view, very simple example::
foreach($user['Task'] as $task) {
echo $task['field1'] . ": " . $task['fied2'];
echo "<br />";
}
of course you might want to display the tasks in tables or something.
A good starting point for learning cakePHP is the Blog-Tutorial:
http://book.cakephp.org/2.0/en/getting-started.html#blog-tutorial