I have been working on a survey-type web app through stages - questions - answers, right now I already have the part of saving the answers of each question and the questions in their respective stage, but when updating or deleting a stage, it is updated with the latest information, but for example if you delete a question, it does not appear visually, but in the database the question remains with its answers taking up space
I describe in img what the problem is
I have 2 questions in the stage, as you can see, if I delete one, I would have to stay as shown in the Image below , but it is not like that, it always shows me the same ones, if I add it obviously if it adds it to me, I don't know how to delete that relation in the database so that it is updated
StepsEdit.blade.php
@extends('layouts.layout')
@section('content')
<form action="/dashboard/formulario/stages/{{ $stage->id }}" method="post">
@method('put')
<h2>ETAPA</h2>
<label>Titulo</label>
<input required type="text" name="title" value="{{ $stage->title }}">
<label>Descripcion</label>
<input required type="text" name="description" value="{{ $stage->description }}">
<h2>PREGUNTAS</h2>
<table>
<tbody>
@foreach($questions as $question)
<tr>
<td><input required name="questions[{{ $question->id }}]" type="text" value="{{ $question->title }}" ><td/>
<td>
<select name="types[{{ $question->id }}]" id="">
@foreach($types as $type)
@if($type->name == $question->type->name)
<option selected value="{{ $type->id }}">{{ @strtoupper($type->name) }}</option>
@else
<option value="{{ $type->id }}">{{ @strtoupper($type->name) }}</option>
@endif
@endforeach
</select>
</td>
<td>
<fieldset>
<legend>Respuestas</legend>
@foreach($question->answers as $answer)
@if($question->type->name == App\Type::OPCIONES['U'])
<input required name="answers[{{ $question->id }}][{{ $answer->id }}]" type="text" value="{{ $answer->title }}">
@elseif($question->type->name == App\Type::OPCIONES['C'])
<input required name="answers[{{ $question->id }}][{{ $answer->id }}]" type="text" value="{{ $answer->title }}">
@elseif($question->type->name == App\Type::OPCIONES['M'])
<input required name="answers[{{ $question->id }}][{{ $answer->id }}]" type="text" value="{{ $answer->title }}">
@endif
@endforeach
</fieldset>
<a href="">Agregar</a>
</td>
</tr>
@endforeach
</tbody>
</table>
<input class="btn btn-primary" type="submit" value="Guardar">
</form>
@endsection
Model Stage
class Stage extends Model
{
protected $fillable = [
'id',
'title',
'description'
];
public function questions(){
return $this->hasMany('App\Question');
}
}
Model Question
class Question extends Model
{
protected $table = 'questions';
protected $fillable = [
'id',
'title',
'stage_id',
'type_id',
];
public function stage() {
return $this->belongsTo('App\Stage');
}
public function type(){
return $this->belongsTo('App\Type');
}
public function answers(){
return $this->hasMany('App\Answer');
}
}
Model Response
class Answer extends Model
{
public function __construct(array $attributes = ['title'])
{
parent::__construct($attributes);
}
protected $table = 'answers';
protected $fillable = [
'title',
'question_id'
];
public function question() {
return $this->belongsTo('App\Question');
}
public static function create(array $array) {
$answers = [];
return $answers;
}
}
Part of the controller that updates
public function update(Request $request, Stage $stage)
{
$rules = [
'title' => 'max:150|string|unique:stages',
'description' => 'max:250|string'
];
$stage = Stage::findOrFail($stage)->first();
if ($request->get('title') != $stage->title ||
$request->get('description') != $stage->description) {
$this->validate($request, $rules);
$stage->title = $request->get('title');
$stage->description = $request->get('description');
$stage->save();
}
/*--------- SE RECORRE TODAS LAS PREGUNTAS ---------*/
foreach ($request->get('questions') as $id_q => $question) {
$question_test = new Question([
'id' => $id_q,
'title' => $question
]);
$type = Type::findOrFail($request->get('types')[$id_q]);
/*--------- SE ACTUALIZA LOS DATOS DE LA PREGUNTA ---------*/
$question_test->stage()->associate($stage);
$question_test->type()->associate($type);
if ($question_test->title == '') {
/*TODO: AGREGAR ENVIO DE ERROR*/
}
$question_test = Question::updateOrCreate([
'id' => $question_test->id
],[
'title' => $question_test->title,
'stage_id' => $question_test->stage_id,
'type_id' => $question_test->type_id
]);
/*--------- SE ACTUALIZA LOS RESPUESTAS DE LA PREGUNTA ---------*/
foreach ($request->get('answers') as $key => $answers) {
if ($id_q == $key) {
foreach ($answers as $id_a => $answer) {
if ($answer == '') {
/*TODO: AGREGAR ENVIO DE ERROR*/
}
$question_test->answers()->updateOrCreate([
'id' => $id_a
],[
'title' => $answer
]);
}
}
}
}
return redirect('/dashboard/formulario/stages');
}
migrations
stages
class CreateStagesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('stages', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('stages');
}
}
Questions
class CreateQuestionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('questions', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->unsignedInteger('stage_id');
$table->unsignedInteger('type_id');
$table->timestamps();
$table->foreign('stage_id')->references('id')->on('stages');
$table->foreign('type_id')->references('id')->on('types');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('questions', function (Blueprint $table) {
$table->dropForeign('questions_type_id_foreign');
$table->dropForeign('questions_stage_id_foreign');
});
Schema::dropIfExists('questions');
}
}
Answers
class CreateAnswersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('answers', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->unsignedInteger('question_id');
$table->timestamps();
$table->foreign('question_id')->references('id')->on('questions');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('answers', function (Blueprint $table) {
$table->dropForeign('answers_question_id_foreign');
});
Schema::dropIfExists('answers');
}
}
The "problem" is that Laravel doesn't have a (simple) way to synchronize one-to-many relationships, as it does for many-to-many relationships, and it works like a charm.
Here you can go to several solutions:
For simplicity, I am going to expose the last solution that I propose, it is rather rudimentary, but it allows you to understand the concept in case you want a more professional implementation from the model or adding your service/package.
I'm going to show only the parts of the controller method that I need.