Domanda

I'm having problems inserting objects in a one to many relationship. This is my code:

/** 
 * Migrations 
 **/
// Create user__groups table
Schema::create('user__groups', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name', 50);
});

// Create users table
Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->timestamps(); // created_at, updated_at DATETIME

    $table->string('username', 40);
    $table->string('password', 20);

    $table->integer('user_group_id')->unsigned();
    $table->foreign('user_group_id')->references('id')->on('user__groups');
});

/** 
 * Models 
 **/
class User extends Ardent {
    protected $table = 'users';
    public $timestamps = true;
    protected $hidden = array('password');
    public $autoPurgeRedundantAttributes = true;
    protected $fillable = array(*);

    // Validation rules for fields in this entity
    public static $rules = array(
        'username'              => 'required|unique:users',
        'password'              => 'required|alpha_dash|min:6|max:20|confirmed',
        'password_confirmation' => 'required|alpha_dash|min:6|max:20'
    );

    // Relations
    public static $relationsData = array(
        'userGroup' => array(self::BELONGS_TO, 'UserGroup')
    );

    // Model mock data for test purposes
    public static $factory = array(
        'username'              => 'string',
        'password'              => '123123',
        'password_confirmation' => '123123',
        'user_group_id'         => 'factory|UserGroup'
    );
}

class UserGroup extends Ardent {
    protected $table = 'user__groups';
    public $timestamps = false;
    public $autoPurgeRedundantAttributes = true;
    protected $fillable = array('*');

    // Validation rules for fields in this entity
    public static $rules = array(
        'name' => 'required|unique:user__groups|alpha_dash'
    );

    // Relations
    public static $relationsData = array(
        'users' => array(self::HAS_MANY, 'User')
    );

    // Model mock data for test purposes
    public static $factory = array(
        'name' => 'string'
    );
}

PHPUnit test

public function test_assignUserToGroup() {
    /* @var $user User */
    $user = FactoryMuff::instance('User');

    // Test assigning user to group 1
    $group1 = FactoryMuff::create('UserGroup');
    $this->assertTrue($group1->users()->save($user) !== false, "User model did not save!".$user->errors());

    // Test assigning user to group 2 (this fails)
    $group2 = FactoryMuff::create('UserGroup');
    $this->assertTrue($group2->users()->save($user) !== false, "User model did not update!".$user->errors()); // <-- The save method always returns false
}

The testrun will reflect that the user object will not be updated. Why? What am I doing wrong? I would expect the code below // Test assigning user to group 2 to perform an update on the existing object, but instead DB::getQueryLog() only displays selects and inserts. This is really annoying.

-- Edit --

It's actually validation that's stopping me. I added a call to Model->errors() in the test above. This pointed out that after save, the username contained in the User object no longer was unique - because it found itself in the database (WTF?). It also said that the password_confirmation field was marked required, but was empty - because it was removed upon last save.

This is just stupid and I do not know if it is Eloquent, Ardent or me who's in fault here. If it was valid before save, it should be valid after save also - right? I will change the title of the question to reflect the real cause of the problem.

È stato utile?

Soluzione

The only solution I've found to this problem, is to not use the 'unique' and 'confirmed' validation rules. I will handle validation for these upon form submission only.

I'm actually not very happy with this solution, so I came up with a slightly different approach:

// Validation rules for fields in this entity
public static $rules = array(
    'username'              => 'required|alpha_dash|min:4|max:40',
    'password'              => 'required|alpha_dash|min:6|max:20',
    'password_confirmation' => ''
);

// Extra validation for user creation
public static $onCreateRules = array(
    'username'              => 'required|alpha_dash|min:4|max:40|unique:users',
    'password'              => 'required|alpha_dash|min:6|max:20|confirmed'
);

Here I split the validation rules into two static arrays, and instead of extending the class from Ardent directly I extend it from a custom class:

use LaravelBook\Ardent\Ardent;
class ModelBase extends Ardent {
    public function save(array $rules = array(), array $customMessages = array(), array $options = array(), Closure $beforeSave = null, Closure $afterSave = null) {
        if (!$this->exists && isset(static::$onCreateRules)) {
            $rules = array_merge($rules, static::$onCreateRules);
        }
        return parent::save($rules, $customMessages, $options, $beforeSave, $afterSave);
    }
}

This overrides the Ardent save method and applies the $onCreateRules for validation ONLY if this entity does not previously exist. :-)

I still think Eloquents validation engine is broken. A call to validate() should only validate dirty fields for one. Secondly it should automatically exclude the current entity id from a unique check. So in any case the solution I present here is a workaround.

I hope someone from Laravel sees this and finds it in their heart to fix it.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top