Attaching a hasOne model to another Laravel/Eloquent model without specifying id - laravel

Background
Given we have the following two tables where type_id references a row in questionType:
question
id | type_id | description
---+---------+------------
1 | 1 | A nice question
.. | .. | ..
questionType
id | name
---+----------------
1 | Multiple-choice
.. | ..
with the following Eloquent models:
class Question extends Model {
public function type() {
return $this->hasOne( 'QuestionType', 'id', 'type_id' );
}
}
class QuestionType extends Model {
}
Question 1
How can I add a new question that references an existing question type without manually doing anything with ids? For example the following works but is ugly imo since I have to manually assign the corresponding question type id:
$q = new Question;
$q->type_id = 1; // Multiple-choice
$q->description = 'This is a multiple-choice question';
$q->save();
One would think there was a way to let the ORM handle the id-assignment (isn't the point to avoid stuff like this with ORMs?), something along the lines of (this does not work in Eloquent ORM):
$q = new Question;
$q->type = QuestionType.where('name', '=', 'Multiple-choice');
$q->description = 'This is a multiple-choice question';
$q->save();
Question 2
In relation to question 1, how would I go about adding a new question that references a new question type without manually doing anything with ids? Similarly I imagine something along the lines of:
$t = new QuestionType;
$t->name = 'Another type';
$q = new Question;
$q->type = $t;
$q->description = 'This is a multiple-choice question';
$q->save();
Here I'd like $q->save() to save both the new question type and question (or something similar).
The following works, but again I'm assigning the id myself which I believe the ORM should handle:
$t = new QuestionType;
$t->name = 'Another type';
$t->save();
$q = new Question;
$q->type = $t->id;
$q->description = 'This is a multiple-choice question';
$q->save();
I've tried playing with different combinations of save(), update() methods without luck. I also looked for attach() which exists on the hasMany relationships but seem to be missing in hasOne.

First off, you misunderstood the relation you refer to.
Here's what you need:
// Question model
public function questionType()
{
return $this->belongsTo('QuestionType', 'type_id');
}
// QuestionType model
public function questions()
{
return $this->hasMany('Question', 'type_id');
}
then you can link them together like this:
$questionType = QuestionType::where(..)->first();
$question = new Question;
... // do something with it
// associate
$question->questionType()->associate($questionType);
// or the other way around - save new question and link to the type:
$questionType->questions()->save($question);
You can explicitly pass an id to associate as well:
$question->type_id = $someTypeId;
$question->save();
You can't do this:
$question->questionType = $someQuestionType;
for this way Eloquent handles model attributes, not relations.
Question 2:
$questionType = new QuestionType(['name' => 'multiple']);
$questionType->save();
$question = new Question([ ... some values ... ]);
// then either this way:
$questionType->questions()->save($question);
// or, again, the other way around:
$question->questionType()->associate($questionType);
$question->save();

Answering question 1 for me both methods are good, you don't need to change anything.
Answering question 2 you should do it as you showed. ORM won't create automatically QuestionType until you use manually save method.
For example if you used code:
$t = new QuestionType;
$t->name = 'Another type';
$t2 = new QuestionType;
$t2->name = 'Another type 2';
$q = new Question;
$q->type = $t; // what here - $t2 or $t ?
$q->description = 'This is a multiple-choice question';
$q->save();
what ORM should decide? Question isn't connected to any of $t or $t2 so ORM won't decide it for you. You need to tell ORM what's the type.
I'm not an ORM/Eloquent expert but I think you expect too much from ORM. ORM shouldn't guess what you want to do. It help you manage relations or objects but it won't associate objects if you don't tell it to do so.
You could however try with mutator. You could add to your Question model:
public function setTypeAttribute($value)
{
$value->save();
$this->attributes['type_id'] = $value->id
}
and now it should be possible to use $q->type = $t; (However I haven't tested it)

Related

Laravel Eloquent Query containing pivots and relations

I want to make graphs based on values i get from my questionnaire. This is how my database looks like (Only showing the ones I need):
questionnaires
id
client_id
questionnaire_answers
id
questionnaire_id
questionnaire_question_id
answer
questionnaire_questions
id
question
What I want is basically get all the answers of question 1 (of all their questionnaires) from a specific client.
The idea is that they answer a question that is based on severity, so 1 - 5 basically.
I've tried this in the controller:
$questionnaires = JsonResource::collection(
Questionnaire::where('client_id', '=', 2)
->with([
'clients',
'questionnaire_answers',
'questionnaire_answers.questionnaire_question',
'questionnaire_answers.questionnaire_question.questionnaire_question_type'
])
->get()
);
dd($questionnaires);
All relations work.
What I want is basically get all the answers of question 1 (of all their questionnaires) from a specific client. The idea is that they answer a question that is based on severity, so 1 - 5 basically.
I'd redo the query so it starts from the Question model.
Assuming the following relationships:
Question belongsToMany Questionnaire (using Answer as the pivot model)
// Question model
public function questionnaires()
{
return $this->belongsToMany(Questionnaire::class)
->withPivot('answer')
->as('answer')
->using(Answer::class);
}
$client_id = ...;
$question = Question::query()
->whereHas('questionnaires', function ($questionnaire) use ($client_id) {
$questionnaire->where('client_id', $client_id);
})
->with([
'questionnaires' => function ($questionnaire) use ($client_id) {
$questionnaire->where('client_id', $client_id)
->with('client');
},
'question_type',
])
->find(1);
foreach ($question->questionnaires as $questionnaire) {
$questionnaire->client->... // client attributes
$question->.... // question attributes
$questionnaire->answer->... // answer attributes.
$questionnaire->question_type->... // question_type attributes
}

How to add data to relative table Laravel?

I have model Object and model Prototype.
Incoming data($_POST) is array prototype_id[]:
prototype_id[] = 1;
prototype_id[] = 2;
prototype_id[] = 3;
That should be saved in table ObjectPrototypes. No i dont have this model.
So, in controller I do:
$object = new Object();
$object->name = "Object";
How to add/attach prototype_id[] to saved object $object?
Important:
Object can has one or more prototypes
One (quick) solution could be to do raw insert on this table. Assuming pivot table name is object_prototype, here is what you could do:
$object = new Object();
$object->name = "Object";
$object->save();
foreach($request->input('prototype_id') as $p) {
\DB::table('object_prototype')->insert([
'object_id' => $object->id,
'prototype_id' => $prototype
]);
}
It should work, thought it's not really Laravelish.
You may also work with pivot, but it requires more boilerplate. You may have a look here: https://laravel.com/docs/5.4/eloquent-relationships#updating-many-to-many-relationships and here: https://laravel.com/docs/5.4/eloquent-relationships#one-to-many-inverse That's what I do usually but it's a bit too long for a short answer and I think Laravel Doc is more complete.

Laravel - insert multiple rows with pivot table data

I have 3 tables:
Questions
Answers
question_answers
Question Model:
public function answer()
{
return $this->belongsToMany(Answer::class);
}
I created a page where I type a question and 4 answers, and I insert them to my database.
$question = new Question;
$question->title = $request->question_title;
$question->save();
$answers = $request->answers;
$answer = Answer::insert($answers);
How I can insert the question_answers for each question too?
question_answers looks like this:
I couldn't find a clue how to do it with the insert method
Since you're using many-to-many relationship, you should use attach() method. For example:
$question = Question::create($request->question); // Save question.
$answersIds = [];
foreach ($request->answers as $answer) {
$answersIds [] = Answer::create($answer)->id; // Save each answer.
}
$question->answers()->attach($answersIds); // Attach answers to the question.
Also you can't use insert() to bulk insert answers, because you need to get answer IDs to attach answers to the question.

Laravel Eloquent: Store model together with related submodels

i got the models Exam and Question. Simplified code:
class Exam {
public function questions() {
return $this->hasMany('Question');
}
// ...
}
class Question {
public function exam() {
return $this->belongsTo('Exam');
}
// ...
}
Now i wanna upload a file with exam data. The parser class shall create the exam without saving it. Then gradually the questions should be created and added to the exam. At the end everything should be saved in a transaction.
class ExamParser {
$exam = new Exam();
// ...
while ($linesRemaining) {
$question = new Question();
// ...
$exam->questions[] = $question; // something like this?
}
$exam->saveTogetherWithQuestions(); // how do i realize this?
}
I know basically how to save related models but not how I can just relate them and save the whole construct later.
Create an array of Question first. Then create an Exam, and save all questions via Exam's relations. Put all codes in a transaction.
Updated: Moved model creation out of transaction execution.
$questions = [];
while ($linesRemaining) {
$question = new Question();
// ...
$questions[] = $question;
}
$exam = new Exam();
// ...
DB::transaction(function() {
$exam->save();
$exam->questions()->saveMany($questions);
});
Important note that you have to save Exam before attach Question to it. Parent Exam must have its own primary key first (by saving itself) to allow attaching success. Otherwise, you will have Question saved without parent's id.

One to Many relation - how to implement push

I am trying to do an insert which will create a parent record for one table and then insert records that link back to the parent record into another.
In other words this: User completes Course information form, then completes a series of questions on the same page. On submission, the course information is inserted into its own table then questions are inserted into a separate one.
My Course model is this:
class CourseVerification extends Eloquent
{
public function courseverificationqanda()
{
return $this->hasMany('CourseVerificationQandA', 'id', 'verification_id');
}
My Question model is this:
class CourseVerificationQandA extends InnovedBaseModel
{
public function courseverification()
{
return $this->belongsTo('CourseVerification');
}
On form submission, my controller is doing this:
// create course verification record first
$veri = new CourseVerification;
$veri->verification_date = $input['verification_date'];
// create collection to store questions
$collection = new Illuminate\Database\Eloquent\Collection();
// loop through submitted questions and push them to the collection
for($i = 0; $i < count(Input::get('question_id')); $i++) {
$answer = new CourseVerificationQandA;
$answer->question_id = $input['question_id'][$i];
$answer->answer = $input['answer'][$i];
$answer->additional_notes = $input['additional_notes'][$i];
$collection->push($answer);
}
// add collection to quesetion relation
$veri->courseverificationqanda = $collection;
// insert both course record and questions
$veri->push();
The push method then errors and debugs the SQL command
insert into `CourseVerification`
(`verification_date`, `topic_id`, `course_id`, `verifier_id`,`iv_status`,
`verification_type`, `courseverificationqanda`, `created_by`)
values
(29/10/2014, 1294, 47, 1, 1, I, [{"question_id":"2","answer":"0","additional_notes":""},
{"question_id":"3","answer":"0","additional_notes":""},
{"question_id":"4","answer":"0","additional_notes":""}], 1)
As you can see, the assignment of the collection to $veri->courseverificationqanda is then getting treated as a table column in the SQL query when it is actually a relationship to the question table.
Any ideas?
You have a few mistakes there.
You don't assign collection to the relation. You need to load that relation (as a collection in this case) and push on it, then just save everything:
$veri = new CourseVerification;
$veri->verification_date = $input['verification_date'];
// Save the parent model before you can add its children
if ($veri->save())
{
// loop through submitted questions and push them to the collection
for($i = 0; $i < count(Input::get('question_id')); $i++) {
$answer = new CourseVerificationQandA;
// associate with the parent model
// explicitly
$answer->verification_id = $veri->getKey();
// or the eloquent way
// $answer->courseverification()->associate($veri);
$answer->question_id = $input['question_id'][$i];
$answer->answer = $input['answer'][$i];
$answer->additional_notes = $input['additional_notes'][$i];
$veri->courseverificationqanda->push($answer);
}
}
// save both course record and questions
$veri->push();
Another thing are your relations, which are both wrong:
// this is how it should look like
// CourseVerification
public function courseverificationqanda()
{
return $this->hasMany('CourseVerificationQandA', 'verification_id', 'id');
}
// QandA
public function courseverification()
{
return $this->belongsTo('CourseVerification', 'verification_id');
}
In firsts case you swapped the keys, so it would work but not the way it should.
In second case you didn't specify the foreign key at all, so Eloquent would look for courseverification_id in the table (method_name_id).

Resources