phpcakephpcakephp-2.3

saveAll() Failing with CakePHP 2.3.1


I am building a CakePHP 2.3 application, and can't for the life of me figure out why saveAll keeps failing.

I have the following models:

I have no problem saving my Artist model with the save() function, however as soon as I try to use the saveAll method, Cake seems to fail validation - which is crazy because I have even totally removed all my validation rules from my models.

Here's my view, nothing crazy:

<?php
    echo $this->Form->create('Artist', array('inputDefaults' => array('class' => 'input-block-level')));
    echo $this->Form->input('Artist.name', array('label' => __('Name')));
    echo $this->Form->input('Artist.artist_section_id', array('label' => __('Section'), 'empty' => true));
?>
<label>Tags</label>
<div class="checkboxes">
    <?php echo $this->Form->input('Tag', array('label' => false, 'multiple' => 'checkbox', 'class' => false));; ?>
</div>
<?php
    echo $this->Form->input('Artist.website', array('label' => __('Website')));
    echo $this->Form->input('ArtistPreview.0.url', array('label' => __('Artist Preview')));
    echo $this->Form->input('Artist.description', array('label' => __('Description')));
?>
<?php

    echo $this->Form->submit(__('Add'), array('class' => 'btn btn-primary btn-large', 'div' => false)) . ' ';
    echo $this->Form->button(__('Cancel'), array('type' => 'button', 'class' => 'btn btn-large', 'div' => false, 'onclick' => 'closeDialog()'));
    echo $this->Form->end();
?>

And here's my add controller method:

public function add() {
    $this->layout = 'form';

    if (!$this->request->is('ajax')) {
        throw new ForbiddenException();
    }

    if ($this->request->is('post')) {
        $this->setData($this->request->data['Artist'], array(
            'user_id' => AuthComponent::user('id'),
            'date' => $this->Date->gmt()
        ));
        if ($this->Artist->saveAll($this->request->data)) {
            $this->redirectAjax('/artists/view/' . $this->Artist->id);
        }
    }

    $this->set(array(
        'title_for_layout' => 'Add Artist',
        'artistSections' => $this->Artist->ArtistSection->find('list', array('order' => 'name')),
        'tags' => $this->Artist->Tag->find('list', array('order' => 'name'))
    ));
}

As I mentioned above, the saveAll method fails everytime. I have inspected what is getting output and Cake is outputting a bunch of blank <div>s with the error-message class, but there is no message. Every field on the form gets one of these divs.

Now, when I change my code to use the save function instead of saveAll, everything gets saved properly except the ArtistPreview data, which is to be expected. Even my HABTM Tags are being saved properly, and nothing fails validation.

I have been using Cake since the 1.3 days so I'm quite familiar with it, and have even done exactly what I'm asking in previous projects. I'm leaning towards this being a bug, unless I'm missing something here. Any ideas?


EDIT

I have also tried the saveAssociated method, along with setting the deep key to true when trying to save. This will let me save the artist, but it will not save the related data (ArtistPreview).


EDIT 2

As requested, here are the two tables:

-- -----------------------------------------------------
-- Table `artists`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `artists` ;

CREATE  TABLE IF NOT EXISTS `artists` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
  `user_id` INT UNSIGNED NULL ,
  `artist_section_id` INT UNSIGNED NULL ,
  `artist_status_id` INT UNSIGNED NULL ,
  `name` VARCHAR(200) NULL ,
  `website` VARCHAR(500) NULL ,
  `description` TEXT NULL ,
  `date` DATETIME NULL ,
  `comment_count` INT UNSIGNED NOT NULL DEFAULT 0 ,
  PRIMARY KEY (`id`) ,
  INDEX `FK_artists_users_idx` (`user_id` ASC) ,
  INDEX `FK_artists__artist_sections_idx` (`artist_section_id` ASC) ,
  INDEX `FK_artists__artist_statuses_idx` (`artist_status_id` ASC) ,
  CONSTRAINT `FK_artists__users`
    FOREIGN KEY (`user_id` )
    REFERENCES `users` (`id` )
    ON DELETE SET NULL
    ON UPDATE CASCADE,
  CONSTRAINT `FK_artists__artist_sections`
    FOREIGN KEY (`artist_section_id` )
    REFERENCES `artist_sections` (`id` )
    ON DELETE SET NULL
    ON UPDATE CASCADE,
  CONSTRAINT `FK_artists__artist_statuses`
    FOREIGN KEY (`artist_status_id` )
    REFERENCES `artist_statuses` (`id` )
    ON DELETE SET NULL
    ON UPDATE CASCADE)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `artist_previews`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `artist_previews` ;

CREATE  TABLE IF NOT EXISTS `artist_previews` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
  `artist_id` INT UNSIGNED NULL ,
  `url` VARCHAR(500) NULL ,
  `date` DATETIME NULL ,
  PRIMARY KEY (`id`) ,
  INDEX `FK_artist_previews__artists_idx` (`artist_id` ASC) ,
  CONSTRAINT `FK_artist_previews__artists`
    FOREIGN KEY (`artist_id` )
    REFERENCES `artists` (`id` )
    ON DELETE CASCADE
    ON UPDATE CASCADE)
ENGINE = InnoDB;

Here is my setData function, which just merges two arrays. Instead of using hidden fields, I set fields in the controller which aren't populated by user input:

public function setData(&$original, $new) {
    $original = array_merge($original, $new);
}

Solution

  • So I finally figured this out, and wow was it a rookie mistake. I think I must've been half asleep yesterday when I was coding. The problem was in my model:

    class Artist extends AppModel {
        public $belongsTo = array(
            'User',
            'ArtistSection',
            'ArtistStatus'
        );
    
        public $hasMany = array(
            'ArtistPicture',
            'Artist' // <--- problem was here, had 'Artist' instead of 'ArtistPreview'
        );
    
        public $hasAndBelongsToMany = array(
            'Tag'
        );
    
        public function __construct($id = false, $table = null, $ds = null) {
            parent::__construct($id, $table, $ds);
    
            $this->validate = array(
                'name' => array(
                    'rule' => 'notEmpty',
                    'message' => __('Artist name cannot be blank.')
                ),
                'website' => array(
                    'rule' => 'url',
                    'message' => __('Must be a valid URL.'),
                    'allowEmpty' => true
                ),
                'artist_section_id' => array(
                    'rule' => 'notEmpty',
                    'message' => __('Section is required.')
                )
            );
        }
    }
    

    Thank you to those of you who took the time to look at this question, even though I posted the wrong information.