$this->assertDatabaseHas() not working with JSON/JSONb columns.
So how can I tests these types of columns in Laravel?
Currently, I have a store action. How can I perform an assertion, that a specific column with pre-defined values was saved.
Something like
['options->language', 'en']
is NOT an option, cause I have an extensive JSON with meta stuff.
How can I check the JSON in DB at once?
UPD
Now can be done like that.
I have solved it with this one-liner (adjust it to your models/fields)
$this->assertEquals($store->settings, Store::find($store->id)->settings);
Laravel 7+
Not sure how far back this solution works.
I found out the solution. Ignore some of the data label, Everything is accessible, i was just play around with my tests to figure it out.
/**
* #test
*/
public function canUpdate()
{
$authUser = UserFactory::createDefault();
$this->actingAs($authUser);
$generator = GeneratorFactory::createDefault();
$request = [
'json_field_one' => [
'array-data',
['more-data' => 'cool'],
'data' => 'some-data',
'collection' => [
['key' => 'value'],
'data' => 'some-more-data'
],
],
'json_field_two' => [],
];
$response = $this->putJson("/api/generators/{$generator->id}", $request);
$response->assertOk();
$this->assertDatabaseHas('generators', [
'id' => $generator->id,
'generator_set_id' => $generator->generatorSet->id,
// Testing for json requires arrows for accessing the data
// For Collection data, you should use numbers to access the indexes
// Note: Mysql dose not guarantee array order if i recall. Dont quote me on that but i'm pretty sure i read that somewhere. But for testing this works
'json_field_one->0' => 'array-data',
'json_field_one->1->more-data' => 'cool',
// to access properties just arrow over to the property name
'json_field_one->data' => 'some-data',
'json_field_one->collection->data' => 'some-more-data',
// Nested Collection
'json_field_one->collection->0->key' => 'value',
// Janky way to test for empty array
// Not really testing for empty
// only that the 0 index is not set
'json_field_two->0' => null,
]);
}
Note: The below solution is tested on Laravel Version: 9.x and Postgres version: 12.x
and the solution might not work on lower version of laravel
There would be two condition to assert json column into database.
1. Object
Consider Object is in json column in database as shown below:
"properties" => "{"attributes":{"id":1}}"
It can assert as
$this->assertDatabaseHas("table_name",[
"properties->attributes->id"=>1
]);
2. Array
Consider array is in json column as shown below:
"properties" => "[{"id":1},{"id":2}]"
It can assert as
$this->assertDatabaseHas("table_name",[
"properties->0->id"=>1,
"properties->1->id"=>2,
]);
Using json_encode on the value worked for me:
$this->assertDatabaseHas('users', [
'name' => 'Gaurav',
'attributes' => json_encode([
'gender' => 'Male',
'nationality' => 'Indian',
]),
]);
Related
Hello i have request with unknown name inputs like Game234_3v3player_3 etc. Due to all inputs are nullable and game number is unknown i validated it like this:
$request->validate([
'*_2v2player_*' => [
'exists:users,name',
'nullable',
],
'*_2v2enemyPlayer_*' => [
'exists:enemy_players_list,nickname',
'nullable',
],
'*_3v3player_*' => [
'exists:users,name',
'nullable',
],
'*_3v3enemyPlayer_*' => [
'exists:enemy_players_list,nickname',
'nullable',
In validation, this works. But, i want to store parts of request in variable. What i tried is:
$data = $request->only([
'*_1v1enemyPlayer_*',
'*.2v2enemyPlayer.*',
'*3v3enemyPlayer*',
]);
Etc...
However, it returns empty array in every way in this example /./*_. With dots, dashes etc, etc.
How i can then took into variable only few request atributes with unknown names?
Thanks.
You can understand your request keys with array_keys($request->all()).
Finally you can decide which key you are looking for.
Ok did it other way.
$enemyPlayers = [];
$requestArray = array_keys($request->all());
foreach($requestArray as $player)
{
if (str_contains($player, 'enemyPlayer')) {
array_push($enemyPlayers, $player);
}
}
I am trying to check unique validation on three columns employee_id,designation_id,station_id but the data are coming as an array which is making my situation unique and different from other SO questions/answers. I already checked few question like below: checks unique validation on multiple columns
But in my case, I can't get the value as they are inside an array. I also tried to implement Custom Rule or Request but in vain. For all the attempts, I am failing to get the field value such as $request->employee_id as they are inside an array for my case. May be I'm not trying it right.
Controller Code:
$this->validate($request, [
'posting.*.employee_id' => 'required,unique: // what to do here ??',
'posting.*.designation_id' => 'required',
'posting.*.station_id' => 'required',
'posting.*.from_date' => 'required|date',
]);
I am trying to validate uniqueness for both create and update (along with ignore $this->id facility) but don't know how to implement it here for array. It would be no problem if there was no array. Any help/suggestion/guide is much appreciated. Thanks in advance.
You can do this by creating a rule i.e UniquePosting so your controller code would look like
$this->validate($request, [
'posting' => ['required'],
'posting.*' => ['required', new UniquePosting()],
'posting.*.employee_id' => 'required',
'posting.*.designation_id' => 'required',
'posting.*.station_id' => 'required',
'posting.*.from_date' => 'required|date',
]);
Now inside your UniquePosting rule passes function will look like
public function passes($attribute, $value) {
$exists = Posting::where(['employee_id' => $value['employee_id'], 'designation_id' => $value['designation_id'],'station_id' => $value['station_id')->exists();
return !$exists;
}
Add any change if needed, overall that's the concept for testing uniqueness of the whole array.
Can't figure out why JSON field is being ignored.
This one doesn't work:
Registries::create([
'nr' => $old_document->no,
'metas->name' => 'r01',
]);
In model I have set:
protected $casts = [
'metas' => 'array',
];
And:
protected $fillable = [
'nr',
'metas'
];
I think the problem is in attributes casting, because this one is working:
Registries::create([
'nr' => $old_document->no,
'metas' => json_encode(['name'=>'r01']),
]);
I'm not getting any errors just JSON column stays empty.
I'm not fan of attribute casting in relational dabatases.
So it's just my guess based on documentation about array.
The array cast type is particularly useful when working with columns that are stored as serialized JSON.
You're trying to put associative array which for JSON is an object.
I might guess that it's a bug.
So try to typecast it on insert:
Registries::create([
'nr' => $old_document->no,
'metas' => (object)['name'=>'r01'],
]);
attribute casting
and make it object:
protected $casts = [
'metas' => 'object',
];
So it seams that $metas->name is working only for updating, in my case I need to insert like this:
Registries::create([
'nr' => $old_document->no,
'metas' => ['name'=>'r01']
]);
I'm not able to run this simple query in Laravel 5.3
$top_performers = DB::table('pom_votes')
->groupBy('performer_id')
->get();
It gives me:
SQLSTATE[42000]: Syntax error or access violation: 1055 'assessment_system.pom_votes.id' isn't in GROUP BY (SQL: select * from `pom_votes` group by `performer_id`)
However if I copy raw query from the error and fire directly in PhpMyAdmin, it works fine.
I have already checked this:
https://laravel.com/docs/5.3/queries#ordering-grouping-limit-and-offset
Any help would be appricaited.
Thanks,
Parth Vora
Edit your applications's database config file config/database.php
In mysql array, set strict => false to disable MySQL's strict mode
Maybe your issue is due to the fact that you are using a MySQL server vith version 5.7.5+. From this version on the way GROUP BY works is changed since they make it behave in order to be SQL99 compliant (where in previous versions it was not).
Try to do a full group by or change the configuration of your MySQL server.
Link to official MySQL doc where full GROUP BY is explanined
More safe method instead of disabling strict ('strict' => false) what you could do is pass an array to the config, enabling only the modes that you want:
// config/database.php
'connections' => [
//...
'mysql' => [
//...
'strict' => true,
'modes' => [
//'ONLY_FULL_GROUP_BY', // Disable this to allow grouping by one column
'STRICT_TRANS_TABLES',
'NO_ZERO_IN_DATE',
'NO_ZERO_DATE',
'ERROR_FOR_DIVISION_BY_ZERO',
//'NO_AUTO_CREATE_USER', // This has been deprecated and will throw an error in mysql v8
'NO_ENGINE_SUBSTITUTION',
],
],
],
For anybody who is still getting the same error after changing that setting, try clearing the config cache by running php artisan config:cache
Go to config/database.php
Update strict value false.
return [
'connections' => [
'mysql' => [
'strict' => false
]
]
]
There are ways to fix this
#1
Get only the columns we are grouping by, in this case category_id.
NOTE: Columns in select must be present in groupBy, and vice versa.
$posts = Post::query()
->select('category_id')
->groupBy('category_id')
->get();
category_id
1
2
#2
But I want all columns!
Okay, so you want to get all columns. Then the trick is to simply not use groupBy() on a database level. Instead, you can use it with the returned collection instead.
$posts = Post::query()
->get()
->groupBy('category_id');
[
'1' => [
['id' => 1, 'name' => 'Post 1', 'category_id' => 1, 'author_id' => 4 'visits' => 32],
['id' => 2, 'name' => 'Post 2', 'category_id' => 1, 'author_id' => 8 'visits' => 12],
],
'2' => [
['id' => 3, 'name' => 'Post 3', 'category_id' => 2, 'author_id' => 12 'visits' => 201],
['id' => 4, 'name' => 'Post 4', 'category_id' => 2, 'author_id' => 4 'visits' => 0],
],
]
#3
It is possible to simply disable "strict mode" in Laravel, by setting it to false in the database.php config file. While possible I cannot recommend doing so. It is better to spend the time learning how to write proper SQL queries, as the results given by turning "strict mode" off, can be unpredictable and lead to problems down the road.
Reference
https://sinnbeck.dev/posts/laravel-groupby-error
You can define this line before your query, let's suppose you want to use groupBy so for that instead of changing the config strict to false, simply add this line before where you had used groupBy:
\DB::statement("SET SQL_MODE=''");//this is the trick use it just before your query where you have used group by. Note: make sure your query is correct.
//this is just an example code.
$Rspatients = DB::table('reports')
->select(
DB::raw("day(created_at) as day"),
DB::raw("Count(*) as total_patients"))
->orderBy("created_at")
->groupBy(DB::raw("day(created_at)"))
->get();
My company uses raw SQL to run group by without risking changing mysql settings.
here is an working example :
public static function getPositivesDaily($start_date, $end_date, $admin_id)
{
$positives = DB::select(
'select COUNT(inspections.id) as total,DATE_FORMAT(inspections.created_at, :format) as date
from inspections
where inspections.created_at between :start_date and :end_date
and inspection_results = 1
and admin_id = :admin_id
GROUP BY date',
['format'=>'%Y-%m-%d', 'start_date'=>$start_date, 'end_date'=> $end_date, 'admin_id'=>$admin_id]
);
return $positives;
}
Ask me anything about this code if you don't understand and I will reply as soon as I can.
cheers.
If you false strict mode then you can't use other strict functionality to fix this error Go to the Illuminate\Database\Connectors\MySqlConnector.php and change function like below:
protected function strictMode() {
return "set session
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY
_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'";
}
replace function with this.
I have a form with three fields: title, body and photo[]. I'm trying to validate it so that at least one item is filled in, but I can't seem to get it to work. If I upload a file I still receive an error for title and body.
public function rules()
{
return [
'title' => 'required_without_all:body,photo.*',
'body' => 'required_without_all:title,photo.*',
'photo.*' => 'required_without_all:title,body',
'photo.*' => 'mimes:jpeg,gif,png',
];
}
Update: Jonathan pointed out that I had my rules wrong. I've fixed them and am now using this. It's still not working; when I try to upload a photo I get the error message that the other fields are required.
public function rules()
{
return [
'title' => 'required_without:body,photo.*',
'body' => 'required_without:title,photo.*',
'photo.*' => 'required_without:title,body|mimes:jpeg,gif,png',
];
}
If you're looking to ensure the photo field is an array then you need 'photo' => 'array' and then you can use 'photo.*' => '' for the other validations of the array's children.
The rules are separated by a pipe character | so if you were going to combine the two in your example it would be 'photo.*' => 'required_without_all:title,body|mimes:jpeg,gif,png',. I don't see you using the pipe to separate rules so I can't be sure you are aware of it.
This may have been where you were going wrong in the first place (two keys in the associative array that are identical) and some kind of precedence taking affect negating one of the rules.
You could try something like this (for the record I think you were on the right track to begin with using required_without_all as this stipulates the need to be required if all of the given fields are missing):
public function rules()
{
return [
'title' => 'required_without_all:body,photo',
'body' => 'required_without_all:title,photo',
'photo' => 'array',
'photo.*' => 'required_without_all:title,body|mimes:jpeg,gif,png',
];
}
Reference