Laravel graphql Upsert Directive not working as expected - laravel

Please Correct me if I am wrong.
I have two models "Users" and "Profiles"
Scenario 1:
In the "Profiles" model I have defined "user_id" as primary key and foreign key to "users.id"
class Profile extends Model
{
/**
* primary key
*/
protected $primaryKey = 'user_id';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'user_id', 'country'
];
at schema.graphql
type Mutation {
upsertProfile(user_id: ID, country: String): Profile #upsert
}
type Profile {
user_id: ID!
country: String
user: User #belongsTo
}
Let's suppose there is an id 28 in users table. When I try to run mutation:
mutation {
upsertProfile(user_id: 28, country: "India") {
country
}
}
It works fine and updates the country, but if there is no user_id 28 exists, as per definition it should create one.
As it defines the user_id column is not an auto-increment column
I did one more test
Scenario 2:
I removed 'user_id' as the primary key and added the id column as the primary key and auto-increment.
Let suppose id 1 is there, then after running the mutation:
mutation {
upsertProfile(id: 1, country: "India") {
country
}
}
I got the expected result.
But when I try to run mutation
mutation {
upsertProfile(user_id: 28, country: "India") {
country
}
}
I am getting duplicates of result with new id everytime(auto-increment) whenever I ran this mutation.
My question,
How to use upsert if user_id exists then update the row, else create a row.
As laravel createOrUpdate function prototype contains checking on multiple columns to get a row updated, is there any way to do the same on upsert directive.
BTW, at scenario 1 I debugged query and found that insert query is running but I am getting an exception at the grpahql-playground result
{
"errors": [
{
"debugMessage": "No query results for model [App\\Models\\Profile] 0",
"message": "Internal server error",
"extensions": {
"category": "internal"
},
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"upsertProfile"
],
....
]

Make sure the ID attribute is fillable/unguarded.

In my case it was a timestamp issue. I put this line of code in my model and problem was solved
public $timestamps = false;

Related

How to Query Multiple Pivot Relationship in Laravel Lighthouse

Hi, there I'm fairly new to laravel-lighthouse. I've read the documentation but unfortunately, there is no detail about this issue. My question is How can I query more than one pivot table. I have an Item table that belongs to many other Models.
The code is given below.
class Item extends Model
{
public function provisions(): BelongsToMany
{
return $this->belongsToMany(Pwmp::class,
'project_wise_material_provision_items', 'item_id', 'provision_id')
->withPivot('qty', 'unit_cost')
->withTimestamps()
->as('provision_items');
}
public function procurements(): BelongsToMany
{
return $this->belongsToMany(Pwpp::class,
'project_wise_procurement_plan_items', 'item', 'procurement')
->withTimestamps()
->as('procurement_items')
->withPivot('qty', 'unit_cost');
}
public function log_sheets(): BelongsToMany
{
return $this->belongsToMany(Braols::class,
'bid_receiving_and_opening_log_sheet_items', 'item', 'braols')
->withTimestamps()
->as('log_sheet_items')
->withPivot('qty', 'unit_cost', 'tax');
}
public function workshop(): BelongsToMany
{
return $this->belongsToMany(TransformerWorkshop::class,
'transformer_workshop_items', 'item', 'workshop')
->as('workshop_items')
->withTimestamps()
->withPivot('qty', 'flow', 'type');
}
public function sub_office(): BelongsToMany
{
return $this->belongsToMany(MaterialReceiptInSubOffice::class,
'material_receipt_in_sub_office_items', 'item', 'receipt')
->as('sub_office')
->withTimestamps()
->withPivot('id','qty', 'type');
}
Here is my GraphQL schema
type Item{
id: ID!
workshop_items: [TransformerWorkShopItemsPivot]
provision_items: [ProjectWiseMaterialProvisionItem]
sub_office: MaterialReceiptInSubOfficeItemsPivot
}
type MaterialReceiptInSubOfficeItemsPivot{
qty: Int
type: Int
id: ID
}
type ProjectWiseMaterialProvisionItem {
qty: String
unit_cost: String
}
type TransformerWorkShopItemsPivot{
qty: Int
type: Int
flow: Int
}
Now when i query the api
```graphql
material_receipt_in_sub_office_store(id: 4){
id
items{
id
provision_items{
unit_cost
qty
}
sub_office{
type
id
qty
}
}
}
}
```
it returns the following result
{
"data": {
"material_receipt_in_sub_office_store": {
"id": "4",
"items": [
{
"id": "431",
"name": "Drill Machine",
"image": "crane2.jpg",
"category_id": {
"id": "993"
},
"provision_items": null,
"sub_office": {
"type": null,
"id": null,
"qty": null,
"__typename": "MaterialReceiptInSubOfficeItemsPivot"
}
},
I am unable to fetch the multiple pivot table data.
however, if I query pivot it returns the data of only one pivot.
First, it looks like you don't use Laravel model's relationships into your type definition.
By specifying a #belongsToMany / #hasMany directive (see more here) you'll be able to optimize your queries.
type Item{
id: ID!
workshop_items: [TransformerWorkShopItemsPivot] #hasMany(relation: "workshop")
provision_items: [ProjectWiseMaterialProvisionItem] #hasMany(relation: "provisions")
sub_office: MaterialReceiptInSubOfficeItemsPivot #hasMany
}
I'm just not sure about the fact you give table aliases in your relation definition..
Then, by doing the query, you can fetch as much pivot relations as you want. Never got relation problems by doing this.

How to select custom attributes fields with relation in Laravel?

I have tables emails and email_attachments as follows:
Schema::create('emails', function (Blueprint $table) {
$table->id();
$table->string('email')->nullable(false);
$table->string('subject')->nullable(false);
$table->text('body')->nullable(false);
$table->timestamps();
});
Schema::create('email_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('email_id')->nullable(false);
$table->string('file_name')->nullable(false);
$table->string('file_path')->nullable(false);
$table->foreign('email_id')->references('id')->on('emails');
});
And my models classes Email and EmailAttachment as follows:
class Email extends Model
{
use HasFactory;
protected $fillable = ['email', 'subject', 'body'];
function attachments()
{
return $this->hasMany(EmailAttachment::class);
}
}
class EmailAttachment extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = ['email_id', 'file_name', 'file_path'];
protected $appends = ['url'];
function email()
{
return $this->belongsTo(Email::class);
}
public function getUrlAttribute()
{
return Storage::disk('public')->url($this->file_name);
}
}
Doing this return Email::with('attachments')->get(); I get everything as response:
{
"id": 1,
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"created_at": "2021-02-26T23:32:08.000000Z",
"updated_at": "2021-02-26T23:32:08.000000Z",
"attachments": [
{
"id": 1,
"email_id": 1,
"file_name": "foo.jpg",
"file_path": "/home/user/laravel/storage/app/foo.jpg",
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]
}
And this return Email::with('attachments:file_name')->get(); I get attachments as an empty array:
{
"id": 1,
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"created_at": "2021-02-26T23:32:08.000000Z",
"updated_at": "2021-02-26T23:32:08.000000Z",
"attachments": []
}
How can I get only this?
{
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"attachments": [
{
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]
}
I already did this tries:
return Email::with('attachments:url')->get(['email', 'subject', 'body']);
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `url` from `email_attachments` where `email_attachments`.`email_id` in (0))"
return Email::with('attachments')->get(['email', 'subject', 'body', 'url']);
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `email`, `subject`, `body`, `url` from `emails`)"
return Email::with('attachments:url')->get(['email', 'subject', 'body', 'url']);
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `email`, `subject`, `body`, `url` from `emails`)"
PS.: I'm using Laravel 8.29.0.
There are several ways you can achieve this, usually, in other frameworks, these objects tend to be called "DTO objects" ( Data Transfer Objects )
For Laravel, you could simply get the resulting query from the database, and perform simple array transformations on it using the collections() API.
For example:
$collection = Email::with('attachments')->get();
$collection->map(function ($record) {
return ['email' => $record->email, 'subject' => $record->subject, 'attachments' => $record->attachments ];
});
Now you would just return this as the response:
$response = $collection->map(...);
return response()->json($response, 200);
From your controller.
Though you should be aware that these transformations happen in PHP and not SQL side of things, but the user won't know which field you've returned from the database. https://laravel.com/docs/8.x/queries#select-statements
You can add ->select() anywhere between the other queries and ->get() because until that point you're still in the query builder mode ( You're making your SQL query )
So you could do this:
Email::with('attachments')->select('my_field','my_field2')->get();
Keep in mind that this works for any query builder methods. https://laravel.com/docs/8.x/queries
Finally, you may look into API resources, as they provide a way you can format your responses. The documentation will explain it with far more better job then me: https://laravel.com/docs/8.x/eloquent-resources
When loading specific fields in eager loading you should also select the identifier of the record which is used to map the related records. So in your case email (hasMany) attachments via foreign key email_id which points to Email
See Eager Loading Specific Columns
When using this feature, you should always include the id column and any relevant foreign key columns in the list of columns you wish to retrieve.
Email::with('attachments:email_id,file_name')->get();
Now this will give you 2 fields from attachments relation
"attachments": [
{
"email_id": 1,
"file_name": "foo.jpg"
}
]
So if you need url attribute which is a custom attribute and not physically present in your table you will need to trick the query builder to produce such SQL query with url attribute included, initially with null value and later on when this query output is transformed by laravel data transformation your url's accessor getUrlAttribute() will fill this attribute with your expected output
Email::with(['attachments' => function ($query) {
$query->select(['email_id', 'file_name', \DB::raw('null as url')]);
}])->get();
Now attachments collection will have following data in it
"attachments": [
{
email_id": 1,
"file_name": "foo.jpg",
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]

Relation with condition in laravel

I have 3 types of author in my app. In table of post author stored in column post_author like "AuthorType_id", where AuthorType can be user, group or system.
The question is: can I create a conditional relation?
What this means.
Relation author() checks a value and return related model.
Data in column stores like "user_123", "system_123", "group_123" where user, system or group is AuthorType and 123 - is id of record in related table.
I'm really confused about this case.
I'm using laravel 6.
Ex:
public function author() {
$type = explode("_", $this->post_author);
switch($type[0]) {
case "user":
return $this->hasOne('App\User', 'id', $type[1]);
case "group":
return $this->hasOne('App\UserGroup', 'id', $type[1]);
default:
return $this->hasOne('App\BluePoster', 'id', $type[1]);
}
}

Retrieve username from User table for each member in a Team

I'm facing some issues in establishing relationships amongst User, Clan, and Clan Member models. I have three models in which I have defined the relationship as...
Clan model
public function clanMembers() {
return $this->hasMany('App\ClanMember', 'clan_id', 'clan_id');
}
ClanMember model
public function clan() {
return $this->belongsTo('App\Clan', 'clan_id', 'clan_id');
}
I am trying to get Clan details of a requested user and his other Clan Members. I am using the following:
$clanMembers = ClanMember::find(Auth::user()->id)->clan()->with('clanMembers')->get();
From the above, I am getting the response correct.
"data": [
{
"id": 2,
"clan_leader_id": 3,
"clan_name": "#rockers1",
"clan_avatar": "",
"game_id": 1,
"max_members_count": 50,
"clan_id": "1505ccd15b01",
"created_at": "2019-05-04 04:31:44",
"updated_at": "2019-05-04 04:31:44",
"clan_leader_name": ""
"clan_members": [
{
"id": 2,
"user_id": 2,
"clan_id": "1505ccd15b01",
"role_id": 2,
"status": 0,
"created_at": "2019-05-04 04:33:03",
"updated_at": "2019-05-04 04:33:03"
}
]
}
]
Now I want to establish a relationship between the User and Clan model which has id and clan_leader_id as a foreign key to get clan_leader_name from User table in Clan model and user_name in place of user_id in clan_members. Clan member has user_id and id with the user as a foreign key.
Check out many-to-many relationships. Your models/tables should be: User, Clan, and ClanUser, where clan_user is an intermediate pivot table containing clan_id and user_id.
Your Clan model should contain the following relationship
public function users()
{
return $this->belongsToMany('App\User');
}
And your User model should contain the following relationship
public function clans()
{
return $this->belongsToMany('App\Clan');
}
To get a list of clans for a user:
$clans = User::find($userId)->clans()->get();
To get a list of users for a clan:
$users = Clan::find($id)->users()->get();

How to return a collection of models with selected columns and relation eager loaded in Laravel 5.2

Problem details:
I have three models
a Directorate with id and name fields,
an Employee with id and name fields and
a Telephone with id, tel, employee_id, directorate_id, description and type fields. The employee_id may be nullable, that is there are telephones stored in database with employee_id = null
The models are related as follows:
an employee may have many telephones
a directorate, may have many telephones
class Directorate extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
public function employees()
{
return $this->hasMany(Employee::class);
}
}
class Employee extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
public function directorate()
{
return $this->belongTo(Directorate::class);
}
}
class Telephone extends Model
{
public function employee()
{
return $this->belongsTo(Employee::class);
}
public function directorate()
{
return $this->belongsTo(Directorate::class);
}
}
Question:
I want to fetch a Collection of all the Telephone models that belong to a specific Directorate, that have employee_id = null and also having their directorate relation eager loaded. In addition, from that resulting collection of Telephone models, I need only some of the models' fields, that is id, tel and description
Tries
What I tried so far was the following:
I created a query scope in the Telephone model:
public function scopeHaveNoEmployeeId($query)
{
return $query->where('telephones.employee_id', '=', null);
}
In my controller
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get(['id', 'tel', 'description']);
However what I am receiving are the requested fields of the filtered models without the relation eager loaded, for instance:
[
{
"id": 79,
"tel": "0648136867",
"directorate": null
},
{
"id": 380,
"tel": "0223796011",
"directorate": null
}
]
I tried also to lazy eager load the relation afterwards but with no luck.
Finally I noticed that if I request all the Telephone models fields, the relation will eager load as I request. For example:
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get();
Then as a result:
[
{
"id": 79,
"tel": "0648136867",
"directorate": {
"id": 23
"name": "Some name"
}
},
{
"id": 380,
"tel": "0223796011",
"directorate": {
"id": 23
"name": "Some name"
}
}
]
Your telephone does not have any relationship with the directorate model.
put this in your Telephone model.
public function directorate()
{
return $this->belongsTo(Directorate::class);
}
Actually, after digging into Laravel's details for a while, I noticed that my initial question was out of context and somewhat silly. I was first eager loaded a relationship and then fatuously I filtered out the relationship by not including it in get() parameters. I had just to do the following:
$myTelephones = Telephone::with('directorate')
->haveNoEmployeeId()
->where('directorate_id', $directorateId)
->get(['id', 'tel', 'description', 'directorate']);

Resources