質問

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.

役に立ちましたか?

解決

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.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top