Two models to store in database as one transaction - laravel

I have two models Item and ItemDetail and they have a one-to-many relationship
One-to-many because I store the item details as Entity–attribute–value Table, it is nothing but a table the columns and label and value that stores ItemDetails as following
id|item_id_fkey|Label |Value
1 |1 |Color |Black
2 |1 |Description |Item 1 Details
3 |1 |Size |2x4x6
4 |2 |Description |Item 2 Details
5 |2 |Weight |1000
When I create an Item it ask me Item->name and Item->Price, on Submit it calls the ItemsController#store and redirect to ItemDetailController#create with newly created item id.
ItemDetailsController#create redirects to a Form with lot more fields to for gathering item details (e.g. ItemDetail->Description, ItemDetail->Dimensions etc), and on submit it stores the ItemDetails model.
Problem:(Not as complex as the situation), The issue is I want it to be such that if user creates item in first form and does not update the item details in second form, item shouldn't present in Item table as well.
In another words two store methods should be one single transaction.

Solution 1:
You can put everything in one form (may require creating dynamic HTML elements) and make your controller's store() method check if there's a non-empty array of values for the ItemDetail model. If there are values, create the Item model and the ItemDetail model(s), abort otherwise.
Solution 2:
You can have your ItemDetailsController#store method accept an $itemId which represents the newly created Item model and, as above, check if there's a non-empty array of values for the ItemDetail model(s) then create or abort.

I am assuming the two forms you are referring to are on separate pages and therefore are two separate requests to the server.
You could save the information in the first form in an object and pass it to the session.
Controller for First Form:
public function handleFirstForm() {
//perform validation and any other logic
$input = Input::get('contentsOfFirstForm');
Session::put('contentsOfFirstForm', $input);
}
The array you put into the session will only be used if the customer proceeds to fill out the item details.
Controller for Second Form:
public function handleSecondForm() {
$input = Input::get('contentsOfSecondForm');
//only save if the second form is not blank
if($input!==null) {
//run any other logic
//Save First Form
$item = Session::get('contentsOfFirstForm');
Item::create([$item]);
//Save Second Form
$itemDetails = $input;
ItemDetail::create([$item]);
}
}
You may also want to do a check using model events to see if the first form successfully saved, and ONLY then, save the second form, to prevent cases where your second model successfully saved, but the first model for whatever reason could not be saved.

Related

SOLVED: Looking for a smarter way to sync and order entries in Laravel/Eloquent pivot table

In my Laravel 5.1 app, I have classes Page (models a webpage) and Media (models an image). A Page contains a collection of Media objects and this relationship is maintained in a "media_page" pivot table. The pivot table has columns for page_id, media_id and sort_order.
A utility form on the site allows an Admin to manually associate one or more Media items to a Page and specify the order in which the Media items render in the view. When the form submits, the Controller receives a sorted list of media ids. The association is saved in the Controller store() and update() methods as follows:
[STORE] $page->media()->attach($mediaIds);
[UPDATE] $page->media()->sync($mediaIds);
This works fine but doesn't allow me to save the sort_order specified in the mediaIds request param. As such, Media items are always returned to the view in the order in which they appear in the database, regardless of how the Admin manually ordered them. I know how to attach extra data for the pivot table when saving a single record, but don't know how to do this (or if it's even possible) when passing an array to attach() or sync(), as shown above.
The only ways I can see to do it are:
loop over the array, calling attach() once for each entry and passing along the current counter index as sort_order.
first detach() all associations and then pass mediaIds array to attach() or sync(). A side benefit would be that it eliminates the need for a sort_order column at all.
I'm hoping there is an easier solution that requires fewer trips to the database. Or am I just overthinking it and, in reality, doing the loop myself is really no different than letting Laravel do it further down the line when it receives the array?
[SOLUTION] I got it working by reshaping the array as follows. It explodes the comma-delimited 'mediaIds' request param and loops over the resulting array, assigning each media id as the key in the $mediaIds array, setting the sort_order value equal to the key's position within the array.
$rawMediaIds = explode(',', request('mediaIds'));
foreach($rawMediaIds as $mediaId) {
$mediaIds[$mediaId] = ['sort_order' => array_search($mediaId, $rawMediaIds)];
}
And then sorted by sort_order when retrieving the Page's associated media:
public function media() {
return $this->belongsToMany(Media::class)->orderBy('sort_order', 'asc');
}
You can add data to the pivot table while attaching or syncing, like so:
$mediaIds = [
1 => ['sort_order' => 'order_for_1'],
3 => ['sort_order' => 'order_for_3']
];
//[STORE]
$page->media()->attach($mediaIds;
//[UPDATE]
$page->media()->sync($mediaIds);

Vuetify v-data-table change a row color for a few seconds

We've just moved over from bootstrap to Vuetify, but i'm struggling with something.
We have some updates sent (over signalR) that update a list of jobs, i'd like to be able to target a job that has been changed and change the row color for that particular job for a few seconds so the operator can see its changed.
Has anyone any pointers on how we can do this on a Vuetify v-data-table
Thanks
I ran into the same problem. This solution is a bit crude and a bit too late, but may help someone else.
In this example I change the colour of the row permanently until the page reloads. The problem with a temporary highlight is that if the table is sorted there is no way to put the row in the visible part of the table - v-data-table will put it where it belongs in the sort, even if it's out of the view.
Collect the list of IDs on initial load.
Store the list inside data of the component.
Use a dynamic :class attribute to highlight rows if the ID is not in the list (added or edited rows)
Solution in detail
1. Use TR in the items template to add a conditional class.
<template slot="items" slot-scope="props">
<tr :class="newRecordClass(props.item.email, 'success')">
<td class="text-xs-center" >{{ props.item.email }}</td>
:class="newRecordClass(props.item.email, 'success')" will call custom method newRecordClass with the email as an ID of the row.
2. Add an additional array to store IDs in your data to store
data: {
hydrated: false,
originalEmails: [], <--- ID = email in my case
3. Populate the list of IDs on initial data load
update(data) {
data.hydrated = true; // data loaded flag
let dataCombined = Object.assign(this.data, data); // copy response data into the instance
if (dataCombined.originalEmails.length == 0 ) {
// collect all emails on the first load
dataCombined.originalEmails = dataCombined.listDeviceUsers.items.map( item => item.email)
}
return dataCombined;
}
Now the instance data.originalEmails has the list of IDs loaded initially. Any new additions won't be there.
4. Add a method to check if the ID is in the list
newRecordClass(email, cssClass) {
// Returns a class name for rows that were added after the initial load of the table
if (email == "" || this.data.originalEmails.length==0) return "" // initial loading of the table - no data yet
if (this.data.originalEmails.indexOf(email) < 0 ) return cssClass
}
:class="newRecordClass(..." binds class attribute on TR to newRecordClass method and is being called every time the table is updated. A better way of doing the check would be via a computed property (https://v2.vuejs.org/v2/guide/computed.html#Computed-Properties). Vue would only call it when the underlying data changed - a method is called every time regardless.
Removing the highlight
You can modify newRecordClass method to update the list of IDs with new IDs after a delay to change the colour to normal.
#bakersoft - Did you find a solution? I suspect there is an easier way to skin this cat.

Find Data Using Laravel Framework

I want match data from "jobTable" based on job id then i got 5 data within this 5 data i have userId i want to show data from "userTable" based on userID. Then i will catch it on variable and i want to show it on my view then i will show it using loop.
If i do this below code it shows all data from my userTable not show based on matched JobID base UserId Details.
public function applyUser($jobId){
$applyUsers=ApplyInfo::where('job_id', $jobId)->get();
//5 rows found, with in this 5 rows everybody has userId I want to show data from user table based on this found userId and pass value a variable then i will loop it on view page.
foreach ($applyUsers as $applyUser){
$applyUsersDtials=Employee::find($applyUser->user_id)->get();
}
return view('front.user.apply-user-details', ['applyUsersDtials'=>$applyUsersDtials]);
}
//when i do that that time show all data based on my user table not show based on my JobId
First of all in your code $applyUsersDtials is a variable it should be an array. Next when you need to find a single row you just need to use find(). Try this-
public function applyUser($jobId){
$applyUsers = ApplyInfo::where('job_id', $jobId)->get();
$applyUsersDtials = [];
foreach ($applyUsers as $applyUser){
$applyUsersDtials[] = Employee::find($applyUser->user_id);
}
return view('front.user.apply-user-details',
['applyUsersDtials' => $applyUsersDtials]);
}

Model doesn't show up on relationship right after saving?

For example
$user->transactions()->save($transaction);
dump($transaction->id); // id: 4
dd($user->transactions); // last id is 3
The $transaction that is saved does not show up in $user->transactions. This is a polymorphic relationship.
When you save a model through a relationship, all Laravel does is set the appropriate foreign keys. It doesn't set the newly saved model to the relation or add it to a collection.
If you want the model with updated relations use the fresh function.
$user->transactions()->save($transaction);
$user = $user->fresh('transactions');
Edit answer updated following #JCLee's correction to assign the result of $user->fresh().

Testing oneToMany relationship Laravel, Eloquent doesn't work

I can't get my head around this:
$fragment = factory(Fragment::class)->create();
$this->assertCount(0, $fragment->values);
fragment->fetch(); // updates the 'values' by adding one Value object.
var_dump($fragment->id); // i.e. 6
var_dump(Fragment::first()->id); // 6
var_dump($fragment->values->count()); // 0
var_dump(Fragment::first()->values->count()); // 1
$this->assertCount(1, $fragment->values);
I use DatabaseTranscations, so after a Fragment is created, there is always one and only one. Thus, $fragment and Fragment::first() are the exact same instance. Yet... the values relationship is different. How can this be the case?
Note that this happens only during testing, when I manually test this through my controller (and the values are passed to the blade template page) it works just fine. I am confused :S.
Any ideas?
Relationship attributes ($fragment->values) are only loaded once. They are not kept up to date when you add or delete items from the relationship. They do not hit the database every time to check for changes.
Your second line is $this->assertCount(0, $fragment->values);. Accessing $fragment->values here lazy loads the relationship, and as your assert proves, it is empty.
You then call $fragment->fetch(), in which your comment says it adds a Value object to the fragment. However, your relationship attribute ($fragment->values) has already been loaded from the previous statement, so it will not reflect the additional Value object you added to the relationship.
Therefore, even after the call to fetch(), $fragment->values is still going to be an empty collection. Fragment::first()->values will contain the newly related Value though, because it is getting a new instance of the Fragment, and when it loads the values for the first time, it will pick up the related Value.
When you need to reload the relationship, you can use the load() method. If you add this after your call to fetch() (or put it in your fetch() method, whichever makes sense for you), your test will work fine.
$fragment = factory(Fragment::class)->create();
$this->assertCount(0, $fragment->values);
$fragment->fetch(); // updates the 'values' by adding one Value object.
var_dump($fragment->id); // i.e. 6
var_dump(Fragment::first()->id); // 6
var_dump($fragment->values->count()); // 0
// reload the values relationship
$fragment->load('values');
var_dump($fragment->values->count()); // 1
var_dump(Fragment::first()->values->count()); // 1
$this->assertCount(1, $fragment->values);
The other option you have is to use the relationship query by accessing the relationship method instead of the relationship attribute. If you do $fragment->values()->count() (note: values(), not values), that will hit the database every time and always return the current count.

Resources