My laravel application is built on Laravel 8.12 and uses pest php for tests,
I have two tables groups and meetings and the their relation is one to many, where a meeting belongs to a group and group has many meetings,
I have a test case where I have to assert if a group can be created successful with it's meeting, the test case works well if I create only group without it's meeting but if I create the group with it's meeting using their relation the groups table contains two records of groups instead of one.
This is my test case when I create only group record
test('user see all meetings and registered groups successful', function () {
Group::factory()->create();
dd(Group::pluck('id'));
actingWithPermission($this->user, $this->permission)
->get($this->route)
->assertViewHasAll([
'totalRegistered' => 1,
'totalMeetings' => 1,
]);
});
When I create the group record the groups table contains only one record as shown below
^ Illuminate\Support\Collection^ {#3995
#items: array:1 [
0 => 36
]
#escapeWhenCastingToString: false
}
Test case when I create group and it's meeting record
test('user see all meetings and registered groups successful', function () {
Group::factory()
->hasMeetings(1)
->create();
dd(Group::pluck('id'));
actingWithPermission($this->user, $this->permission)
->get($this->route)
->assertViewHasAll([
'totalRegistered' => 1,
'totalMeetings' => 1,
]);
});
When I dump the database contains two records of the groups instead of 1 as shown below:
^ Illuminate\Support\Collection^ {#1847
#items: array:2 [
0 => 36
1 => 37
]
#escapeWhenCastingToString: false
}
This is one to many relation ship between group and meetings
public function meetings()
{
return $this->hasMany(Meeting::class);
}
How can I fix this?
Related
Posted the below not long ago and received some really good answers however whilst developing, my requirements became clearer. Instead of continually editing my previous post, thought I'd make a new one.
How do I return a single Model when working with nested relationships?
Assuming you've read my post above, I need to access the Team Model (not collection) for that particular Match for the logged in user.
FYI, I've named the Model "Team" and then referenced the hasMany function as "players" to the Match Model.
The Team model contains a few columns such as 'match_id', 'user_id', 'team', 'team_alias', 'goals', 'assists', 'paid'. I need to be able to access these.
Something like this in mind (I want to use this in my blade):
#foreach($matches as $match) {
#if($match->player->paid)
some html showing a payment complete
#else
some html showing payment required
#endif
}
#endforeach
Any ideas on how I can achieve this?
Edit: Adding more details for clarity
My Match Model looks like this:
Match {#645 ▼
#original: array:10 [▼
"id" => 5
"season_id" => 1
"venue_name" => "Wembley Stadium"
"venue_address" => "Wembley, Something Lane, London, Postcode"
"home_score" => null
"away_score" => null
"motm" => null
"match_date_time" => "2019-11-21 10:00:00"
"created_at" => "2019-11-13 23:14:44"
"updated_at" => "2019-11-13 23:14:44"
]
}
I use the venue_name, venue_address and match_date_time to display a table of all matches available for a user. A few more options I need to display on this table are whether a person has paid or not. This information is stored in the Team table (once a person submits their availability).
The Team model looks like this:
Team {#667 ▼
#original: array:10 [▼
"id" => 37
"match_id" => 5
"user_id" => 2
"paid" => 0
"team" => null
"team_alias" => null
"goals" => null
"assists" => null
"created_at" => "2019-11-14 20:48:02"
"updated_at" => "2019-11-14 20:48:02"
]
}
The Match Model is linked via id to the Team model which contains match_id.
How can I easily access the Team Model for the logged in user from this particular Match model?
For what i've understand, you already have a Match, and a logged user, and you want to find the Team where the logged user play and the Match given right?
Team::where([['user_id', auth()->user()->user_id /*or the User table primary key*/], ['match_id', $match->match_id /*or the Match table primary key*/]])
or, if you have already implement the realtionship from Match and Team inside the Model, you can do something like
$match->teams()->where('user_id', auth()->user()->user_id /*or the User table primary key*/)
My application allows a user to create scenarios by linking together soe_blocks. In turn, soe_blocks refer to a variable number of soe_entries.
To build scenarios, soe_blocks are linked to the scenario and ordered by an offset. The soe_blocks can be used in many different scenarios. soe_entries can relate only to a single soe_block
I think the relationship is defined as:
scenarios belongsToMany soe_blocks through scenarios_soe_blocks
soe_blocks belongsToMany scenarios through scenarios_soe_blocks
scenarios_soe_blocks is where the offset is kept
soe_entries haveOne soe_blocks
Tables:
scenarios: id | name
data: 0, 'scenario_1'
soe_blocks: id | name
data: 0, 'soe_block_1'
1, 'soe_block_2'
scenarios_soe_blocks: id | scenario_id | soe_block_id | offset
data: 1, 0, 1, 1
2, 0, 2, 2
Models:
class ScenariosTable extends Table
{
$this->belongsToMany('SoeBlocks', [
'foreignKey' => 'scenario_id',
'targetForeignKey' => 'soe_block_id',
'through' => 'ScenariosSoeBlocks',
'joinTable' => 'soe_blocks'
]);
}
class SoeBlocksTable extends Table
{
$this->belongsToMany('Scenarios', [
'foreignKey' => 'soe_block_id',
'targetForeignKey' => 'scenario_id',
'joinTable' => 'scenarios_soe_blocks',
'through' => 'ScenariosSoeBlocks'
]);
}
class ScenariosSoeBlocksTable extends Table
$this->belongsTo('SoeBlocks', [
'foreignKey' => 'soe_block_id',
'joinType' => 'INNER'
]);
}
Controllers:
public function view($id = null)
{
$scenario = $this->Scenarios->get($id, [
'contain' => ['SoeBlocks', 'RunStatus', 'ScenarioLog']
]);
$this->set('scenario', $scenario);
}
As far as I can make out from CakePHP Doc, this is all I need. But I couldn't get the ScenarioController->view() method to return the offsets from the scenarios_soe_blocks table associated with the soe_blocks.
I tried to add ScenariosSoeBlocks into the 'contain' clause in the ScenarioController, but got the error: Scenarios is not associated with ScenariosSoeBlocks. I found an SO article that suggested I add the following to the ScenarioTable:
$this->hasMany('ScenariosSoeBlocks', [
'foreignKey' => 'scenario_id'
]);
This seems to have worked, and now I can request ScenariosSoeBlocks in my controller like this:
$scenario = $this->Scenarios->get($id, [
'contain' => ['SoeBlocks', 'ScenariosSoeBlocks', 'RunStatus', 'ScenarioLog']
]);
Which at least gets the data into the view template, but not in the single object I'm hoping for. Eventually, I want to be able to CRUD the soe_blocks along with their associated soe_entries, in an object that looks like this:
offset | soe_block_id | soe_entry_id |
I have many other questions, like how to save etc., but I figured I need to get this working first.
So, my questions for now are:
are my associations correct?
how do I retrieve all the associations to view?
are my associations correct?
The first two are, but then it should be:
soe_blocks hasOne soe_entries
soe_entries belongsTo soe_blocks
how do I retrieve all the associations to view?
By containing them, just like you did in your first example. This question seems to originate from the question how to access the join table data, which is very simple, the join table data is being set on the target table entity (Scenario or SoeBlock, depending on from which side/table you issue the query), in a property named _joinData:
$joinTableEntity = $scenario->soe_blocks[0]->_joinData;
$offset = $joinTableEntity->offset;
You can easily gather information about the data structure by dumping your entity contents:
debug($scenario);
See also
Cookbook > Database Access & ORM > Associations - Linking Tables Together
Cookbook > Database Access & ORM > Saving Data > Saving Additional Data to the Join Table
I am working on application that is made up of Leads, each Lead -> hasMany -> Fields. My application will accept an infinite amount of whitelisted fields. Some Leads will have a lot of Fields, others will have maybe around 5. Due to this, I've opted for the Field table to be vertical, rather than fit in all the fields I accept horizontally, and then run into MySQL errors down the line (table too wide, etc.)
Here's my structure:
Lead Table
id
...
Field Table:
id
lead_id
field_name
field_value
I have created a model factory for my Lead model, that automatically creates 5 random fields using Faker.
I have an array of texts, numbers, dates (etc) fields which my application accepts.
Field Factory:
...
$texts = config('fields.whitelist.text');
foreach ($texts as $text) {
$fields[$text] = $faker->sentence();
}
...
$randomField = array_random(array_keys($fields));
return [
'field_name' => $randomField,
'field_value' => $fields[$randomField],
];
I've been doing this:
$lead = factory(Lead::class)->create()
->each(function ($l) {
$l->fields()->save(factory(Field::class, 5)->make());
});
However, I now have a minimum array of Fields which each Lead must have. I have these minimum fields in another config.
Is it possible to automatically create the x minimum Fields on the vertical table, using a factory?
E.g.
Minimum Fields
first_name
date_of_birth
How can I write a factory to automatically create the following structure:
[
'field_name' => 'first_name',
'field_value' => '<random name>',
],
[
'field_name' => 'date_of_birth',
'field_value' => '<random date>',
],
Edit: and if possible, not insert duplicate field_name values. Not like it's 100% deal breaker, but I want to make sure I 100% know what data I am working with, so checking x number of duplicates I imagine would be a nightmare
If you want each Lead to have those minimum fields, then add those fields to your each() closure. Like this:
$lead = factory(Lead::class)->create()->each(function ($lead) {
$lead->fields()->createMany([
'field_name' => 'first_name',
'field_value' => $faker->firstName,
],
[
'field_name' => 'date_of_birth',
'field_value' => $faker->dateTime(),
]);
$lead->fields()->save(factory(Field::class, 3)->make());
});
I changed the Field factory to 3 because there are 2 fields from the "minimum fields" that are inserted for every Lead.
im stuck while trying to count the entities and order the set of results after that.
I have connected the models to each other:
// ProductsTable
$this->belongsToMany('Users', [
'through' => 'ProductsUsers',
'join_table' => 'products_users'
]
);
// UsersTable
$this->hasMany('Products', [
'through' => 'ProductsUsers',
]
);
// ProductsUsersTable
$this->belongsTo('Users');
$this->belongsTo('Products');
By this Query, I get the Products including the users:
$products = $this->Products->find()
->contain([
'Users',
'ProductCategories'
]);
It looks like that:
Products
-> Product 1
-> User1, User2, User 3
-> Product 2
-> User1, User 3
Now I want to sort the result. But my tries fail.
I tried this:
$products = $this->Products->find()
->contain([
'Users',
'ProductCategories'
])
->select([
'users_count' => $this->Products->find()->func()->count('Users')
])->order(['users_count' => 'asc'])
->select($this->Products);
But no success. $Products->users_count includes all users.
I would be happy for any hint.
You should try Counter Cache:
It is possible though to make this work for belongsToMany associations. You need to enable the CounterCache behavior in a custom through table configured in association options. See how to configure a custom join table Using the ‘through’ Option.
This eliminates the need to compute on the fly and you have a field for user & product count in products & users table respectively.
Lets say that i have the following associations schema:
Person => [
hasMany => [
Courses => [Person.id = Courses.person_id]
],
Courses => [
belongTo => [
Schools => [School.id = Courses.school_id]
]
When I view a person through mydomain/person/view/1 I need to have a table to show the Courses of that Person. Inside this table each Course need to show the name of the School.
So I tried the following on my controller:
public function view($id = null)
{
$person = $this->Persons->get($id, [
'contain' => [
'Courses.Schools',
]
]);
$this->set('persons', $test);
$this->set('_serialize', ['person']);
}
What I get on view is:
Person => [
firstname => test,
lastname => test,
courses => [
0 => [
id => 1,
shool_id => 1,
person_id => 1,
]
]
]
There is no school in the array although I used it in the contain option. So I can't display the name of the school. Am I doing anything wrong? Is there any guideline how can I show these fields on the view.
Basically I am sorry for this. This is a caused because of the debugKit. The debugkit is showing through the variables panel the associations only until the level I have mentioned but I used a var_dump and saw that the associations and the related fields are fetched/loaded correctly. I trusted the debugKit and I thought that they where not loaded.