how can i solve the unique field error in laravel? - php

'call_signs' => [
'required', 'array', 'min:1',
'call_signs.*.call_sign' => [
Rule::unique('call_signs', 'call_sign')->whereNull('deleted_at')->ignore($user->id, 'user_id')
]
],
this is my unique code
it gives me the error
call_signs is an array
BadMethodCallException
Method Illuminate\Validation\Validator::validateUnique:callSigns,callSign,"12",userId,deletedAt,"NULL" does not exist.
my Callsign table has softDelete

Laravel's Unique Validation Rule provides only ignore method and not whereNull method. Please note that whereNull method is part of Database Query Builder.
Also, it is usually not good to check for uniqueness on soft deletes on tables. You should consider using hard delete for such use case.
Anyway, if you want to use for some reason, you can create your own unique custom validation rule. You can read more about it here, contains example also.

Related

Laravel issue with updating hasMany relationship records

I have a User model which has a hasMany relationship to a Brands model and I am having issues updating the Brands for a user properly.
I have a form which allows a user to enter / delete / update their own Brands in text fields, the current method i am using to update the users Brands after they have entered them all or edited them is to delete all existing Brands associated with the User then loop over the values, create the Brand model and then 'saveMany' ... But i seem to be getting a constraint violation when adding ... I am wondering if there is a better way to do this;
My User model has the following;
public function brands()
{
return $this->hasMany('Brands::class');
}
Then in my controller I have the following code to update the Brands;
$user->brands()->delete();
foreach ($request['brands'] as $brand) {
$brandArray[] = new Brand([
'name' => $brand['name'],
'rating' => $brand['rating'],
]);
}
!empty($brandArray) && $user->brands()->saveMany($brandArray);
Is there a better way of doing this?
Let's separate things into three parts:
# constraint key violation:
If you've added foreign key constraint on another table, and you need to delete the brands, you should also delete all those related data constrained by your foreign key.
# design
If deleting brand related data is not possible, then maybe we can think about if there is a better design. Maybe we could add a hook on the frontend that call a DELETE API whenever certain data is removed by the user.
# query
If the brand has some unique key, you could use upsert instead of saveMany. That will be more efficient.
# conclusion
I would suggest deleting brands by hooks on the frontend whenever a brand is removed by users, and use upsert to deal with create and update stuff
It looks fine to me. But from my point of view,
Instead of deleting all fields, then creating them. You can use updateOrCreate eloquent method inside your foreach.
And in place of foreach, you can use the map method.
Since you only want to delete all the previous brands of the user, and create brand new brands without editing anything, you can simply follow the concept below:
In your controller:
// Step 1) load all the user brands
$user->load('brands');
// Step 2) delete all the brands with this user
$user->brands()->delete();
// Step 3) create all the new brands.
$user->brands()->createMany($request['brands']);
/* Concept of how createMany works:
The brand array should look similar to this, you can do dd($request['brands']) to verify
$user->brands()->createMany([
['name' => 'A new name.', 'rating' => '1'],
['name' => 'Another new name.', 'rating' => '2'],
]);
*/
You can find more examples in laravel documentation on createMany method: https://laravel.com/docs/9.x/eloquent-relationships#the-create-method
You can also go a step further and validate the array from the request:
$data = request()->validate([
'brands.*' => 'required',
]);
$user->brands()->createMany($data['brands']);
Hope this helps, good luck!
When you save everything you have for the user, do this under:
foreach ($request['brands'] as $brand) {
Brand::updateOrCreate(
[
'name' => $brand['name'],
'rating' => $brand['rating'],
],
[
'name' => $brand['name'],
'rating' => $brand['rating'],
]
);
}

Validation in Laravel on relationship

In Laravel, I have a persons model that has a many-to-many relationship with its group. The person's name needs to be unique in its group but not on the persons table. How would ensure that?
My validation is done in the controller store method using $request->validate(['name => ...
I currently save the new person in a controller using simply - Person::create([...
My simple approach is using a composite primary keys on pivot table and use basic exception handling like try catch stuff whenever inserting data is fail due to migration
$table->foreignId('group_id') // Add any modifier to this column
$table->foreignId('person_id') // Add any modifier to this column
$table->primary(['group_id', 'person_id']);
If you want to do it on controller, make sure to setup relationship. Then just use Rule::notIn() Validation
'name' => [
'required',
Rule::notIn(/* put your logic here */),
],
You can use 'exist' rule in Laravel Validation like that:
'name' => 'exists:group,name,person_id,'.$id
For more info you can check here:
https://laravel.com/docs/9.x/validation#rule-unique

Testing insert methods by using factory make of Laravel with a PostgreSQL reserved id

Our Laravel app uses PostgreSQL and before we insert a register in a table we reserve the next valid id by calling PostgreSQL nextval function.
I want to test the insert method of UserRepository. As our database expects to receive an id I would need to generate it also in the test method that tests the insert method, so I use Laravel's factories make method like this to create a fake id:
$userModel = UserModel::factory()->make([
'id' => $this->userRepository->nextValId()
// Another solution 'id' => $this->faker->unique()->randomNumber(5)
]);
$this->userRepository->insert($userModel->toArray());
This might not seem too complex, but imagine I now have an insertMany method that inserts several users at once, and I want to test this insertMany method. In this case the creation with Laravel's factory() gets more complicated. To start off, this code wouldn't work now as the ids of all users would be the same:
$collection = UserModel::factory(4)->make([
'id' => $this->userRepository->nextValId()
// Another solution 'id' => $this->faker->unique()->randomNumber(5)
]);
$this->userRepository->insertMany($collection->toArray());
So I have to rewrite it like this:
$collection = UserModel::factory(4)->make();
$users = $collection->toArray();
$users[0]['id'] = $this->userRepository->nextValId();
$users[1]['id'] = $this->userRepository->nextValId();
$users[2]['id'] = $this->userRepository->nextValId();
$users[3]['id'] = $this->userRepository->nextValId();
$this->userRepository->insertMany($users);
So the question is: is there a one-liner for this use case? Is there a way to tell inside the array of the make that a key has to be different for every model created by the factory? (In this case, I would want $this->userRepository->nextValId() to act for each one of the models created.)
Of course I do not consider using Laravel's factories create method as it would create the register already in the database table and the later insertion would throw a duplicate key exception.
Side note: this is a fake example, not real, so it is possible that there might be a little bug. The code pasted is not proved. Mine is different, but the idea is the same as the one in the example used.
I have found a solution using Laravel's factory sequences:
https://laravel.com/docs/9.x/database-testing#sequences
This would be the cleanest code in my opinion:
$collection = UserModel::factory()->count(4)->state(new Sequence(
fn ($sequence) => ['id' => $this->userRepository->nextValId()]
// Or this using DatabaseTransactions trait: fn ($sequence) => ['id' => $sequence->index + 1]
))->make();
$this->userRepository->insertMany($collection->toArray());

Laravel - assert not soft deleted? Inverse? - Laravel Tests

Laravels test suite has an assertion called
$this->assertSoftDeleted()
But is there the inverse? To assert that something is not soft deleted?
I've tried assertDatabaseHas but doesn't work.
I have done it this way $this->assertTrue(! $goal->fresh()->trashed());
Works perfect.
But is there another, better, more ---elegant--- and laravel's way?
Something not being soft deleted just means that the deleted_at column is null. You could use the assertDatabaseHas method where that column is empty:
$this->assertDatabaseHas('goals', [
'id' => $goal->id,
'deleted_at' => null
]);
If you wanted to make this a bit more dynamic, you could extend your base TestCase class with a custom method that will do this for you and allow you to keep your tests with a readable assertion name, and additionally do things for you like find the appropriate table/column names automatically:
public function assertModelNotSoftDeleted(Model $model)
{
return $this->assertDatabaseHas($model->getTable(), [
$model->getKeyName() => $model->getKey(),
$model::DELETED_AT => null,
]);
}

Laravel 5.2 Unique Validation always invalidate on UPDATE

I have been trying to figure this one out for quite a whiale and have gone through every post from here and Laracast to figure this out but in vein.
I have done this before and it worked but I am not quite sure why it doesn't now.
Basically setting up a rule in your form request should follow the format below:
<?php
class MyFormRequest extends Request{
public function rules(){
return [
'field' => 'required|unique:table_name:field,' . $this->input('field');
];
}
}
This to me should work but according to my experience at the moment doesn't. I have tried to separate the rules by checking the incoming request method and assign rules based on whether the request is an update or create.
Shouldn't what I have suffice for this requirement? What would be the best way of re-using my FormRequest but making sure that I am able to validate uniqueness of as many table fields as I want regardless of their data types because I am sensing that perhaps this is related to the fact that Laravel seems to be doing an aggregate on the field being validated and if it is not an integer for example it will keep on displaying the error message.
Another possible way of solving this is if I implement my own validation method for the fields I am interested to validate. How would I go about doing this?
This rule works only if the primary key of "table_name" is "id" but if it is different then you have to write it as the fourth parameter to unique rule as follows:
<?php
class MyFormRequest extends Request{
public function rules(){
return [
'field' => 'required|unique:table_name:field,' . $this->input('field').','.'primary_key_field';
];
}
}
hope it help you !!!
You need to tell the validator which rows to exclude from duplicate search.
If your primary key is id, you need to pass its value:
'field' => 'required|unique:table_name,field,' . $this->input('id')
Your code has also some other errors:
'field' => 'required|unique:table_name:field,' . $this->input('field');
^ ^
The rule "unique" needs aditional arguments when updating. Those aditional arguments will be responsible for ignore the actual registry you're working on. If not applied, this rule will always invalidate, since the registry always will be found on database.
Your thirdy argument should be the ID you want to ignore. (you're passing a value...)
*Another thing: I picked up a misstype from your rule. Check below the right sintax with new arguments.
'field' => 'required|unique:table_name,field,'.$this->input('id')'
PS: if your id column from your table has a different name than "id", you need to specify this column name as the fourth parameter.
'field' => 'required|unique:table_name,field,'.$this->input('id').',column_name'

Categories