CakePHP does not insert linking Association for HABTM Relationship - cakephp

I have got two Models in CakePHP 2.2.1:
class Episode extends AppModel {
public $hasAndBelongsToMany = array(
'Tags' => array('className' => 'Tag')
);
}
and
class Tag extends AppModel {
public $hasAndBelongsToMany = array(
'Episode' => array('className' => 'Episode')
);
}
And I want to insert an Episode and a Tag at the same time and link them to each other. From the documentation I got that this would be the right code which I execute inside a controller method:
$this->Tag->create();
$this->Tag->save(array(
'name' => 'Test Tag'
));
$tagid = $this->Tag->getInsertID();
var_dump($tagid); // works
$this->Episode->create();
$this->Episode->save(array(
'Episode' => array(
'title' => 'Test with Tag'
),
'Tags' => array(
'Tag' => array($tagid)
)
));
I get no error message and both records are created, but there is no row inserted into the episodes_tags table. CakePHP does these queries:
Nr Query Err Aff Rws Took (ms)
1 INSERT INTO `pottcast`.`tags` (`name`) VALUES ('Test Tag') 1 1 0
2 INSERT INTO `pottcast`.`episodes` (`title`) VALUES ('Test with Tag') 1 1 0
3 SELECT `EpisodesTag`.`tag_id` FROM `pottcast`.`episodes_tags` AS `EpisodesTag` WHERE `EpisodesTag`.`episode_id` = 9 0 0 0
What did I do wrong?

Try This :
In Episode Model:
public $hasAndBelongsToMany = array(
'Tag' => array(
'className' => 'Tag',
'joinTable' => 'episodes_tags',
'foreignKey' => 'episode_id',
'associationForeignKey' => 'tag_id',
'unique' => 'keepExisting',
));
In your controller:
$this->Episode->save(array(
'Episode' => array(
'title' => 'Test with Tag'
),
'Tag' => array(
'Tag' => $tagid
)
));
Ask if not work for you.

Related

CakePHP is linking models locally, but not on hosted copy?

I have employees who have one employer. In my employees table I have a field called employer_id. A report using this information is working on my local development setup, but not on my web hosting. I can't figure out what might be causing it not to.
When I try to pull up a list of all the employees I get the following error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column
'Employer.last_name' in 'where clause'
SQL Query: SELECT Employee.id, Employee.employer_id,
Employee.first_name, Employee.last_name, Employee.email,
Employee.webinar_id, Employee.questions,
Employee.correct_answers, Employee.required_to_pass,
Employee.status, Employee.role, Employee.sport,
Employee.program, Employee.created, Employee.modified FROM
sportrisk_wp.employees AS Employee WHERE
((Employee.last_name NOT LIKE '') AND (Employer.last_name NOT
LIKE '')) AND contain = '1' ORDER BY Employee.created DESC LIMIT
50
On my local copy when I change debug to 2 I see the following in the SQL log (it's joining the Employer):
SELECT Employee.id, Employee.employer_id,
Employee.first_name, Employee.last_name, Employee.email,
Employee.webinar_id, Employee.questions,
Employee.correct_answers, Employee.required_to_pass,
Employee.status, Employee.role, Employee.sport,
Employee.program, Employee.created, Employee.modified,
Employer.id, Employer.employer_id, Employer.company,
Employer.first_name, Employer.last_name, Employer.phone,
Employer.email, Employer.username, Employer.password,
Employer.usercode, Employer.subscriber, Employer.active,
Employer.admin, Employer.created, Employer.modified,
Employer.status FROM local.employees AS Employee LEFT JOIN
local.users AS Employer ON (Employee.employer_id =
Employer.id) WHERE ((Employee.last_name NOT LIKE '') AND
(Employer.last_name NOT LIKE '')) ORDER BY Employee.created
DESC LIMIT 50
I believe this is all the relevant code.
UsersController
// inside a larger function
// Load Employee Model
$this->loadModel('Employee');
// Base conditions - to hide some old records which do not contain name information.
$conditions = array(
"Employee.last_name NOT LIKE" => '',
"Employer.last_name NOT LIKE" => ''
);
// Find records & paginate
$this->paginate = array(
'limit' => 50,
'conditions' => array(
"AND" => $conditions
),
'order' => array('Employee.created DESC')
);
$employees = $this->paginate('Employee');
?>
Employee Model
<?php
class Employee extends AppModel
{
var $name = 'Employee';
var $recursive = 1;
var $belongsTo = array (
'Employer' => array (
'className' => 'Employer',
'foreignKey' => 'employer_id',
'order' => 'Employee.last_name ASC'
)
);
public $actsAs = array('Containable');
var $validate = array(
'email' => array(
'rule' => 'notBlank'
),
'first_name' => array(
'rule' => 'notBlank'
),
'last_name' => array(
'rule' => 'notBlank'
),
'usercode' => array(
'rule' => 'notBlank'
)
);
}
?>
Employer Model
<?php
class Employer extends AppModel
{
var $name = 'Employer';
var $recursive = -1;
var $hasOne = 'Supervisor';
var $hasMany = 'Employee';
public $useTable = 'users';
public $actsAs = array('Containable');
var $validate = array(
'email' => array(
'rule' => 'notBlank'
),
'password' => array(
'rule' => 'notBlank'
)
);
}
?>
I can't figure out why it isn't linking the Employer model in one environment but is on the other. I've done a straight copy of all the files.
SQL Queries
This is the query I get on the hosted site
SELECT `Employee`.`id`, `Employee`.`employer_id`, `Employee`.`first_name`, `Employee`.`last_name`, `Employee`.`email`, `Employee`.`webinar_id`, `Employee`.`questions`, `Employee`.`correct_answers`, `Employee`.`required_to_pass`, `Employee`.`status`, `Employee`.`role`, `Employee`.`sport`, `Employee`.`program`, `Employee`.`created`, `Employee`.`modified` FROM `sportrisk_wp`.`employees` AS `Employee` WHERE ((`Employee`.`last_name` NOT LIKE '') AND (`Employer`.`last_name` NOT LIKE '')) ORDER BY `Employee`.`created` DESC LIMIT 50
This is what I get on my local site (and what I'm looking for)
SELECT `Employee`.`id`, `Employee`.`employer_id`, `Employee`.`first_name`, `Employee`.`last_name`, `Employee`.`email`, `Employee`.`webinar_id`, `Employee`.`questions`, `Employee`.`correct_answers`, `Employee`.`required_to_pass`, `Employee`.`status`, `Employee`.`role`, `Employee`.`sport`, `Employee`.`program`, `Employee`.`created`, `Employee`.`modified`, `Employer`.`id`, `Employer`.`employer_id`, `Employer`.`company`, `Employer`.`first_name`, `Employer`.`last_name`, `Employer`.`phone`, `Employer`.`email`, `Employer`.`username`, `Employer`.`password`, `Employer`.`usercode`, `Employer`.`subscriber`, `Employer`.`active`, `Employer`.`admin`, `Employer`.`created`, `Employer`.`modified`, `Employer`.`status` FROM `local`.`employees` AS `Employee` LEFT JOIN `local`.`users` AS `Employer` ON (`Employee`.`employer_id` = `Employer`.`id`) WHERE ((`Employee`.`last_name` NOT LIKE '') AND (`Employer`.`last_name` NOT LIKE '')) ORDER BY `Employee`.`created` DESC LIMIT 50
END RESULT
In the end (after a chat with Ved) my paginate array ended up being this, and my report loads.
$this->paginate = array(
'contain' => array('Employer'),
'limit' => 50,
'conditions' => array(
"AND" => $conditions
),
'joins' => array( array( 'alias' => 'Employer', 'table' => 'users', 'type' => 'LEFT', 'conditions' => 'Employer.id = Employee.employer_id' ) ),
'fields' => array('Employee.first_name', 'Employee.last_name', 'Employee.email', 'Employee.webinar_id', 'Employee.questions', 'Employee.correct_answers', 'Employee.required_to_pass', 'Employee.status', 'Employee.role', 'Employee.sport', 'Employee.program', 'Employee.created', 'Employer.id', 'Employer.employer_id', 'Employer.company', 'Employer.first_name', 'Employer.last_name', 'Employer.email', 'Employer.active'),
'order' => array('Employee.created DESC')
);
I think you have forget to load associated data, use contain
// Find records & paginate
$this->paginate = array(
'contain' => array('Employer'),
'fields' => array('Employer.id'), // add more fields
'limit' => 50,
'conditions' => array(
"AND" => $conditions
),
'joins' => array( array( 'alias' => 'Employer', 'table' => 'users', 'type'
=> 'LEFT', 'conditions' => 'Employer.id = Employee.employer_id' ) ),
'order' => array('Employee.created DESC')
);

Cakephp 2 retrieve data where condition depends on child table

I am having a strange behavior which I do not understand with my cakephp 2.
In my Model 'Study' I have a has many relation:
class Study extends AppModel {
public $hasOne = array(
'SubjectFilter' => array(
'className' => 'Subject_filter',
'dependent' => true
)
);
public $hasMany = array(
'ExecutedStudyTable' => array(
'className' => 'ExecutedStudyTable',
'foreignKey' =>'study_id',
'dependent' => true,
),
);
The ExecutedtStudyTable Model looks like this:
class ExecutedStudyTable extends AppModel {
public $belongsTo= array(
'Study' => array(
'className' => 'Study',
'foreignKey' => 'study_id'
),
'User' => array(
'className' => 'User',
'foreignKey' => false,
'conditions' => array('ExecutedStudyTable.user_id = User.id')
)
);
}
When retrieving data everything looks good until I try to do this:
$studies = $this -> Study -> find('all',array(
'conditions' => array(
'Study.user_id' => $cId,
'OR'=>array(
array('Study.state'=>'active'),
array('Study.state'=>'rehearsal')
),
'SubjectFilter.studyCode'=>null,
'ExecutedStudyTable.user_id'=>$user
)
));
I get this error: Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'ExecutedStudyTable.user_id' in 'where clause'
Cakephp builds this query like this:
SELECT `Study`.`id`, `Study`.`user_id`, `Study`.`created`, `Study`.`modified`, `Study`.`started`, `Study`.`completed`, `Study`.`title`, `Study`.`description`, `Study`.`numberParticipants`, `Study`.`state`, `User`.`id`, `User`.`username`, `User`.`password`, `User`.`role`, `User`.`firstName`, `User`.`familyName`, `User`.`email`, `User`.`addressStreet`, `User`.`addressCity`, `User`.`addressZip`, `User`.`phone1`, `User`.`phone2`, `User`.`created`, `User`.`modified`, `User`.`passwordResetCode`, `User`.`passwordResetDate`, `User`.`sendUsernameDate`, `SubjectFilter`.`id`, `SubjectFilter`.`study_id`, `SubjectFilter`.`m`, `SubjectFilter`.`f`, `SubjectFilter`.`age18_24`, `SubjectFilter`.`age25_34`, `SubjectFilter`.`age35_44`, `SubjectFilter`.`age45_54`, `SubjectFilter`.`age55plus`, `SubjectFilter`.`studyCode` FROM `eyevido`.`studies` AS `Study` LEFT JOIN `eyevido`.`users` AS `User` ON (`Study`.`user_id` = `User`.`id`) LEFT JOIN `eyevido`.`subject_filters` AS `SubjectFilter` ON (`SubjectFilter`.`study_id` = `Study`.`id`) WHERE `Study`.`user_id` = 1402402538 AND ((`Study`.`state` = 'active') OR (`Study`.`state` = 'rehearsal')) AND `SubjectFilter`.`studyCode` IS NULL AND `ExecutedStudyTable`.`user_id` = 1130451831
The ExecutedStudyTable is not joined like the SubjectFilter and no alias is defined for the table. Why is this working correctly on the hasOne relation and not on the hasMany? Where do I make the mistake?
I appreciate your replies
If you want to filter a model by associated model's field, one way is by Joining tables.
Note: Mention proper table name for model ExecutedStudyTable. Also mention the join condition between two tables.
$studies = $this->Study->find('all',array(
'fields' => array('Study.*'),
'joins' => array(
array('table' => 'executed_study_table', // Table name
'alias' => 'ExecutedStudyTable',
'type' => 'INNER',
'conditions' => array(
'ExecutedStudyTable.<field name> = Study.<field>', // Mention join condition here
)
)
),
'conditions' => array(
'Study.user_id' => $cId,
'OR'=>array(
array('Study.state'=>'active'),
array('Study.state'=>'rehearsal')
),
'SubjectFilter.studyCode' => null,
'ExecutedStudyTable.user_id' => $user
),
'recursive' => -1
));

CakePHP get id value from input select

I have theCelebrant andCelebration models where aCelebrant has NCelebration. I am populating an input select in view of Celebration add that lists all registered Celebrant. Until then all right, the problem is that when you make the form submission the value of the select is coming as the number of select the viewing position, and I would like to come Celebrant ID.
Here's what I did:
CelebrationsController
$this->set('celebrants', $this->Celebration->Celebrant->find('list'));
view/Celebrations/add
echo $this->Form->input('celebrant_id', array('type' => 'select', 'label'=>'Celebrante', 'options' => $celebrants, 'empty' => __( 'Selecione um Celebrante' )));
model/Celebrant
var $hasMany = array(
'Celebration' => array(
'className' => 'Celebration',
'foreignKey' => 'celebrant_id'
)
);
model/Celebration
var $belongsTo = array(
'Celebrant' => array(
'className' => 'Celebrant',
'foreignKey' => 'celebrant_id'
)
);

CakePHP - Model Associations/ binding

I would like to bind models, mostly because the datasources change from one model to the other. Let's say I have the following query :
SELECT ext.col1, ext.col2, ext.col3, COUNT(*) Link Count
FROM `external.`Media` ext
INNER JOIN `internal`.`links` l ON ext.someId = links.id
INNER JOIN `internal`.tags t ON l.tag_id = t.tag_id
WHERE t.parent_tag_id = 1098
AND t.metadata = 'someValue'
GROUP BY ext.col1, ext.col2, ext.col3
So I have 3 models from which data is coming from. Currently I have the following in the Media model..
public $belongsTo = array(
'Links' => array(
'className' => 'Links',
'foreignKey' => 'someId',
),
'Tag' => array(
'className' => 'Tag'
)
);
Then in the other models :
Tag Model
public $useTable = "tags";
public $name = 'Tag';
public $hasAndBelongsToMany = array(
'TagLink' => array(
'className' => 'TagLink',
'foreign_key' => 'tag_id',
'conditions' => array(
'Tag.parent_tag_id' => 1098
)
)
);
Link Model
public $useTable = "tag_links";
public $name = 'TagLink';
public $hasAndBelongsToMany = array(
'Tag' => array(
'className' => 'Tag',
'foreign_key' => 'tag_id'
)
);
Then back in the Media model , I use the find expecting to have the model binding set up :
return $this->find("all", array(
'conditions' => array(
'Tag.metadata' => $username
)
));
This generates an error :
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Tag.metadata' in 'where clause'
I am assuming that this is happening because the table is not found
[SQL Generated]
SELECT `Media`.`media_id`, `Media`.`mediatype`, `Media`.`created`, `Media`.`active_flag`,
`Media`.`mediafile`, `Media`.`fallbackfile`, `Media`.`setupId`, `Media`.`externalId`,
`Media`.`width`, `Media`.`height`, `Media`.`brands`, `Media`.`languages`, `Media`.`products`,
`Media`.`projects`, `Media`.`affiliates`, `Media`.`description`, `Media`.`linking_code` FROM
`external`.`ext_media_gallery` AS `Media` WHERE `Tag`.`metadata` = 'testuser' AND
`MediaGallery`.`active_flag` = 'active' AND ((FIND_IN_SET('testuser', `Media`.`affiliates`)) OR
(`Media`.`affiliates` = ''))
This basically shows that the binding is incorrect/not happening
Basically I want to replace the following to avoid using joins on the fly since these would need to apply for every query.. hence the model binding..
This works for me, but I want to convert it to do it using model binding :
return $this->find("all", array(
"joins" => array(
array(
"table" => "internal.links",
"alias" => "Link",
"type" => "INNER",
"conditions" => array(
"Link.id = Media.someId"
)
),
array(
"table" => "internal.tags",
"alias" => "Tag",
"type" => "INNER",
"conditions" => array(
"Tag.tag_id = Link.tag_id",
"Tag.parent_tag_id = 3214",
"Tag.metadata" => $username
)
),
)
));

Adding associated data in model::beforeSave

I'm trying to add associated data to during cakePHPs beforeSave() but it looks like cakePHP is ignoring it.
<?php
App::uses("AppModel", "Model");
class Recipe extends AppModel {
public $hasMany = array('Ingredient');
public function beforeSave($options=array()) {
if(!isset($this->data["Ingredient"])) {
Debugger::log("data[Ingredient] didn't exist! adding some ingredients...");
$this->data["Ingredient"] = array(
array(
'Ingredient' => array(
'name' => 'Gluten Free Flour',
'amount' => '12 cups'
)
),
array(
'Ingredient' => array(
'name' => 'Sugar',
'amount' => '50 cups'
)
),
array(
'Ingredient' => array(
'name' => 'A shoe',
'amount' => 'Just one'
)
),
);
}
Debugger::log("Saving Data: ");
Debugger::log($this->data, 7, 10);
return true;
}
}
In my RecipesController I have this:
public function test_save() {
//Test saving With the ingredients in the recipe
Debugger::log("Saving w/ ingredients");
Debugger::log($this->Recipe->saveAssociated(array(
'Recipe' => array(
'name' => 'Test Recipe 1'
),
'Ingredient' => array(
array(
'name' => 'Test Ingredient 1 for Test Recipe 1',
'amount' => 'a few'
),
array(
'name' => 'Test Ingredient 2 for Test Recipe 1',
'amount' => 'a lot'
)
)
)));
Debugger::log("Saving w/o ingredients");
Debugger::log($this->Recipe->saveAssociated(array(
'Recipe' => array(
'name' => 'Test Recipe 2'
)
)));
}
The save with ingredients works, but the save without ingredients doesn't, the data in Recipe->data before beforeSave returns is this:
array(
'Recipe' => array(
'name' => 'Test Recipe 1'
),
'Ingredient' => array(
(int) 0 => array(
'Ingredient' => array(
'name' => 'Test Ingredient 1 for Test Recipe 1',
'amount' => 'a few'
)
),
(int) 1 => array(
'Ingredient' => array(
'name' => 'Test Ingredient 2 for Test Recipe 1',
'amount' => 'a lot'
)
)
)
)
And when Ingredient is added in the before save:
array(
'Recipe' => array(
'name' => 'Test Recipe 1'
),
'Ingredient' => array(
(int) 0 => array(
'Ingredient' => array(
'name' => 'Gluten Free Flour',
'amount' => '12 cups'
)
),
(int) 1 => array(
'Ingredient' => array(
'name' => 'Sugar',
'amount' => '50 cups'
)
),
(int) 2 => array(
'Ingredient' => array(
'name' => 'A shoe',
'amount' => 'Just one'
)
)
)
)
When the data is in the array before I call a save method, it'll work just fine, only when I try and add the associated data in the beforeSave.
From the documentation:
Be sure that beforeSave() returns true, or your save is going to fail.
http://book.cakephp.org/2.0/en/models/callback-methods.html#beforesave
Edited: I tested your code and you are right, related data added in beforeSave() is being ignored. This is happening because you cannot add or modify related data in a model's beforeSave(). It means that, in your Recipe's beforeSave(), you cannot neither add Ingredients nor modify the Ingredients you previously attached to Recipe in your controller.
In your Recipe's beforeSave() you can only modify Recipe data.
Is this a bug? It is not, according to Mark Story (creator of CakePHP):
Its a feature, as currently saveAll only lets you use beforeSave to
modify the record that's about to be saved, not the full data set. I
personally don't feel that should change, but its something that could
be discussed.
This statement is about saveAll() but the important part is what he said about beforeSave(). Check the full thread: https://github.com/cakephp/cakephp/issues/1765

Resources