I need to create a single objective that has a multi-select drop down wherein different departments can share that same objective. I wanted to create a relationship table with an output like this.
Department Table
id | name
1 Science Department
2 Math Department
3 Biology Department
Objective Table
id | name
1 Be the best
Relationship Table
objective_id | department_id
1 1
1 2
1 3
This is what I think of inside the controller.
public function store(Request $request) {
$objective = Objective::updateOrCreate(
[ 'id' => $request->id ?? null ],
[ 'name' => $request->name ]
);
// From multiple select drop down
foreach($request->departments as $department) {
RelationshipTable::updateOrCreate(
[ // what should be the case? ],
[
'objective_id' => $objective->id,
'department_id' => $department['id'],
]
);
}
}
I'm not sure on how I would define this in the Model and how I could call their relationship inside the resource. I even think that my controller is wrong or are there better ways to achieve this?
First You are running query under loop is a very bad process.. may this process will help u? change it as your need!
public function store(Request $request) {
$objective = Objective::updateOrCreate(
[
'id' => $request->id ?? null,
'name' => $request->name
]
);
// From multiple select drop down
$insert_array = [];
foreach($request->departments as $department) {
array_push($insert_array,[
'objective_id' => $objective->id,
'department_id' => $department['id'],
]);
}
RelationshipTable::updateOrCreate($insert_array);
}
//Relationship Should Be Like in this example
Relationship Model
public function object() {
return $this->hasOne('Model Class of Object' , 'objective_id ' , 'id')
}
public function depertment() {
return $this->hasMany('Model Class of depertment' , 'department_id' , 'id')
}
Related
The solution to this is probably easy and I'm just missing it, but I can't seem to figure out how to limit "customers" based on the "user" that the customer belongs to.
This is a many to many relationship, so a customer can belong to more than one user and a user can have more than one customer.
Here is my relationship definition:
public $belongsToMany = [
'user_id' => [
'RainLab\User\Models\User',
'table' => 'tablename_users_customers',
]
];
And here is the scope function that doesn't work as I'd expect:
public function scopeUser($query) {
$user = Auth::getUser()->id;
return $query->where('user_id', $user)->get();
}
Finally, here is my error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'user_id' in 'where clause' (SQL: select * from `tblcustomers` where `user_id` = 1)
Obviously, the error is because the "user_id" column doesn't exist in the 'tblcustomers' table, but rather in the pivot table. How can I use the "user_id" from the pivot table in my scope function? I need to only display Customers that belong to the currently logged in user.
Yes this can be possible
But First thing is you need to remove get() method from the scope, scope meant to return query object for chaining methods further.
Your relation and scope should look like this
// relation
public $belongsToMany = [
// PLEASE MAKE RELATION NAME CORRECT HERE
'users' => [ // not user_id, use `users`
'RainLab\User\Models\User',
'table' => 'tablename_users_customers',
// 'key' => 'customer_id', if needed
// 'otherKey' => 'user_id' if needed
]
];
// scope
use RainLab\User\Models\User;
public function scopeUser($query) {
return $query->whereHas('users', function($usersQuery) {
$user_id = Auth::getUser()->id;
return $usersQuery->where((new User)->getTable() . '.id', $user_id);
});
}
// usage
$result = Customer::user()->get();
dd($result);
// you will get only customers which has relation with current logged in user.
if any doubts please comment.
Let' say I want to create a phone book for a big company with multiples offices.
My database is composed by 3 tables:
- User
- City (represent the office's location)
- Role (a User can have multiple roles)
I'd like to create a form with 3 different fields to perform the query, how can I do that ?
For now I've got only 1 field and this is my main model User
```
public function city()
{
return $this->belongsTo(City::class);
}
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function toSearchableArray()
{
$office = $this->city;
$array = [
'name' => $this->name,
'phone' => $this->phone,
'postal_code' => $office->postal_code,
'city' => $office->name,
];
return $array;
}
```
How would you do a research with Algolia and add filters to the query (City and Roles) ?
How should I store Roles ? Plain text ?
Thank you
Scout only support numbers in the where clause: https://laravel.com/docs/5.5/scout#where-clauses
I would recommend to also store the ID for offices and do search like:
User::search('')->where('office_id', 2)->get();
For Roles, because you have multiple values, you should store them as in an array.
You cannot use where on arrays, so you will need to leverage the callback parameter to add a filter entry in the options array.
User::search('', function ($algolia, $query, $options) {
$options = array_merge($options, [
'filters' => 'roles:engineer'
]);
return $algolia->search($query, $options);
})->get();
You can find Algolia's doc about filtering here: https://www.algolia.com/doc/guides/searching/filtering/
Please let me know if that worked for you.
I have next structure pivot table: products_equipment_value(id, product_id, equipment_id, value_id).How to update table fields equipment_id and value_id ?
public function equipments()
{
return $this->belongsToMany('App\Equipment', ' product_equipment_value');
}
public function values()
{
return $this->belongsToMany('App\Value', ' product_equipment_value', 'product_id', 'value_id')
}
Use(not work)
$product->equipments()->detach();
foreach($data['eq'] as $key => $val){
$product->equipments()->attach([
'equipment_id' => $key,
'value_id' =>$val
]);
}
You should use withPivot function on the relations.
public function equipments()
{
return $this->belongsToMany('App\Equipment', 'product_equipment_value')->withPivot('value_id');
}
Then when you attach the models
$product->equipments()
->attach([
$key => ['value_id' =>$val],
]);
Referring to Many to Many Eloquent's relationship (https://laravel.com/docs/5.5/eloquent-relationships#many-to-many) you can sync IDs like this :
$product->equipments()->sync([array_of_equipments_ids]);
Example:
$product->equipments()->sync([3, 8, 9, 24]);
If you defined the Many to Many inverse relationship you can also do this that way from the equipment instance :
$equipment->values()->sync([array_of_values_ids]);
If you have extra columns with your pivot table you can add them like this :
public function equipments(){
return $this->belongsToMany('App\Equipment', 'product_equipment_value')->withPivot('extra_column1');
}
And so :
$product->equipments()->sync([1 => ['extra_column1' => 'Value for this column']])
Note that you can use sync() or attach() methods to construct many-to-many associations. You can take a look here : https://stackoverflow.com/a/23969879/8620746
User Table: has zone_id field in it.
Zones table: has world_id field in it.
Each user can be in one zone, for example, zone_id = 1
Each zone belongs to one world, for example - world_id = 5
My desired output is returning user zone and world info.
This is how I can make it without any relationship set:
$zone = Zone::find($user->zone_Id);
$world = World::find($zone->world_id);
$data = $user;
$data['zone'] = $zone;
$data['zone']['world'] = $world;
My question is.. I'm sure relationship can be used for a cleaner code, but I'm not sure how to set it up.
Should I stick with the current code or define a relationship?
If the answer for 1 is define a relationship, Any help of what's the right relationship between these 3 models?
Solution 1:
`public function getZone(Request $request)
{
$token = $request->input('token');
$user = JWTAuth::toUser($token);
// Simplest example using relationships
$userWithZone = User::with('zone.world')->find($user->id); // You'll get the `zone` relationship info here, too
return $userWithZone;
}`
Error: returns "Call to a member function getQuery() on null"
Here's an example of how you can achieve this with Eloquent Relationships.
// User.php
class User
{
public function zone()
{
return $this->belongsTo(Zone::class);
}
}
// Zone.php
class Zone
{
public function world()
{
return $this->belongsTo(World::class);
}
}
Simplest example using relationships
$user = User::with('zone.world')->find($id); // You'll get the `zone` relationship info here, too
You can get more complex with your relationships if you want
$user = User::with(['zone' => function($query) {
$query->with('world')
->select('id', 'zone_name', 'world_id');
})->select('username', 'zone_id')->find($id);
or even...
$user = User::with(['zone' => function($query) {
$query->with(['world' => function($query2) {
$query2->select('id', 'world_name');
}])->select('id', 'zone_name', 'world_id');
})->select('username', 'zone_id')->find($id);
Your resulting $user will look something like:
'user' => [ // This is a Collection
'username',
'email',
'zone_id',
'zone' => [ // This is a Collection
'id',
'zone_name',
'world_id',
'world' => [ // This is a Collection
'id',
'world_name'
]
]
];
I have three database table:
product (id, name)
product_has_adv (product,advantage,sort,important)
advantage (id, text)
In ProductModel I defined this:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id']);
}
I get the advantages without any problems.
But now I need to add a where product_has_advantage.important = 1 clausel and also sort the advantages by the sort-columen in the product_has_advantage-table.
How and where I have to realize it?
Using via and viaTable methods with relations will cause two separate queries.
You can specify callable in third parameter like this:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'], function ($query) {
/* #var $query \yii\db\ActiveQuery */
$query->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
});
}
The filter by important will be applied, but the sort won't since it happens in first query. As a result the order of ids in IN statement will be changed.
Depending on your database logic maybe it's better to move important and sort columns to advantage table.
Then just add condition and sort to the existing method chain:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
}
Using viaTable methods with relations will cause two separate queries, but if you don't need link() method you can use innerJoin in the following way to sort by product_has_advantage table:
public function getAdvantages()
{
$query = AdvantageModel::find();
$query->multiple = true;
$query->innerJoin('product_has_advantage','product_has_advantage.advantage = advantage.id');
$query->andWhere(['product_has_advantage.product' => $this->id, 'product_has_advantage.important' => 1]);
$query->orderBy(['product_has_advantage.sort' => SORT_DESC]);
return $query;
}
Note than $query->multiple = true allows you to use this method as Yii2 hasMany relation.
Just for reference https://github.com/yiisoft/yii2/issues/10174
It's near impossible to ORDER BY viaTable() columns.
For Yii 2.0.7 it returns set of ID's from viaTable() query,
and final/top query IN() clause ignores the order.
For who comes here after a while and don't like above solutions, I got it working by joining back to the via table after the filter via table.
Example for above code:
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->innerJoin('product_has_advantage','XXX')
->orderBy('product_has_advantage.YYY'=> SORT_ASC);
}
Take care about changing XXX with the right join path and YYY with the right sort column.
First you need to create a model named ProductHasAdv for junction table (product_has_adv) using CRUD.
Then create relation in product model and sort it:
public function getAdvRels()
{
return $this->hasMany(ProductHasAdv::className(), ['product' => 'id'])->
orderBy(['sort' => SORT_ASC]);;
}
Then create second relationship like this:
public function getAdvantages()
{
$adv_ids = [];
foreach ($this->advRels as $adv_rel)
$adv_ids[] = $adv_rel->advantage;
return $this->hasMany(Advantage::className(), ['id' => 'advantage'])->viaTable('product_has_adv', ['product' => 'id'])->orderBy([new Expression('FIELD (id, ' . implode(',', $adv_ids) . ')')]);
}
This will sort final result using order by FIELD technique.
Don't forget to add:
use yii\db\Expression;
line to head.
I`ve managed this some how... but it needs additional work after.
The point is that you have to query many-to-many relation first from source model and after that inside that closure you should query your target model.
$query = Product::find();
$query->joinWith([
'product_has_adv' => function ($query)
{
$query->alias('pha');
$query->orderBy('pha.sort ASC');
$query->joinWith(['advantage ' => function ($query){
$query->select([
'a.id',
'a.text',
]);
$query->alias('a');
}]);
},
]);
Then you just have to prettify the sorted result to your needs.
The result for each row would look like
"product_has_adv": [
{
"product": "875",
"advantage": "true",
"sort": "0",
"important": "1",
"advantage ": {
"id": "875",
"text": "Some text..",
}
},
As explained by #arogachev, the viaTable uses two separate queries, which renders any intermediate orderBy obsolete
You could replace the viaTable with an innerJoin as follows, in a similar solution to #MartinM
public function getAdvantages()
{
return $this->hasMany(AdvantageModel::class, ['pha.product' => 'id'])
->innerJoin('product_has_advantage pha', 'pha.advantage = advantage.id')
->andWhere(['pha.important' => 1])
->orderBy(['pha.sort' => SORT_ASC]);
}
By adjusting the result of hasMany, you are adjusting the query for the target class - AdvantageModel::find(); product_has_advantage can be joined via the advantage identity
The second parameter of hasMany, link, can be viewed as [ query.column => $this->attribute ], which you can now support via the joined product_has_advantage and its product identity
Note, when using viaTable, the link parameter can be viewed as if the intermediate query is complete and we are starting from there; [ query.column => viaTable.column ]
hence ['id', 'advantage'] in your question
public function getAdvantages()
{
return $this
->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
->viaTable('product_has_advantage', ['product' => 'id'])
->andWhere(['important' => 1])
->orderBy(['sort' => SORT_DESC]);
}