Laravel Eloquent updateOrCreate doesn't work properly - laravel

I once wrote probably same question last time and I'm back..
Laravel Eloquent firstOrCreate doesn't work properly
On the last question, I found that fillable property filters update field manifest. So, if you want to update a table based on fieldA and fieldB, then your code might be..
$modelOrRelation->updateOrCreate(
['fieldA' => 'a', 'fieldB' => 'b'], ['otherfields' => 'update value']
);
and you MUST specify those fields on fillable property. $fillable = ['fieldA', 'fieldB', ...]
This is what I know about firstOrCreate and updateOrCreate.
At this time, following code generate many same rows. It looks like, the first parameter ['candle_date_time_kst'] do nothing..
// candleRelation is hasMany relation..
$candleRelation = $market->candles($period);
$created = $created->add($candleRelation->updateOrCreate(
[
'candle_date_time_kst' => $time,
],
$item
));
This creates many same candle_date_time_kst value rows. At this time, fillable property already filled target fields.
What else do I miss?
Is updateOrCreate should not trust? I didn't think so.. There are something I miss... any insight?
#220114 update
So, I do my homework..
Using DB::getQueryLog(), I get this query..
It looks like, updateOrCreate() remembers the last update value. Then if I reuse same eloquent relation object for another updateOrCreate(), method use last update parameter again. It makes and clause, so return record is none..
So, I use newQuery() method for initialize query bindings.
$created->add($candleRelation->newQuery()->updateOrCreate(
[
'candle_date_time_kst' => $time
],
$item
));
#220114
Unfortunately, retest reveals newQuery() actually not helping..
I tried $relation->newModelInstance() and getting same bindings.
What I trying to do is getting same parent binding without anything else. .. anyone knows?
Based on binding, when I get relation model I can get clean binding also. So I just do below..
$created->add($market->candles($period)->updateOrCreate(
[
'candle_date_time_kst' => $item['candle_date_time_kst']
],
$item
));
Only change is $candleRelation to $market->candles($period).
On each attempt, new relation instance produce so binding problem won't even exists.
.... I'm mad.

you need to supply an array in the format
[ column => value, ... ] not [ value ]

I had a similar problem a time ago. And the UpdateOrInsert method solved it.
Unfortunately, this method is Query Builder, not eloquent. But to achieve this result that was the only really working solution to me.
The issue for only happened when I tried to use more than 1 column on where clause, like in your example.

Related

Laravel 5.7.13 - FirstOrCreate/FirstOrNew not storing value

I'm trying to update a table with two columns 'id' and 'comp_id'. I only want 'comp_id' to exist in the table one time, so I'm using firstOrCreate to do this.
This call is creating a record in the table because the id auto-increment keeps going up every time I try this, but the comp_id value is not being saved.
$connections = $connectionTable->where('base_id', $baseId)->get();
$noMatchTable = new noMatch;
foreach($connections as $connection){
$rec = $noMatchTable->setTable($comparisonId.'_no_match')->firstOrCreate(['comp_id' => $connection->comp_id]);
}
I've also tried.
$rec = $noMatchTable->setTable($comparisonId.'_no_match')->firstOrNew(['comp_id' => $connection->comp_id])->save();
In both cases $rec->id shows a new id.
Fillable for the model is set correctly
#fillable: array:1 [
0 => "comp_id"
]
What frightfully simple thing am I missing?
I did some reading on this, and it seems like updateOrCreate() in Builder.php calls firstOrNew()in the same file. If you look at firstOrNew() it will return a new model instance, which means your setTable() will not work as the new instance will contain the default table name. I like the solution posted here
Update the table name at runtime not working - laravel Eloquent ORM
Sadly there is no way to dynamically set a table name and use updateOrCreate()

Laravel Eloquent increment without updating timestamps

I have an Eloquent model on which I would like to increment a single attribute. So far I've been using the following line of code to achieve this:
Thread::where('id', $threadId)->increment('like_count');
This however has the unwanted side-effect of updating the updated_at timestamp. I've found the following way of updating a record without altering the timestamp:
$thread = Thread::where('id', $threadId)->first();
$thread->timestamps = false;
$thread->like_count++;
$thread->save();
But that suddenly looks a lot less concise. Therefore, I would like to know of there's a way to use the increment method without updating timestamps.
If you do not need timestamps at all, you can disable it once for all for that particular model using :
public $timestamps = false; inside your model. This will add additional step that whenever you want the timestamps to be updated, you need to assign them value manually like $object->created_at = Carbon::now()
Secondly, if you want those disabled for particular query, then as you mentioned in your question is one way.
Another way is using query builder. Now timestamps is the functionality associated with Eloquent. However, if you update using simple query builder, it does not update timestamps on its own.
So you can do :
DB::table('threads')
->where('id', $threadId)
->update([ 'votes' => DB::raw('votes + 1') ]);
However, I will personally prefer using Eloquent way of doing this if given a choice.
Update
You can now pass additional parameter to increment function to specify what other columns you would like to update.
So this will become :
$thread = Thread::find($threadId);
$thread->increment('votes', 1, [
'updated_at' => $thread->updated_at
]);
old thread but with laravel 7 and php7.4 you can do
Thread::where('id', $threadId)
->where(fn($q) => $q->getModel()->timestamps = false)
->increment('like_count');
older versions of php:
Thread::where('id', $threadId)
->where(function($q) {$q->getModel()->timestamps = false;})
->increment('like_count');
You could encapsulate the whole process into one method of the model.

How to set field for ::create in Laravel?

I use this method to create new row in DB:
$input = $request->all();
return Recipient::create([$input]);
How I can add additional field to $input with an value?
I tried:
$input["user_id"] = Auth::id();
But when I display query INSERT, I can not see field user_id
The problem here is that you probably don't set $fillable property in Recipient model. You should add user_id there:
$fillable = [ ..., 'user_id'];
However merging input is usually not the best way to deal with such things. If you have set relationships properly, it should be possible to do something like this:
Auth::user()->recipients()->create($request->all());
Update
Like #Marcin Nabialek said: add 'user_id' to the $fillable array. And, his solution is cleaner.
Merge
The method merge is what you're looking for: Merge new input into the current request's input array.
$request->merge(['user_id' => Auth::user()->id]);
Recipient::create($request->all());
See the api documentation here: https://laravel.com/api/5.2/Illuminate/Http/Request.html#method_merge
I'm still not sure what exactly you are looking for. Suppose you have 2-3 fields user_id, title, content. For inserting into the database you need to do following:
$input = new Recipient(['title'=>'You title', 'content'=>'Your content']);
$input->save();
It will save two fields with the incremented user_id.
Bro, You're using wrong method,
Try this one:
$input["user_id"] =Auth::user()->id;

Laravel validation: unique with multiple columns and soft_delete

I am trying to do a Laravel validation rules as follow:
"permalink" => "required|unique:posts,permalink,hotel_id,deleted_at,NULL|alpha_dash|max:255",
The explanation to the rules is:
I have a table "Posts" in my system with the following fields (among others): hotel_id, permalink, deleted_at. If MySQL would allow make an unique index with null values, the sql would be:
ALTER TABLE `posts`
ADD UNIQUE `unique_index`(`hotel_id`, `permalink`, `deleted_at`);
So: I just add a new row IF: the combination of hotel_id, permalink and deleted_atfield (witch must be NULL) are unique.
If there is already a row where the permalink and hotel_id field are the same and 'deleted_at' field is NULL, the validation would return FALSE and the row wouldnt be inserted in the database.
Well. I don't know why, but the query Laravel is building looks like:
SELECT count(*) AS AGGREGATE FROM `posts`
WHERE `hotel_id` = the-permalink-value AND `NULL` <> deleted_at)
What the heck...
The query I was hoping Laravel build to validation is:
SELECT count(*) AS AGGREGATE FROM `posts`
WHERE `permalink` = 'the-permalink-value' AND `hotel_id` = ? AND `deleted_at` IS NULL
Could someone explain me how this effectively works? Because everywhere I look it looks like this:
$rules = array(
'field_to_validate' =>
'unique:table_name,field,anotherField,aFieldDifferentThanNull,NULL',
);
Does anyone could help me?
Thank you
all.
Finally, I got a proper understanding of the validation (at least, I think so), and I have a solution that, if it is not beautiful, it can helps someone.
My problem, as I said before, was validate if a certain column (permalink) is unique ONLY IF other columns values had some specific values. The problem is the way Laravel validation string rules works. Lets get to it:
First I wrote this:
"permalink" => "required|unique:posts,permalink,hotel_id,deleted_at,NULL|alpha_dash|max:255",
And it was generating bad queries. Now look at this:
'column_to_validate' => 'unique:table_name,column_to_validate,id_to_ignore,other_column,value,other_column_2,value_2,other_column_N,value_N',
So. The unique string has 3 parameters at first:
1) The table name of the validation
2) The name of the column to validate the unique value
3) The ID of the column you want to avoid (in case you are editing a row, not creating a new one).
After this point, all you have to do is put the other columns in sequence like "key,value" to use in your unique rule.
Oh, easy, an? Not so quickly, paw. If you're using a STATIC array, how the heck you will get your "currently" ID to avoid? Because $rules array in Laravel Model is a static array. So, I had to came up with this:
public static function getPermalinkValidationStr() {
$all = Input::all();
# If you are just building the frozenNode page, just a simple validation string to the permalink field:
if(!array_key_exists('hotel', $all)) {
return 'required|alpha_dash|max:255';
}
/* Now the game got real: are you saving a new record or editing a field?
If it is new, use 'NULL', otherwise, use the current id to edit a row.
*/
$hasId = isset($all['id']) ? $all['id'] : 'NULL';
# Also, check if the new record with the same permalink belongs to the same hotel and the 'deleted_at' field is NULL:
$result = 'required|alpha_dash|max:255|unique:posts,permalink,' . $hasId . ',id,hotel_id,' . $all['hotel'] . ',deleted_at,NULL';
return $result;
}
And, in the FrozenNode rules configuration:
'rules' => array(
'hotel_id' => 'required',
'permalink' => Post::getPermalinkValidationStr()
),
Well. I dont know if there is a easiest way of doing this (or a much better approach). If you know something wrong on this solution, please, make a comment, I will be glad to hear a better solution. I already tried Ardent and Observer but I had some problems with FrozenNode Administrator.
Thank you.

Saving a model with multiple foreign keys in Laravel 4

I understand that in order to save a foreign key, one should use the related model and the associate() function, but is it really worth the trouble of going through this
$user = new User([
'name' => Input::get('name'),
'email' => Input::get('email')
]);
$language = Language::find(Input::get('language_id');
$gender = Gender::find(Input::get('gender_id');
$city = City::find(Input::get('city_id');
$user->language()->associate($language);
$user->gender()->associate($gender);
$user->city()->associate($city);
$user->save();
when one can simply do this?
User::create(Input::all());
I feel like I'm missing something here, maybe there's an even simpler and cleaner way to handle foreign keys in controllers (and views)?
You can use push() method instead which would allow you to push to related models.
This link should answer your query.
Eloquent push() and save() difference
I really don't see anything wrong at all with doing User::create(Input::all());.
Obviously you'd want some validation, but it's doing the same thing.
I think the associate() method is more useful for the inverse of your situation.
For example, say you had a form which a user could fill out to add their city to your app, and upon doing so, they should automatically be assigned to that city.
$city = City::create(Input::all()); would only achieve the first half of your requirements because the user has not yet been attached as city does not have a user_id column.
You'd then need to do something like $city->user()->associate(User::find(Auth::user()->id));

Resources