Laravel: avoid duplicate entries in database [duplicate] - php

This question already has answers here:
Laravel avoid duplicate entry from model
(2 answers)
Closed 3 years ago.
I'm building a Laravel API. I have a models called Reservations. I want to avoid that a user creates two reservations for the same product and time period.
I have the following:
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
return new ReservationResource($reservation);
Documentation says:
The firstOrCreate method will attempt to locate a database record
using the given column / value pairs
The above code is working:
it does add the item to the database if it does not exist yet (in
this case, the reply to the REST API returns the new model instance
which is correct.
it does not add the item to the database if the combination equals listing_id, user_id_from, start_date and end_date.
However, in the latter case (item already exists), it will also return in the REST API reply a reservation with the ID of the matched row.
Example: in the below table, reservation with id 2 already exists:
id listing_id user_id_from start_date end_date
------------------------------------------------------
1 2 3 2019-09-12 2019-10-14
Sending the below REST API request:
{
"listing_id": 2,
"user_id_from": 3,
"start_date": "2019-09-12",
"end_date": "2019-10-14",
}
returns
"data": {
"id": 1,
"user_id_from": 3,
"listing_id": 2,
"price": 388,
"start_date": "2019-09-12",
"end_date": "2019-10-14",
}
I would want to have a JSON reply stating that the item already exists. How can this be achieved?

firstOrCreate() is just a two-part function as you might expect, doing a lookup on first() and falling back to create() if not exists. You could implement much the same logic in your controller, and append a message to the first() result if it exists, and return the result as-is if created. Alternatively, you could modify the response with a HTTP_OK or HTTP_CREATED depending on the result. Or both.
public function store(Request $request)
{
$attributes = [
'listing_id' => $request->input('listing_id'),
'user_id_from' => $request->input('user_id_from'),
'start_date' => $request->input('start_date'),
'end_date' => $request->input('end_date')
];
// return existing reservation if exists
$reservation = Reservation::where($attributes)->first();
if ($reservation !== null) {
// add explicit message here if you want
return response(json_encode($reservation), Response::HTTP_OK);
}
// else create a new one
$reservation = Reservation::create($attributes);
// reload model
$reservation = Reservation::find($reservation->id);
return response(json_encode($reservation), Response::HTTP_CREATED);
}

I think you are looking for Laravel's wasRecentlyCreated:
if(! $reservation->wasRecentlyCreated){
// create & encode a JSON response or use this variable & method on the blade page
}

Related

sending daily summary email

I am writing up a cron job for daily email notification.
Here are the scenario lets say
User A gets 10 leads a day.
User B gets 5 leads a day.
User C gets 2 leads a day.
i want to send one email to User A having 10 leads information in it.
then one email to User B having 5 leads information in it.
then one email to User C having 2 leads information in it.
so I want to try to create a summary email having lead information in it for a particular user.
foreach ($today_leads as $today_lead) {
$data[] = [
'user_id' => $today_lead->user_id,
'user_fullname' => $today_lead->user_fullname,
'user_email' => $today_lead->user_email,
'lead_firstname' => $today_lead->first_name,
'lead_lastname' => $today_lead->last_name,
'lead_email' => $today_lead->email,
'lead_phone' => $today_lead->phone,
'lead_source' => $today_lead->source,
];
Mail::to(data['user_email'])->send(new DailyLeadSummary($data));
}
if I write my in foreach loop then I end up sending 10 emails to User A, 5 emails to User B, and so on.
Any other approach to handle this?
You can first group your results based on user_email and then send email in second loop.
$grouped_data = [];
foreach ($today_leads as $lead) {
$grouped_data[$lead->user_email][] = [
'user_id' => $lead->user_id,
'user_fullname' => $lead->user_fullname,
'user_email' => $lead->user_email, // you can remove it if not required in email body anywhere
'lead_firstname' => $lead->first_name,
'lead_lastname' => $lead->last_name,
'lead_email' => $lead->email,
'lead_phone' => $lead->phone,
'lead_source' => $lead->source,
];
}
foreach($grouped_data AS $user_email => $data)
{
Mail::to($user_email)->send(new DailyLeadSummary($data));
}
Looking at your implementation, you're on the right path, but you need to make some adjustments.
first of all, you need to query the user table to get users with leads on the particular day. do that like this
// This will return only users who got leads today
$usersWithLeads = User::whereHas('leads', function($query) {
// This will match all leads that was created today
$query->where('created_at', >=, now()->toStartOfDay());
})->get();
// then you can loop over $usersWithLeads
foreach($usersWithLeads as $usersWithLead) {
// You can then retrieve the leads gotten today via the leads relationship
$leads = $usersWithLead->leads()->where('created_at', now()->startOfDay());
// Then send your mail
Mail::to($usersWithLead)->send(new DailyLeadSummary($leads));
}
You can then render the leads on your mailable view however you like. But this makes sure you send only one email per user.
Also, You can modify the query to retrieve $usersWithLeads to also retrieve the daily leads like this:
$usersWithLeads = User::with(['leads' => function ($query) {
$query->where('created_at', now()->toStartOfDay());
}])->whereHas('leads', function($query) {
// This will match all leads that was created today
$query->where('created_at', >=, now()->toStartOfDay());
})->get();
Then your loop will look like:
foreach($usersWithLeads as $usersWithLead) {
// Then send your mail
Mail::to($usersWithLead)->send(new DailyLeadSummary($usersWithLead->leads));
}
Note: the now() method used in the query is a Carbon helper method for Carbon::now() which gives you the current time, chaining the ->startOfDay() method on now() retrieves the datetime at 0:00 (when the day started)
This Answer also assumes you have a table to hold leads and have declared a relationship in your User model like:
public function leads() {
return $this->hasMany(App\Leads::class, 'user_id', 'id');
}
Let me know if you need more help with this.

How to get data with pivot data attached?

I'm trying to get the data from my Lists table and include an array with the ID's of the tracks in that list.
This is a sample of the database model, with a relation N:M.
In my List model, I added this method:
public function tracks()
{
return $this->belongsToMany(Track::class, 'List_Tracks', 'id_list', 'id_track');
}
So, in my ListController, I'm doing the following:
$list = List::find($id);
$list->tracks_list = $list->tracks->pluck('track_id');
return $list;
And what I get is as many objects as tracks I have in a same list, for example:
[
{
"id_track": 1,
"name": "Yesterday",
"tracks_list": [
1,
2
]
"pivot": {
"id_list": 1,
"id_track": 1
}
},
{
"id_track": 2,
"name": "Lucy in the sky with diamonds",
"pivot": {
"id_list": 1,
"id_track": 2
}
}
]
But what I want to get is:
{
"id_list": 1,
"name": "The Best of The Beatles",
"tracks_list": [
1,
2
]
}
I think the things I've tried are much more complex than the proper solution.
How would you get the data in this way?
Thanks in advance.
You need to load the relationship first and then write rest of the eloquent query, otherwise you will get only list of ids from the pivot table.
List::with('tracks')->where('id', '=', $id)->first();
// Following method should work too
List::with('tracks')->find($id);
Based on your commentary - to get only array of ids related to the list you don't need to load the relationship and can use only:
List::find($id)->tracks()->pluck('id');
// In case previous method will not work, you can try this one:
List::find($id)->tracks()->pluck('tracks.id');
// before dot should be name of the table, not relationship's.
So if you need only ids of the tracks that are attached to the playlist. I would recommend adding to your List model following method:
// App\Models\List
public function getTrackIdsAttribute()
{
return $this->tracks->pluck('id')->toArray();
}
Then you should be able to simply call List::find($id)->trackIds to get all ids of tracks attached to the given list. If you're not sure why I am defining the method as getTrackIdsAttribute and call only trackIds on the model, take a look at Eloquent: Mutators.

Laravel Collection Transform/Map Method Inconsistent Behaviour

In my HTML frontend, I have a jQuery DataTable displaying all records fetched via AJAX from the database - a rather pretty straight forward thing. I use the Laravel Collection's ->transform(function($o){ . . . }) to iterate through the collection and return it in an array-esque manner. Just think of the following piece of code in a controller:
$cAllRecords = DatabaseRecord::all();
if(!empty($aData['sFilterIds']))
{
$cAllRecords = $cAllRecords->whereIn('creator', explode(',', $aData['sFilterIds']));
}
return response()->json(['data' => $cAllRecords->transform(function ($oDatabaseRecord) {
/** #var $oDatabaseRecord DatabaseRecord */
$sActionsHtml = 'edit';
$sUrl = route('some.route', ['iDatabaseRecordId' => $oDatabaseRecord->getAttribute('od')]);
return [
$oDatabaseRecord->getAttribute('id'),
$oDatabaseRecord->getAttribute('updated_at')->toDateTimeString(),
$oDatabaseRecord->getAttribute('created_at')->toDateTimeString(),
$sActionsHtml
];
})]);
I'm actually just filtering for records created by certain user IDs (the whereIn() call in line 4. However, the response sent back to the client looks different for different users filtered leading the jQuery table to show 'no records available', as it had received an malformed answer from the server. For one user, the response looks like this:
{
"data":[
[
1,
"2019-05-29 16:44:53",
"2019-05-29 16:44:53",
"<a href=\"#\">edit<\/a>"
]
]
}
This is a correctly formed server response and will show up in the table regularly. Great! Now something that drives me insane - the same code for another user (ID 1, while the first request was for user ID 2) returns this:
{
"data":{
"1":[
3,
"2019-05-29 17:08:49",
"2019-05-29 17:08:49",
"<a href=\"#\">edit<\/a>"
]
}
}
which, pretty obviously, is malformed and is not correctly parsed by the datatable. OK, now combing them two filters and filtering for user ID 1 and 2 will, again, return the response correctly formatted:
{
"data":[
[
1,
"2019-05-29 16:44:53",
"2019-05-29 16:44:53",
"<a href=\"#\">edit<\/a>"
],
[
3,
"2019-05-29 17:08:49",
"2019-05-29 17:08:49",
"<a href=\"#\">edit<\/a>"
]
]
}
I tried a number of things, none of which had worked since it's merely guessing why it could work with one user and not with another. (Things like reversing the order of IDs to be filtered, etc., but I found out that the filtering is not the problem. It MUST be the transform, which behaves inconsistent.)
Any ideas on why this happens and how to tackle it? I mean, it's not the only way to achieve what I'm after, I was using ->each() and array_push for all the time before but wanted to get rid of it for the sake of making use of Laravel's helpers (or possibilites) - the manual iteration and array pushing process worked out seamlessly before, and even other parts of the app work well with the Collection transform over array iteration and pushing. Why doesn't it here?
Update: The ->map() collection method behaves exactly same. Map, as opposed by transform, does not alter the collection itself. However, this should not be a relevant part within this application any way. I really can't understand what's going wrong. Is this possibly Laravel's fault?
Please note that transform method returns a Illuminate\Support\Collection.
It's better that you call all() after the transform to get an array result.
Like this:
...
return response()->json(['data' => $cAllRecords->transform(function ($oDatabaseRecord) {
/** #var $oDatabaseRecord DatabaseRecord */
$sActionsHtml = 'edit';
$sUrl = route('some.route', ['iDatabaseRecordId' => $oDatabaseRecord->getAttribute('od')]);
return [
$oDatabaseRecord->getAttribute('id'),
$oDatabaseRecord->getAttribute('updated_at')->toDateTimeString(),
$oDatabaseRecord->getAttribute('created_at')->toDateTimeString(),
$sActionsHtml
];
})->all()]);
#Cvetan Mihaylov's answer made me look at all the available collection methods (https://laravel.com/docs/5.8/collections#available-methods) and I found ->values() to return the values reindexed. And - that did the trick! :-)
return response()->json(['data' => $cAllRecords->transform(function ($oDatabaseRecord) {
/** #var $oDatabaseRecord DatabaseRecord */
$sActionsHtml = 'edit';
$sUrl = route('some.route', ['iDatabaseRecordId' => $oDatabaseRecord->getAttribute('od')]);
return [
$oDatabaseRecord->getAttribute('id'),
$oDatabaseRecord->getAttribute('updated_at')->toDateTimeString(),
$oDatabaseRecord->getAttribute('created_at')->toDateTimeString(),
$sActionsHtml
];
})->values()]);

Is there a way to check if a couple of inputs are unique in laravel 5.2?

update
What I have is a table with these columns:
- ID
- production_year
- type
If the type is already present in the table with the value the user wants to pass, check if production_year is already present too, but fail the validation ONLY if these values are present in the same record. Otherwise let the user to store this record.
I'm trying to check the uniqueness of a couple of fields in the same record...
I've seen the documentation about the conditional validation, but I didn't quite find the answer there.
the code
public function rules()
{
return [
// I'd like to check the uniqueness of both of them. In the same record
'production_y' => 'required|unique',
'fk_type' => 'required|unique',
];
}
Any idea? Thanks!
Laravel 5.3 Update:
Now, you can generate the same rule fluently using the Rule (\Illuminate\Validation\Rule) class.
NULL,id part of the string way is no more required. See:
public function rules()
{
$type = $this->get('fk_type');
return [
'production_y' => [
'required',
Rule::unique('your_table', 'production_year')->where(function($query) {
$query->where('type', $type);
}),
],
];
}
Original Answer
Can't test right now, can you try:
public function rules()
{
$type = $this->get('fk_type');
return [
'production_y' => "required|unique:your_table,production_year,NULL,id,type,{$type}",
// ...
];
}
Explaination:
unique:your_table Set the table for the unique check.
,production_year This matches with production_y.
,NULL,id check all the records.
3.1. if you use like {$id},id it will check uniqueness except the record with the {$id},
,type,{$type} and the type should be {$type}
That will produce sth. like (not exact query, just to express the idea):
select count(*) from your_table where production_year = $product_y and where type = $type and where id <> null
if someone cames from laravel 8, i tried this and it worked !!
for me i need to check the uniqueness of (category_id,subcategory_id) which mean you can find (1,2),(1,3),(2,1),(2,3) but you cant find similar couple !!
'category' => "required|unique:tickets,category_id,NULL,id,subcategory_id,{$request->input('subcategory')}"

Update collection erases all information inside

I am new to MongoDB and trying to perform my first updates.
I have my users collection that is populated by an insert statement. Now, I want to add or insert the field authCode with some data into a specific user.
My problem is that when I perform the following function the whole user data becomes replaced by the information in that update statement. My understanding is that by using upsert I would insert or update a users collection. given that the user already exist I expect just the authCode field to be created or updated.
Could anyone point out what am i doing wrong?
public function addAuthCode( array $userId, $code ) {
$user = $this->db()->user;
$user->update(
$userId,
array( 'authCode' => $code ),
array( 'upsert' => true, 'safe' => true )
);
}
You'll want to take a look at the MongoDB documentation for updates found here: http://docs.mongodb.org/manual/reference/method/db.collection.update/#db.collection.update
Specifically:
Add New Fields
db.bios.update(
{ _id: 3 },
{ $set: {
mbranch: "Navy",
"name.aka": "Amazing Grace"
}
}
)
Notice that in order to add a field, you need to use the $set operator
Chris#MongoLab

Categories