Import action multiple models

Hi, I am working with an importer class. My use case is that one csv is converted to multiple models. For example one row contains a card number. I first want to insert the card to retrieve a card_id. I have the following models which need to be inserted based on this csv: - cards - cars - stations - countries - and the main one which are rows, containing foreign keys to all the above. I know I can use the relationship method column but the problem is that for example cars is build up from license_plate. It is only needed in the cars relationship. I am currently trying this approach, but was wondering if I am doing it wrong:
5 Replies
gladjanus43
gladjanus43OP7mo ago
public function resolveRecord(): ?TankRow
{

$car = Car::updateOrCreate([
'license_plate' => $this->data['license_plate'],
]);

$card = Card::updateOrCreate([
'issuer_nr' => $this->data['issuer_nr'],
'authority_nr' => $this->data['authority_nr'],
'serial_nr' => $this->data['serial_nr'],
'car_id' => $car->id,
]);

// Name is editable later so we don't check for it
$country = Country::updateOrCreate([
'code' => $this->data['country_code'],
'vat' => $this->data['vat']
]);

// Name is editable later so we don't check for it
// We do fill it when it doesn't exist
$station = Station::updateOrCreate([
'number' => $this->data['station_nr'],
'country_id' => $country->id,
], [
'name' => $this->data['station_name'],
]);

// Description is editable later so we don't check for it
// We do fill it when it doesn't exist
$product = Product::updateOrCreate([
'code' => $this->data['product_code'],
], [
'description' => $this->data['product_description'],
]);

$tankrow = TankRow::firstOrNew([
'car_id' => $car->id,
'card_id' => $card->id,
'product_id' => $product->id,

'station_id' => $station->id,
'amount' => $this->data['amount'],
'price' => $this->data['price'],
'mileage' => $this->data['mileage'],
]);

Log::info('GIJS - Returning tankrow ' . json_encode($tankrow));

return $tankrow;
}
public function resolveRecord(): ?TankRow
{

$car = Car::updateOrCreate([
'license_plate' => $this->data['license_plate'],
]);

$card = Card::updateOrCreate([
'issuer_nr' => $this->data['issuer_nr'],
'authority_nr' => $this->data['authority_nr'],
'serial_nr' => $this->data['serial_nr'],
'car_id' => $car->id,
]);

// Name is editable later so we don't check for it
$country = Country::updateOrCreate([
'code' => $this->data['country_code'],
'vat' => $this->data['vat']
]);

// Name is editable later so we don't check for it
// We do fill it when it doesn't exist
$station = Station::updateOrCreate([
'number' => $this->data['station_nr'],
'country_id' => $country->id,
], [
'name' => $this->data['station_name'],
]);

// Description is editable later so we don't check for it
// We do fill it when it doesn't exist
$product = Product::updateOrCreate([
'code' => $this->data['product_code'],
], [
'description' => $this->data['product_description'],
]);

$tankrow = TankRow::firstOrNew([
'car_id' => $car->id,
'card_id' => $card->id,
'product_id' => $product->id,

'station_id' => $station->id,
'amount' => $this->data['amount'],
'price' => $this->data['price'],
'mileage' => $this->data['mileage'],
]);

Log::info('GIJS - Returning tankrow ' . json_encode($tankrow));

return $tankrow;
}
However this approach is not working for me, because it is trying to insert to tank_rows based on the getColumn definition instead of the new and returned $tankRow... Column not found: 1054 Unknown column 'issuer_nr' in 'field list' (Connection: mysql, SQL: insert into tank_rows (car_id, card_id, product_id, station_id, amount, price, mileage, issuer_nr, authority_nr, serial_nr, license_plate, datetime, station_nr, station_name, product_code, product_description, vat, country_code, updated_at, created_at) values (...) I dont understand where it is retrieving these columns from, because I am specifically returning a new TankRow model object When I look in the log, it shows
local.INFO: GIJS - Returning tankrow {"car_id":5181,"card_id":5181,"product_id":5181,"station_id":5181,"amount":16,"price":"1.7237","mileage":"0"}
local.INFO: GIJS - Returning tankrow {"car_id":5181,"card_id":5181,"product_id":5181,"station_id":5181,"amount":16,"price":"1.7237","mileage":"0"}
As the result of the Log. When I dive a bit deeper into the Import action, it shows that the result of resolveRecord is used directly to save the record. But then for me it does not make sense why it still uses all the ImportColumns to try and insert the fields, instead of the record
in the extended Import Class this is called:

$this->record = $this->resolveRecord();
$this->record->save()
in the extended Import Class this is called:

$this->record = $this->resolveRecord();
$this->record->save()
My second thought was to only make ImportColumns for the actual fields I need for the tankrow, but in order to for example generate the Card instance, I need to have license plate. I cant find any solution on how to access the data from one ImportColumn in another ImportColumn. Any help or ideas would really be appreciated
zaidpirwani
zaidpirwani7mo ago
did you find a solution to this ? Currently I have 2 imports, one for my questions model and another for my answers model I would LOVE to be able to import questions and answers all from one CSV questionText, questionImage, answerAText, answerAImage,answerACorrect, answerBText, answerBImage,answerBCorrect, answerCText, answerCImage,answerCCorrect, answerDText, answerDImage,answerDCorrect I would like the importer to create a question record first and then create 4 answer records that link to the new question record so users can create only 1 excel sheet, one simple CSV and no need to worry about resolving question or knowing question ID for answer import
zaidpirwani
zaidpirwani7mo ago
I am just starting on this, so as per docs I will try with protected function afterSave(): void { // Runs after a record is saved to the database. } and then use $this->data, $this->record and $this->originalData https://filamentphp.com/docs/3.x/actions/prebuilt-actions/import#lifecycle-hooks
zaidpirwani
zaidpirwani7mo ago
I got it to work, - creating and saving other records / child records when importing I have additional columns in my csv, but only the required/main model columns in the getColumns method - I have also added a couple of OPTIONS on the upload modal itself the options are used to add additional info to the main record for the importer I use afterFill for that and then I am using afterSave to create child records from other models and records and get data of the additional columns from $this->originalData object below is some code - I have removed unnecessary stuff - so may not be directly usable, but it conveys the message - maybe error and I have not worked at UPDATING the original model or its child records - so that is an exercise for later class QuestionImporter extends Importer { protected static ?string $model = Question::class; public static function getColumns(): array { return [ ImportColumn::make('text') ->requiredMapping() ->rules(['required']) ->guess(['Question-Text']), ImportColumn::make('neverShuffleOptions') ->requiredMapping() ->boolean() ->rules(['required', 'boolean']) ->guess(['Shuffle-Options']), ]; } public static function getOptionsFormComponents(): array { return [ Select::make('quiz_id') ->relationship('quiz', 'name') ->label('Quiz'), ]; } protected function afterFill(): void { $this->record->user_id = auth()->user()->id; $this->record->quiz_id = $this->options['quiz_id']; } protected function afterSave(): void { $newAnswer = Answer::create([ 'text' => $this->originalData['Answer-Text'], 'image' => $this->originalData['Answer-Image'], 'question_id' => $this->record->id, ]); }
gladjanus43
gladjanus43OP7mo ago
Awesome! I will try this tomorrow Now I have solved it with unsetting the values from this->data and returning null in the resolve record. It works but your solution seems a lot nices
Want results from more Discord servers?
Add your server