Laravel Eloquent Relationships - php

I have a table of Questions and another table that has Answers. A question has many answers, and an answer belongs to a question. These relationships have been defined in the Question and Answer model and work as expected.
However, when I try to get a bunch of questions with the answers, Eloquent returns an empty array.
return Question::with('answers')
->where('category_id', $input['category'])
->take($input['num_questions'])
->orderBy(DB::raw('RAND()'))
->get();
I get the following response...
{
"id": "1",
"category_id": "1",
"question": "Why did the chicken cross the road?",
"feedback": "Why did you ask that?",
"created_at": "2014-04-24 16:57:48",
"updated_at": "2014-04-24 16:57:48",
"answers": []
},
{
"id": "2",
"category_id": "1",
"question": "How awesome is Laravel?",
"feedback": "That's debatable.",
"created_at": "2014-04-24 16:57:48",
"updated_at": "2014-04-24 16:57:48",
"answers": []
}
When printing the raw queries I see the following...
{
"query": "select * from `questions` where `category_id` = ? order by RAND() asc",
"bindings": [
"1"
],
"time": 2.11
},
{
"query": "select `id`, `choice`, `correct` from `answers` where `answers`.`question_id` in (?, ?, ?, ?)",
"bindings": [
"3",
"4",
"1",
"2"
],
"time": 0.92
}
When I run these queries manually, I see answers, but for some reason laravel shows an empty array. Why? What am I doing wrong here?

First remove the ->select() code off your model, then do:
return Question::with(array('answers'=>function($query)
{
$query->select(array('id', 'choice','question_id')); // question_id is mandatory because Laravel need it for mapping
}))
->where('category_id', $input['category'])
->take($input['num_questions'])
->orderBy(DB::raw('RAND()'))
->get();

I was manually selecting what fields I wanted returned in the model. This apparently breaks when you select multiple rows rather than a single row.
return $this->hasMany('Answers')->select(array('id', 'choice'));
Removing that ->select() code off the model fixed it.

Related

Exempt a specific attribute from been selected in Laravel Mysql Json Column

I am trying to return results of a specific table in Laravel while hiding a specific attribute from the returned SQL results.
Part of the JSON response is as shown
...
"custom_filters": [
{
"question_type": "single",
"question": "How long have you been an influencer",
"answer": 1
},
{
"question_type": "choice",
"question": "Have you ever went viral?",
"choices": [
"yes",
"no"
],
"answer": "yes"
},
{
"question_type": "single",
"question": "How many followers do you have on Instagram",
"answer": 12000
}
],
...
Expected Output
...
"custom_filters": [
{
"question_type": "single",
"question": "How long have you been an influencer",
},
{
"question_type": "choice",
"question": "Have you ever went viral?",
"choices": [
"yes",
"no"
]
},
{
"question_type": "single",
"question": "How many followers do you have on Instagram"
}
],
...
I want the answer part of the JSON OBJECT to be hidden/removed in the final output as shown above. I know i could get the results from my Eloquent model and filter the results accordingly discarding the answer part. but I feel like there is a better way of doing this.
by the way here is the code for fetching the results
$posts = Posts::where('status', 'published')->orderBy('post_id', 'desc')
->paginate(30);
use this
Posts::where('status', 'published')->orderBy('post_id', 'desc')->whereJsonContains('json_data', [['custom_filters' => ['answer' => 'yes']]])->get()

Ambiguous results for the same code- Laravel Eloquent

I'm fetching some models from database using Laravel Eloquent. The code is like this-
$jobs = User::find($request->user_id)
->jobs
->where('status', $request->status);
return $jobs;
For the value of status = 0, the output is an array of objects-
[
{
"id": 6,
"name": "Mwkkdndnnd",
"desc": ".amsmsmskxnndsmsms",
"credits": "100",
"category_id": "1",
"views": "0",
"user_id": "11",
"deadline": "2017-07-19",
"status": "0",
"assigned_to": null,
"assigned_on": null,
"closed_at": null,
"created_at": "2017-07-17 09:57:28",
"updated_at": "2017-07-17 09:57:28"
}
]
But for the value of status = 1, the output is an object containing multiple objects-
{
"1": {
"id": 7,
"name": "Promote my company ",
"desc": "My brand needs to be promoted.",
"credits": "100",
"category_id": "1",
"views": "0",
"user_id": "11",
"deadline": "2017-07-20",
"status": "1",
"assigned_to": "12",
"assigned_on": "2017-07-18 09:32:51",
"closed_at": null,
"created_at": "2017-07-17 18:43:50",
"updated_at": "2017-07-18 09:32:51"
}
}
What is wrong the code?
Any help would be appreciated
Based on my experience with chaining another where with find, there can be unexpected behavior. So you should attempt to simply change everything to where, i.e,
$jobs = User::where('id', $request->user_id)->jobs->where('status', $request->status);
Because generally, the find method will return One model that matches, and calling a where on that model the key is not preserved so it just does a query on all the records instead of one. I may not be entirely correct, but I am pretty sure about find with where in this situation.
I would propose in order to actually see whats wrong, simply call the Job model directly and find where you have that user id (in case the relationship exists in this direction)
Job::where('user_id', $request->user_id)->get();
In fact, my observation is that you might be nursing a potential issue where if user with that id does not exist, then one uncaught error is triggered to the user. Do a check somehow in that place. findOrFail() might be helpful.

Tricky Eloquent query

I've been struggling to figure out a table relationship for the past two days, I'm sure that the solution is simple but it is alluding me.
Four tables/models are involved (including a pivot table):
Skill - skills table. A Skill belongs to a SkillGroup and belongs to many Candidates (candidate_skill pivot table)
Candidate - candidates table. Contains personal information on a candidate, not terribly related to the issue.
SkillGroup - skill_groups table. Each Skill Group has many Skills.
I want to be able to retrieve Skill objects possessed by a Candidate grouped by the SkillGroup. For example:
[
{
"id": 1,
"title": "Information Technology (Skill Group)",
"slug": "information-technology",
"created_at": "2016-05-07 23:58:23",
"updated_at": "2016-05-07 23:58:23",
"skills": [
{
"id": 1,
"title": "Web Development (Skill)",
"slug": "web-development",
"description": "Web developers primarily focus on the back-end of websites",
"created_at": "2016-05-07 23:58:55",
"updated_at": "2016-05-07 23:58:55",
"skill_group_id": 1,
"candidates": [
{
"first_name": "John (Candidate)",
"last_name": "Smith",
"pivot": {
"skill_id": 1,
"candidate_id": 6
}
}
]
}
]
}
]
This is easy to accomplish with the following code, but I want to retrieve only results (SkillGroup -> Skills -> Candidate) for a specific candidate.
\App\SkillGroup::with('skills.candidates')->get();
I have tried the following (and everything else I can think of), the candidate_id does not seem to affect the query - I see skills that are not possessed by the given candidate.
\App\SkillGroup::with(['skills.candidates' => function($query) {
$query->whereCandidateId(6);
}])->get();
Any help would be greatly appreciated, thanks!
EDIT
Thanks to #Giedrius Kiršys, I was able to come up with the following:
\App\SkillGroup::with(['skills.candidates' => function($query) {
$query->wherePivot('candidate_id', 8)->addSelect('candidates.id', 'first_name', 'last_name');
}])->whereHas('skills.candidates', function($q) {
$q->whereCandidateId(8);
})->get();
This only retrieves SkillGroup results with Skills with a Candidate with the given ID.
You want to query by pivot table attribute, but You are querying by candidates.candidate_id attribute.
You can do it like this:
\App\SkillGroup::with(['skills.candidates' => function($query) {
$query->wherePivot('candidate_id', 6);
}])->get();

Laravel Many to many - Unexpected result set on ->select()

I wonder if anyone can help, as I've hit a wall and still learning Laravel ORM. Can anyone explain why, when I run:
public function locationTags(){
return $this->hasMany('App\UserHasLocationTags', 'user_id')
->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id');
}
I get this result set: (snipped...)
{
"id": 1,
"created_at": "2015-05-13 13:04:56",
"updated_at": "2015-05-13 13:04:56",
"email": "REMOVED",
"firstname": "REMOVED",
"lastname": "REMOVED",
"location_id": 0,
"deleted_at": null,
"permissions": [],
"location_tags": [
{
"user_id": 1,
"location_tag_id": 1,
"id": 1,
"created_at": "2015-05-13 13:06:28",
"updated_at": "2015-05-13 13:06:28",
"name": "Test Tag 0",
"location_id": 1,
"deleted_at": null
},
{
"user_id": 1,
"location_tag_id": 2,
"id": 2,
"created_at": "2015-05-13 11:40:21",
"updated_at": "2015-05-13 12:56:13",
"name": "Test Tag 123",
"location_id": 1,
"deleted_at": null
}
]
}
Which is ace! However, when I start to select the columns I want from the location_tags join, with:
public function locationTags(){
return $this->hasMany('App\UserHasLocationTags', 'user_id')
->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id')
->select('lt.id', 'lt.name');
}
I end up with:
{
"id": 1,
"created_at": "2015-05-13 13:04:56",
"updated_at": "2015-05-13 13:04:56",
"email": "REMOVED",
"firstname": "REMOVED",
"lastname": "REMOVED",
"location_id": 0,
"deleted_at": null,
"permissions": [],
"location_tags": []
}
Can someone explain what's going on? And possibly point me in the right direction to limit the selects? Thanks!
Update
I've also tried:
$query = \App\User::with(['permissions', 'locationTags' => function($query){
$query->select('lt.id', 'lt.name');
}]);
Which returns the same result :(
Figured it out. The key here was that you must include a select() value of at least one key that Laravel can use to map the result set. In my case it was user_id, like so:
public function locationTags(){
return $this->hasMany('App\UserHasLocationTags', 'user_id')
->join('location_tags AS lt', 'lt.id', '=', 'location_tag_id')
->select('user_id', 'lt.name', 'location_tag_id');
}
Which then returns a much nicer results set:
{
"id": 1,
"created_at": "2015-05-13 13:04:56",
"updated_at": "2015-05-13 13:04:56",
"email": "REMOVED",
"firstname": "REMOVED",
"lastname": "REMOVED",
"location_id": 0,
"deleted_at": null,
"permissions": [],
"location_tags": [
{
"user_id": 1,
"name": "Test Tag 0",
"location_tag_id": 1
},
{
"user_id": 1,
"name": "Test Tag 123",
"location_tag_id": 2
}
]
}
Hope this helps someone out in the future, because it kept me guessing for a good couple of hours.
I'm sorry but you've gone a little sideways here. The relationship definition should just define the Relationship. It's a powerful feature that supports many other aspects of the ORM. What you've done here is more or less build a custom Query, severely limiting the effectiveness of the relationship.
Up until recently, the proper way would have looked something like this.
// class User
public function userHasLocationTags() {
$this->hasMany('App\UserHasLocationTags', 'user_id');
}
// class UserHasLocationTags
public function locationTags() {
$this->hasMany('App\LocationTags', 'location_tag_id');
}
And you would eager load all the results like this.
$user = User::where('id', 1)->with('userHasLocationTags.locationTags')->first();
The code above produces in 3 queries. One to get the User, one to get all the UserHasLocationTags and a one to get all the LocationTags. This may seem wasteful on first blush but consider the following.
$users = User::take(100)->with('userHasLocationTags.locationTags')->get();
Again, this is only 3 queries but now you have loaded 100 users with all their location tags.
But I can see you are a man with an eye for efficiency, and loading all the intermediate relationships and the whole nested hierarchy probably doesn't sit well with you. Well good news! Laravel 5 has added another relationship type for just this situation. hasManyThrough (scroll down a bit to find it).
Has Many Through
The "has many through" relation provides a convenient short-cut for accessing distant relations via an intermediate relation. For example, a Country model might have many Post through a User model.
So in your case it may look something like this...
// class User
public function locationTags()
{
return $this->hasManyThrough('App\LocationTags', 'App\UserHasLocationTags');
}
$users = User::take(100)->with('locationTags')->get();
So now we are down to two queries. If you feel the need to optimize this further and only select specific columns, you can modify the eager load.
$users = User::take(100)->with(['locationTags' => function ($query)
{
$query->select('user_id', 'name', 'location_tag_id');
})->get();
And if you do that frequently, you should wrap it up in a scope.

How to update document in one collection when a document of other collection is changed or updated in MongoDB

I've created two collections "Users" and "Posts".
Users document structure is as follows:
{
"_id": {
"$oid": "54dde0e32a2a999c0f00002a"
},
"first_name": "Vamsi",
"last_name": "Krishna",
"email": "vamshi#test.com",
"password": "5f4dcc3b5aa765d61d8327deb882cf99",
"date_of_birth": "1999-01-05",
"gender": "male",
"status": "Active",
"date_created": "2015-02-13 12:32:50"
}
While posts document structure is:
{
"_id": {
"$oid": "54e1a2892a2a99d00500002b"
},
"post_description": "Test post 1",
"posted_by": {
"id": "54dde0e32a2a999c0f00002a",
"first_name": "Vamsi",
"last_name": "Krishna",
"gender": "male"
},
"posted_on": "2015-02-16 08:55:53",
"comments": [],
"likes": {
"count": 0,
"liked_by": []
}
}
My query is that when user updates his information it should reflect everywhere like posted by, commented by and liked by. How can I achieve that?
I'm using PHP.
Thanks!!
Mongodb does not have a notion similar to sql on update cascade, so you have to do this in your application (whenever you update user information, update all other documents that relate to this user in other collections).
As you might have guessed this is super inefficient when there are a lot of such documents, which means that your schema is bad. Just have a userID in your document and this will link to your user's collection.

Categories