Laravel 5.3 belongsTo relation on same table shows no result - eloquent

I'd like to make a relationship on the same table but I'm not sure it's possible...
I have a table jobs with the fields id, name and parent_id. I want to get some jobs and their parent (and their own parent if they have one, etc.).
Here's my model :
class Job extends Model
{
public function jobParent()
{
return $this->belongsTo('App\Job', 'parent_id');
}
}
In my controller I do that :
$select = Job::take(10)
->with('jobParent')
->get();
I displayed the queries and I can see the one for the relationship :
select * from jobs where id in (?, ?, ?)
I have 3 parents for my 10 jobs so it seems right but it returns parent: null for each of my jobs...
Do you see what I'm missing ?
I added this to my request because I want also the parent of the parent :
->with(['jobParent' => function($query) {
$query->with('jobParent');
}]);
I can see the two queries so it does works, it just doesn't display !
select * from jobs where id in (?, ?, ?)
select * from jobs where id in (?, ?)
I output my results this way :
$return = [
'success' => 1,
'totalCount' => $totalCount,
'nextOffset' => $offset+$nb,
'jobs' => $jobs
];
$code = 200;
return response()->json($return, $code);
Wich gives me this :
{
"success": 1,
"totalCount": 10706,
"nextOffset": 10,
"jobs": [
{
"name": "xxx",
"parent_id": 15,
"job_parent": null
}, ...
I tried to do a foreach of my jobs and a var_dump($job->jobParent) but it says NULL as well...
I just don't get why it doesn't store the results...

I figured it out : job.id is a string, I casted it in my model and it works fine :
protected $casts = ['id' => 'string'];

Related

Doctrine Entity values are all null except for id

I'm trying to fetch an Object from the database with the repository method findOneBy (id).
Basically, the line looks like this:
public function findAssignedTickets(User $user)
{
$userId = $user->getId();
$ticketMapping = new ResultSetMapping;
$ticketMapping->addEntityResult(Ticket::class, 't');
$ticketMapping->addFieldResult('t', 'id', 'id');
// Postgresql Native query, select all tickets where participants array includes the userId
$query = "SELECT *
FROM (
SELECT id, array_agg(e::text::int) arr
FROM ticket, json_array_elements(participants) e
GROUP BY 1
) s
WHERE
$userId = ANY(arr);
";
$results = $this->getEntityManager()->createNativeQuery($query, $ticketMapping)->getResult();
$results = array_map(function($item) {
return $item->getId();
}, $results); // Transform to array in integers
dump($results); // array:2 [0 => 83, 1 => 84] -> It's correct
$tickets = [];
foreach ($results as $ticketId) {
dump($this->findOneById($ticketId));
// $ticket = $this->findOneById($ticketId);
// $tickets[] = [
// 'identifier' => $ticket->getIdentifier(),
// 'title' => $ticket->getTitle(),
// 'author' => $ticket->getAuthor()->getUsername(),
// 'status' => $ticket->getStatus(),
// 'created' => $ticket->getCreatedAt()->format('c'),
// 'updated' => $ticket->getUpdatedAt()->format('c'),
// ]; // Ticket formatting to send in json
}
return $tickets;
}
which will output :
And I'm sure that the received id matches a row in the database, and that the database contains data, and all fields belong directly to the entity, except for author which represents a ManyToOne and I heard about the lazy displaying of Doctrine, but it shouldn't happen on other fields.
Why can't I retrieve data from the Object even with getters, and why are all the values set to null Except for id ?
EDIT : I was wondering if that had a connexion to the ResultSetMapping I used to fetch the tickets IDs in a totally separate request earlier, and when I added a addFieldResult('t', 'title', 'title'); it did the work, but not on the other fields, another mystery.

Cascade updates on tables and pivot tables with laravel and Eloquent

I created a model nammed sector. For this model i created methods to make links between the database table :
A sector has many valuechain with many segments, which have many
keyneeds
A valuechain has many segments
A segment has many keyneeds ...
It has been translated this way :
public function valuechains()
{
return $this->hasMany('App\Valuechain');
}
public function segments()
{
return $this->hasManyThrough('App\Segment', 'App\Valuechain');
}
public function keyneeds()
{
return $this->hasManyThrough('App\Keyneed', 'App\Segment', 'App\Valuechain');
}
When i soft delete a sector I want to create a cascade on other tables and make updates.
My destroy method is :
public function destroy($id)
{
$sector = Sector::findOrFail($id);
// on update lang_sector pour chaque id
$sector_ids = $sector->langs()->allRelatedIds();
foreach ($sector_ids as $id){
$sector->langs()->updateExistingPivot($id, ['lang_sector.deleted_at' => Carbon::now()]);
}
$sector->valuechains()->update(
[
'valuechains.deleted_at' => Carbon::now(),
'valuechains.updated_at' => Carbon::now(),
]
);
$sector->segments()->update(
[
'segments.deleted_at' => Carbon::now(),
'segments.updated_at' => Carbon::now(),
]
);
$sector->keyneeds()->update(
[
'keyneeds.deleted_at' => Carbon::now(),
'keyneeds.updated_at' => Carbon::now()
]
);
Sector::where('id', $id)->delete();
return redirect()->route('sectors.index')->with('success', 'Sector deleted');
}
I have pivot table for valuechains, segments and keyneeds and i also want to update the updated_at and deleted_at columns on those tables...
For those 3 tables i added to the model the lang method for defining the pivot relation and defining the fields i have inside the pivot table:
public function langs() {
return $this->belongsToMany('App\Lang')
->withPivot(
'vcname',
'vcshortname',
'vcdescription',
'vcshortdescription',
'created_at',
'updated_at',
'deleted_at'
);
}
I have an error message :
SQLSTATE[23000]: Integrity constraint violation: 1052 Champ: 'updated_at' dans field list est ambigu (SQL: update segments inner join valuechains on valuechains.id = segments.valuechain_id set valuechains.deleted_at = 2018-05-09 07:34:30, valuechains.updated_at = 2018-05-09 07:34:30, segments.deleted_at = 2018-05-09 07:34:30, segments.updated_at = 2018-05-09 07:34:30, **updated_at** = 2018-05-09 07:34:30 where valuechains.sector_id = 2)

Why `select` does not replace existing columns?

DOC for select attribute:
select indicates which columns should be selected from the storage
This works as expected:
$rs->search( undef, { select => [ 'me.id', 'me.user_id' ] } )
->search( undef, { select => [ 'me.role_id' ] } )
$rs->as_query; # SELECT "me"."role_id" FROM "users_roles" "me"
But this does not:
$rs->search( undef, { prefetch => [ 'User' ] } )
->search( undef, { select => [ 'User.name' ] } )
$rs->as_query; # SELECT "User"."name", "User"."id", "User"."email", "User"."name" FROM "users_roles" "me" JOIN "users" "User" ON "User"."id" = "me"."user_id"
The prefetch implies +columns ( which is: +select and +as ).
I alter select in second search by requesting only one column: name
What did I miss?
Why I still get columns name, id, email, name instead of only one name?
If I rewrite prefetch by join. This works fine:
$rs->search( undef, {
,join => [ 'User' ]
,collaplse => 1
,'+columns' => [map
{ +{ "cds.$_" => "cds.$_" } }
$c->db->source('Right')->related_source('User')->columns
]
})->search( undef, { select => [ 'User.name' ] } )
$rs->as_query; # SELECT "User"."name" FROM "users_roles" "me" JOIN "users" "User" ON "User"."id" = "me"."user_id"
So it seems there is a bug with prefetch. Because join+collapse++columns is not same as prefetch:
DOC
This attribute is a shorthand for specifying a "join" spec, adding all columns from the joined related sources as "+columns" and setting "collapse" to a true value.
Also docs states:
For example, the following two queries are equivalent
As you can see join and prefetch are not equivalent

How can I expose many-to-many tag-style relationship in a Catalyst app?

I'm building a database application in Catalyst, using jqGrid to do the messy work of handling the display of data. I've got almost everything working, except for being able to filter search results by "tags". I have three tables, with relationships like this:
package MyApp::Schema::Result::Project;
...
__PACKAGE__->has_many(
"job_flags",
"MyApp::Schema::Result::ProjectFlag",
{ "foreign.project_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
...
__PACKAGE__->many_to_many(flags => 'project_flags', 'flag');
1;
and
package MyApp::Schema::Result::Flag;
...
__PACKAGE__->has_many(
"project_flags",
"MyApp::Schema::Result::ProjectFlag",
{ "foreign.flag_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
...
__PACKAGE__->many_to_many(projects => 'project_flags', 'project');
1;
and finally, the join table
package MyApp::Schema::Result::ProjectFlag;
...
__PACKAGE__->belongs_to(
"flag",
"MyApp::Schema::Result::Flag",
{ id => "flag_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
...
__PACKAGE__->belongs_to(
"project",
"MyApp::Schema::Result::Project",
{ id => "project_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
...
1;
In my controller that provides the JSON data to jqGrid, I use Catalyst::TraitFor::Controller::jQuery::jqGrid::Search to translate the request parameters generated by jqGrid into DBIx::Class-style queries:
my $search_filter = $self->jqGrid_search($c->req->params);
my $project_rs = $c->model('DB::Project')->search(
$search_filter, {
join => 'project_flags',
group_by => 'id',
},
);
which is then passed on to the jqGrid page generator:
$project_rs = $self->jqgrid_page($c, $project_rs);
and then I iterate over the result set and build my jqGrid columns.
On the HTML side, I am able to build a JSON string like
{"groupOp":"AND","rules":[{"field":"project_flags.flag_id","op":"eq","data":"2"}]}
and, in this case, show Projects having a row in project_flags with flag id of 2.
I absolutely know I'm not doing this correctly! All of the documentation I can find on Catalyst and DBIx::Class demonstrates similar ideas, but I just can't understand how to apply them to this situation (not that I haven't tried).
How would I go about building "has_flag($flag_id)"-type accessors, and then be able to use them from within jqGrid's API? Where in my Catalyst app would this belong?
One of the ways I'd like to filter is by the lack of a particular flag also.
I've got to be honest with you, I'm not entirely sure I understand your question. It seems to be what you're asking has more to do with DBIx::Class than Catalyst--the latter I know very little about, the former I am learning more about every day. With that in mind, here's my best attempt at answering your question. I am using Mojolicious as the MVC, since that's what I know best.
First, I start by creating a many-to-many database:
CREATE TABLE project(
id INTEGER PRIMARY KEY,
name text
);
CREATE TABLE flag(
id INTEGER PRIMARY KEY,
name text
);
CREATE TABLE project_flag(
project_id integer not null,
flag_id integer not null,
FOREIGN KEY(project_id) REFERENCES project(id),
FOREIGN KEY(flag_id) REFERENCES flag(id)
);
INSERT INTO project (id,name) VALUES (1,'project1');
INSERT INTO project (id,name) VALUES (2,'project2');
INSERT INTO project (id,name) VALUES (3,'project3');
INSERT INTO flag (id,name) VALUES (1,'flag1');
INSERT INTO flag (id,name) VALUES (2,'flag2');
INSERT INTO flag (id,name) VALUES (3,'flag3');
INSERT INTO flag (id,name) VALUES (4,'flag4');
INSERT INTO project_flag (project_id,flag_id) VALUES (1,1);
INSERT INTO project_flag (project_id,flag_id) VALUES (1,2);
INSERT INTO project_flag (project_id,flag_id) VALUES (1,3);
INSERT INTO project_flag (project_id,flag_id) VALUES (1,4);
INSERT INTO project_flag (project_id,flag_id) VALUES (2,1);
INSERT INTO project_flag (project_id,flag_id) VALUES (2,4);
And here is my Perl (Mojolicious) code:
#!/usr/bin/env perl
use Mojolicious::Lite;
use Schema;
helper db => sub {
return Schema->connect('dbi:SQLite:test.db');
};
get '/' => sub {
my $self = shift;
my $rs = $self->db->resultset('Project')->search(
{ 'me.name' => 'project3' },
{
join => { 'project_flags' => 'flag' },
select => ['me.name', 'flag.name'],
as => ['project', 'flag']
}
);
$rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
$self->render( json => [ $rs->all ] );
};
app->start;
And here's the JSON output (pretty print) from project1 (has flags related to it):
[
{
"project":"project1",
"flag":"flag1"
},
{
"flag":"flag2",
"project":"project1"
},
{
"project":"project1",
"flag":"flag3"
},
{
"flag":"flag4",
"project":"project1"
}
]
And here is JSON for project3, with no relationship to any flags:
[
{
"project":"project3",
"flag":null
}
]
I put the files on Github, so you can check them out if you'd like.
In your given situation, say they've typed the word 'c++' into the filter, and you want to return everything that has been tagged 'c++', then:
my $rs = $self->db->resultset('Tag')->search(
{ 'me.name' => 'c++' },
{
join => { 'project_flags' => 'project' },
select => ['me.name', 'project.name'],
as => ['tag', 'project']
}
);
$rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
$self->render( json => [ $rs->all ] );
If you wanted to return all columns, use prefetch rather than join. Also, if you'd like to support the Autosearch functionality, change search to search_like, like so:
my $rs = $self->db->resultset('Tag')->search_like(
{ 'me.name' => $search_filter.'%' },
I hope that if I did not answer your question what I have given is at least a push in the right direction.

How do to specify a column of a distant join (several relations removed) in DBIx::Class?

How do I specify in DBIx::Class the column of the third table I'm joining on? Here is my code:
my $join = $schema->resultset('Track')->search({'name' => 'Eminem'},{'join' => {'cd' => 'artist'}});
It just displays the tracks of Eminem, but I also want to display the artist name, Eminem? I cannot access name in that query because this is a ResultSet for Track, name is a column in the Artist table, the third table in the join.
I guess you want to filter your resultset based on the artist name, not track name:
my $rs = $schema->resultset('Track')->search({
'artist.name' => 'Eminem',
},
{
join => { cd => 'artist' },
});
When looping through the resultset you can access it using the relationship and column accessors:
for my $track ($rs->all) {
say $track->cd->artist->name . ' - ' . $track->name;
}
Use the relationship accessors.
for my $track (
$schema->resultset('Track')->search(
{
name => 'Eminem',
},
{
join => {cd => 'artist'},
}
)->all
) {
use Data::Printer;
p { $track->get_inflated_columns };
p $track->cd->artist->name;
}
__END__
{
cd MyDatabase::Main::Result::Cd {
internals: {
_column_data {
artist 2,
cdid 3,
title "The Marshall Mathers LP"
},
}
},
title "The Way I Am",
trackid 1
}
"Eminem"
{
cd MyDatabase::Main::Result::Cd {
internals: {
_column_data {
artist 2,
cdid 3,
title "The Marshall Mathers LP"
},
}
},
title "Stan",
trackid 2
}
"Eminem"