Saving a Join Model
I've been reading the cookbook for a while now and still don't get how I'm supposed to do this:
My original problem was this: A related Model isn't being validated
From RabidFire's commment:
If you want to count the number of Category models that a new Post is associated with (on save), then you need to do this in the beforeSave function as I've 开发者_开发知识库mentioned. As you've currently set up your models, you don't need to use the multiple rule anywhere. If you really, really want to validate against a list of Category IDs for some reason, then create a join model, and validate category_id with the multiple rule there.
Now, I have these models and are now validating. The problem now is that data isn't being saved in the Join Table:
class Post extends AppModel {
var $name = 'Post';
var $hasMany = array(
'CategoryPost' => array(
'className' => 'CategoryPost'
)
);
var $belongsTo = array(
'Page' => array(
'className' => 'Page'
)
);
class Category extends AppModel {
var $name = 'Category';
var $hasMany = array(
'CategoryPost' => array(
'className' => 'CategoryPost'
)
);
class CategoryPost extends AppModel {
var $name = 'CategoryPost';
var $validate = array(
'category_id' => array(
'rule' => array('multiple', array('in' => array(1, 2, 3, 4))),
'required' => FALSE,
'message' => 'Please select one, two or three options'
)
);
var $belongsTo = array(
'Post' => array(
'className' => 'Post'
),
'Category' => array(
'className' => 'Category'
)
);
This is the new Form:
<div id="content-wrap">
<div id="main">
<h2>Add Post</h2>
<?php echo $this->Session->flash();?>
<div>
<?php
echo $this->Form->create('Post');
echo $this->Form->input('Post.title');
echo $this->Form->input('CategoryPost.category_id', array('multiple' => 'checkbox'));
echo $this->Form->input('Post.body', array('rows' => '3'));
echo $this->Form->input('Page.meta_keywords');
echo $this->Form->input('Page.meta_description');
echo $this->Form->end('Save Post');
?>
</div>
<!-- main ends -->
</div>
The data I am producing from the form is as follows:
Array
(
[Post] => Array
(
[title] => 1234
[body] =>
1234
)
[CategoryPost] => Array
(
[category_id] => Array
(
[0] => 1
[1] => 2
)
)
[Page] => Array
(
[meta_keywords] => 1234
[meta_description] => 1234
[title] => 1234
[layout] => index
)
)
UPDATE: controller action //Controller action
function admin_add() {
// pr(Debugger::trace());
$this->set('categories', $this->Post->CategoryPost->Category->find('list'));
if ( ! empty($this->data)) {
$this->data['Page']['title'] = $this->data['Post']['title'];
$this->data['Page']['layout'] = 'index';
debug($this->data);
if ($this->Post->saveAll($this->data)) {
$this->Session->setFlash('Your post has been saved', 'flash_good');
$this->redirect($this->here);
}
}
}
UPDATE #2: Should I just do this manually?
The problem is that the join tables doesn't have things saved in it. Is there something I'm missing?
removed update #3
Join Table schema:
CREATE TABLE IF NOT EXISTS `category_posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
Update: #4
I will put everything about my application here in the hopes of getting to do what I want to do.
// Post Model
class Post extends AppModel {
var $name = 'Post';
var $hasMany = array(
'CategoryPost' => array(
'className' => 'CategoryPost'
)
);
var $belongsTo = array(
'Page' => array(
'className' => 'Page'
)
);
var $actsAs = array('Containable');
var $virtualFields = array(
'date_posted' => 'DATE_SUB(Post.created, INTERVAL 7 DAY)'
);
var $order = array('Post.modified' => 'desc');
var $validate = array(
'title' => array(
'rule' => 'notEmpty'
),
'body' => array(
'rule' => 'notEmpty'
)
);
function getFeed() {
if ($posts = $this->find('all', array('limit' => 20, 'order' => 'Post.created DESC'))) {
return $posts;
}
return FALSE;
}
function getRecentPosts() {
$conditions = array(
'Post.created < (curdate() + interval 7 day)',
);
return $this->find('all', array('limit' => 8, 'conditions' => $conditions));
}
}
// CategoryPost Model
class CategoryPost extends AppModel {
var $name = 'CategoryPost';
var $validate = array(
'category_id' => array(
'rule' => array('multiple', array('in' => array(1, 2, 3, 4))),
'required' => FALSE,
'message' => 'Please select one, two or three options'
)
);
var $belongsTo = array(
'Post' => array(
'className' => 'Post'
),
'Category' => array(
'className' => 'Category'
)
);
var $actsAs = array('Containable');
}
class Page extends AppModel {
var $name = 'Page';
var $order = array('Page.modified' => 'desc');
var $hasOne = array(
'Post' => array(
'className' => 'Post'
));
var $hasMany = array(
'Snippet' => array(
'className' => 'Snippet'
));
var $validate = array(
'title' => array(
'rule' => 'notEmpty'
),
'uris' => array(
'slugged' => array(
'rule' => '/^[a-z0-9-_]+$/i',
'message' => 'This field should only contain characters, numbers, dashes and underscores'
),
'uniqueUrl' => array(
'rule' => array('uniqueUrl'),
'message' => 'A page has already acquired this url'
)
),
'meta_keywords' => array(
'rule' => 'notEmpty'
),
'meta_description' => array(
'rule' => 'notEmpty'
),
'layout' => array(
'rule' => 'notEmpty'
)
);
}
// Form
<div id="main">
<h2>Add Post</h2>
<?php echo $this->Session->flash();?>
<div>
<?php
echo $this->Form->create('Post');
echo $this->Form->input('Post.title');
echo $this->Form->input('CategoryPost.category_id', array('multiple' => 'checkbox'));
echo $this->Form->input('Post.body', array('rows' => '3'));
echo $this->Form->input('Page.meta_keywords');
echo $this->Form->input('Page.meta_description');
echo $this->Form->end('Save Post');
?>
</div>
<!-- main ends -->
</div>
// Posts#admin_add
function admin_add() {
$this->set('categories', $this->Post->CategoryPost->Category->find('list'));
if ( ! empty($this->data)) {
$this->data['Page']['title'] = $this->data['Post']['title'];
$this->data['Page']['layout'] = 'index';
if ($this->Post->saveAll($this->data, array('validate' => 'first'))) {
$this->Session->setFlash('Your post has been saved', 'flash_good');
$this->redirect(array('action' => 'admin_add'));
}
}
}
// Table structure
CREATE TABLE IF NOT EXISTS `posts` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`page_id` int(11) NOT NULL,
`title` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`uri` varchar(127) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`body` text COLLATE utf8_unicode_ci,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=163 ;
CREATE TABLE IF NOT EXISTS `pages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`uris` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`meta_keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`meta_description` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`layout` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=164 ;
CREATE TABLE IF NOT EXISTS `category_posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=36 ;
Your data is not being saved because you're trying to save an array into a field.
[CategoryPost] => Array
(
[category_id] => Array
(
[0] => 1
[1] => 2
)
)
The form of the data should look like this:
[CategoryPost] => Array
(
[0] => Array
(
[category_id] => 1
)
[1] => Array
(
[category_id] => 2
)
)
You could use the following code to do this:
// Correcting data form of CategoryPost
$categoryPosts = array();
foreach ($this->data['CategoryPost']['category_id'] as $categoryId) {
$categoryPost = array(
'category_id' => $categoryId
);
array_push($categoryPosts, $categoryPost);
}
$this->data['CategoryPost'] = $categoryPosts;
This code can be placed in the controller before the saveAll
call. If you find that you're using this code in multiple places, you can refactor it into the model's beforeSave
:
function beforeSave() {
if (isset($this->data['CategoryPost']['category_id']) && is_array($this->data['CategoryPost']['category_id'])) {
... // above code
}
}
Post the schema of your join table (table name and fields) for a possibly better alternative.
Alright, it took me some brain racking but I finally figured it out. Your last comment kind of put everything in place. Here's the simple fix:
In your view:
echo $this->Form->input('CategoryPost.0.category_id', array('multiple' => 'checkbox'));
This should cause the data to be of the following form:
[CategoryPost] => Array
(
[0] => Array
(
[category_id] => Array
(
[0] => 1
[1] => 2
)
)
)
You need to have it in this form because of the hasMany relationship between Post
and CategoryPost
- or it won't even validate. After you make this change, the beforeSave
function will be called. NOW make the necessary changes to beforeSave to make it work! Debugging $this->data
will help. I leave this part to you, cause I have another better alternative:
1) Shift the multiple
validation rule to the Post
model:
class Post extends AppModel {
...
var $validate = array(
'category_id' => array(
'rule' => array('multiple', array('in' => array(1, 2, 3, 4))),
'required' => false,
'message' => 'Please select one, two or three options'
)
);
...
}
2) Change the view accordingly:
echo $this->Form->input('Post.category_id', array('multiple' => 'checkbox'));
3) Shift the beforeSave
to the Post
model:
function beforeSave() {
if (isset($this->data['Post']['category_id']) && is_array($this->data['Post']['category_id'])) {
$categoryPosts = array();
foreach ($this->data['Post']['category_id'] as $categoryId) {
$categoryPost = array(
'category_id' => $categoryId
);
array_push($categoryPosts, $categoryPost);
}
$this->data['CategoryPost'] = $categoryPosts;
}
return true;
}
This should keep things nice and smooth. Test out both alternatives and let me know if it works! :D
I think that the element for multiple choice should have name like this:
echo $this->Form->input('Category', array('multiple' => 'checkbox'));
For better results create a backup copy of your view file and recreate it using the console bake script.
精彩评论