I'm trying to use Containable to return a model with several of its associated models (and those arrays of data). The models deal with test results. Here are the models:
Models:
Question : has and belongs to many Category
Category : has and belongs to many Question
Attempt : has many AttemptedQuestions
AttemptedQuestion : belongs to Question
I want to return an Attempt with all of it's AttemptedQuestions and their corresponding Quesions + Category
So basically, the relationships map like this:
Attempt => AttemptedQuestion(s) => Question => Category
I suspect that because of the HABTM relationship, Cake would prefer to return:
Attempt => AttemptedQuestion(s) => array( Question, Category )
<--Categories are not contained within the Question array, but rather they are sisters. This is fine as well.
At the moment I am unable to get Category to show up at all. Here's what I'm doing inside a controller (this does NOT result in the Categories appearing in results):
$this->Attempt->contain(array('AttemptedQuestion' => array('Question'=>'Category')));
$attempt_to_be_graded = $this->Attempt->findById( $attempt_id );
What am I doing wrong?
Update Here's a revision based on the answer from @nunser. This also does not work.
$this->Attempt->contain(array('AttemptedQuestion' => array('Question'=>array('Question'=>array('Category') ))));
$attempt_to_be_graded = $this->Attempt->findById($attempt_id );
This is what the data returned looks like:
array(
'Attempt' => array(
'id' => '39',
...
),
'AttemptedQuestion' => array(
(int) 0 => array(
'id' => '189',
...,
'Question' => array(
'id' => '165',
...
)
),
(int) 1 => array(
'id' => '188',
...,
'Question' => array(
'id' => '164',
...
)
)
)
)
Update 2
I'm still struggling with this, but I think my associations have to be correct because the following returns a list of all my Categories just as expected:
$categories = $this->Attempt->AttemptedQuestion->Question->Category->find('all');
Update 3
I've narrowed down the scope of this problem by testing the results of $this->Question->find('first')
from various points throughout the code. It appears that the results are expected UNTIL after this: $test = $this->Test->findById($test_id);
.
Here is the code which demonstrates:
$this->loadModel('Question');
$test = $this->Question->find('first');
debug($test);
//THESE RESULTS INCLUDE Category DATE
$test = $this->Test->findById($test_id);
$this->loadModel('Question');
$test = $this->Question->find('first');
debug($test);
exit;
//THESE RESULTS DO NOT INCLUDE Category DATE
So for reasons I completely do not understand, the intervening Test->find()
seems to prevent the Category data from appearing afterwards. Weird, huh?
Sometimes, on my side is better to use binModel nested instead of a behavior, cause is more configurable, check please this link, and you probably could help you on your question
CakePHP - Building a Complex Query
cakephp join more than 2 tables at once in one join table
Try
HABTM Headaches
These are difficult to manage in Cake. What I've started doing is called "hasMany Threw" which is described in the book here.
The advantage is more control over find queries. It requires an additional
Model
be added, but it's worth it. The automated handling of HABTM in CakePHP is often difficult to understand. Using associations to explicitly define the relationships is more flexible.Circular Reference
Your Models are currently setup with a circular reference, and this can be problematic for a HABTM.
It is very difficult in Cake to create a HABTM on both models. Usually this is done only on one of them, and I'd pick
Question
. With a "hasMany Threw" it's easier.HasMany Threw
If we use a "hasMany Threw" then the containable rules become a lot easier to explain.
Now, I removed
AttamptedQuestion
. I'm not exactly sure what you're trying to model, but if a person can create multiple Attempts per Question, then it's not needed. Just create more then one Attempt record.Find All Records For An Attempt
To find all the associated records for an
Attempt
usingContainable
the find would be as follows.You can still add back the
AttemptedQuestion
model, and just contain it beforeQuestion
.Ambiguous Fieldnames With Associations
You should get into the habit of using
Attempt.id
in conditions instead of justid
. When you uses associations there are often duplicate fieldnames in the SQL result. You need to clarify which ones you are referring to, because you can also haveQuestion.id
as a condition inAttempt
if aJOIN
is used. Soid
is ambiguous on it's own.Give the new information about what triggers the problem.
In the above scenario the
findById
magic function (that's what I call them) calls the lower level data source driver to perform the query.In the
DboSource.php
it's not using theContainable
behavior to control recursion, and changes therecursive
property in theModel
. I think this is causing the trouble with theContainable
behavior.You can fix this by reset the
recursive
back to it's default after thefindById
.I'm going to guess this fixes the problem, but you have to remember to reset it after using
findById
or any other magic function.This should be report to the Cake developers as a bug if it's reproducible.
EDIT:
recursive default is 1 not 0.