Laravel: Relationships User hasOne Group, Group hasMany User - laravel

I want to create a relationship between two tables "users" and "groups", in this relationship, a user belongs to only one group, and the group has multiple users, to solve this, how should i design the table? Need a table "group_user"?

You should use just two tables which names are "users" and "groups". Because one user have only one group. So there is no required to any pivot table.
Tables columns should be like this:
Users: id, name, group_id
Groups: id, name
In the User entity you should do relation like this:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function group()
{
return $this->belongsTo( Group::class, 'group_id', 'id' );
}
And in the Group entity:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function users()
{
return $this->hasMany( User::class, 'group_id', 'id' );
}

Related

Laravel : Define model for reverse OneToMany relationship

I have the following schema:
class Group extends Model
{
/**
* The users that belong to the group.
*/
public function users()
{
return $this->belongsToMany(User::class)->withTimestamps();
}
}
What should my User model look like in the other side ?
class User extends Model
{
/**
* The group that owns the user.
*/
public function group()
{
return $this->???(Group::class)->withTimestamps();
}
}
It's also a belongsToMany, just the inverse. Try this paradigm:
return $this->belongsToMany(
'App\Models\Group',
'user_group',
'user_id',
'group_id'
)->withTimestamps();
Where the explicit params are:
Related Model
Table (Pivot table)
Foreign Pivot Key
Related Pivot Key

Polymorphic relationship with pivot data

I would like to solve the following issue:
I got multiple models like:
Product
Customer
Each model should be able to have one or more Fields with pivot data.
Field:
id
title
type
required
Example:
Product has a field called video_url, type should be string containing the pivot value http://youtube.com/....
Customer has a field called external_id, type should be integer containing the pivot value 242.
The fields should be added dynamically by the user. The user should be able to decide, whether the field is morphing to Product or Customer (or even more later).
Maybe this helps to understand:
What I am doing right now
At the moment I created a new Model for each, product and customer
For customers:
class CustomerField extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function customers()
{
return $this->belongsToMany(Customer::class)->withPivot('value');
}
}
For products:
class ProductField extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function products()
{
return $this->belongsToMany(Product::class)->withPivot('value');
}
}
At the moment this works out, but of course, it's not the most elegant way to solve it.
My question
Is there a possibility to morph a field dynamically to Product or Customer with an additional pivot?
I think this is what you want Polymorphic:Many-to-Many
You don't need to add ProductField and CustomerField models,
you just need to add Product, Customer and Field model.
The fields will dynamically belongs to product or customer by fieldable_type. Even you have more models, it will store the model name to this fieldable_type.
And the tables you need to be created like this below:
fieldables table has fieldable_id and fieldable_type;
fieldable_type will set your model name automatically, like App\Product, and you can custom that by yourself in AppServiceProvider:
Relation::morphMap([
'products' => 'App\Product',
'customers' => 'App\Customer',
]);
In Product Model:
class Product extends Model
{
/**
* Get all of the fields for the product.
*/
public function fields()
{
return $this->morphToMany('App\Field', 'fieldable')->withPivot('value');
}
}
In Customer Model:
class Customer extends Model
{
/**
* Get all of the fields for the customer.
*/
public function fields()
{
return $this->morphToMany('App\Field', 'fieldable')->withPivot('value');
}
}
In Field Model:
class Field extends Model
{
/**
* Get all of the products that are assigned this field.
*/
public function products()
{
return $this->morphedByMany('App\Product', 'fieldable');
}
/**
* Get all of the customers that are assigned this field.
*/
public function customers()
{
return $this->morphedByMany('App\Customer', 'fieldable');
}
}
CRUD with Pivot Value:
After that, you can easily create, get, update, delete pivot value like:
Field::first()->products; # return the products with pivot value
Field::first()->customers; # return the customers with pivot value
Customer::first()->fields;
$field = Field::first();
# create new relationship with pivot value between customer and fields:
Customer::first()->fields()->attach($field, ['value' => 'customer new value field']);
# update pivot with value:
Customer::first()->fields()->sync([$field->id => ['value' => 'update customer value field']]);
# Delete pivot
Customer::first()->fields()->detach($field->id);
The best practice is to use a separate table to hold meta information so that you can easily add/remove "columns" as needed
For example, you could set your meta table up like this:
create table `ProductField` (
products_id int(11),
column_name varchar(255),
value varchar(255),
)
Then in your products model, add functionality to get, insert, check if exists, etc.
public function getMeta($column) {
$meta = DB::table('ProductField ')
->select('column_name', 'value')
->where('products_id', '=', $this->id)
->where('column_name', '=', $column)
->get();
if (!$meta->isEmpty()) {
return $meta;
} else {
return null;
}
}
public function addMeta($column, $value) {
DB::table('ProductField ')->insert(
[
'products_id' => $this->id,
'column_name' => $column,
'value' => $value,
]
);
}
The same way you can achieve dynamic nature for Customers too.
You can also use an array to store the feilds and then dynamically add them to the model
foreach ($request->input('cost') as $key=>$cost) {
Price::create([
'product_id' => $request->product_id[$key],
'date' => Carbon::now(),
'cost' => $cost,
'trend' => 0
]);
}
If you know that there will only be certain dynamic fields ahead of time, you could opt to create accessor methods for them

Clarification with eager loading/mutators/accessors for laravel

I'm making a laravel app for work and I need to load all users with their attached role without any nesting of the roles. I used this tutorial for roles: https://medium.com/#ezp127/laravel-5-4-native-user-authentication-role-authorization-3dbae4049c8a . If I use public $with = ['roles']; on my User model it returns the entire role object within the user object and I need it to just return role:'role_name';
/**
* set up eloquent between roles/user
*
* #return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function roles()
{
return $this->belongsToMany(Role::class);
}
Above is in my User model and below is in my Role model to define the relationships.
/**
* provides a many-to-many relationship to User model
*
* #return User::class
*/
public function users()
{
return $this->belongsToMany(User::class);
}
I thought that by adding this to the User model:
protected $appends = ['role_name'];
public function getRoleNameAttribute()
{
return $this->attribute['name'];
}
it would return everything but all it does is create a role_name: 'user_name'; On the model. So I guess I realize I'm accessing just the Users table and not the Roles table in the DB, but again not really sure what to do. Any help would be greatly appreciated.
If for the purpose of convenience you need to access the role name directly from the model you should refer to the actual relationship data:
protected $appends = ['role_name'];
public function getRoleNameAttribute()
{
return $this->roles->pluck('name');
}
This should append the array of role names to your user model. It will be an array because roles seem to have a many-to-many relationship with the User model.
The issue is you're returning $this->attributes['name'] in the getRoleNameAttribute. You want the role names, so instead you'd do something like this:
If you want an array with the names:
return $this->roles()->pluck('name')
If you want the names as a string:
return array_implode(", ", $this->roles()->pluck('name')->toArray());

LARAVEL: One-to-one relationship on a pivot table?

I can't think of how to handle this in Eloquent. I have a many-to-many relationship that needs a one-to-one relationship assigned to it.
Here is the basic database structure I designed in its simplest form:
ACCOUNTS: id
AGENTS: id
FEES: id
ACCOUNT_AGENT: account_id, agent_id, fee_id
Each Account belongsToMany Agents.
Each Agent belongsToMany Accounts.
Each "Account_Agent" (the many-to-many pivot table) belongsTo Fee.
How do I define that third relationship in an Eloquent model?
I want to be able to access the "account_agent" fee like this (or similar):
$account = Account::first();
foreach ($account->agents as $agent) {
echo $agent->fee;
}
Thanks, hopefully my question is clear.
See here:
https://laravel.com/docs/5.4/eloquent-relationships#many-to-many
Scroll down to Defining Custom Intermediate Table Models
Basically what you have to do is to define a class like this:
class AccountAgent extends Illuminate\Database\Eloquent\Relations\Pivot
{
/**
* Many:1 relationship with Fee model
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function fee()
{
return $this->belongsTo(Fee::class);
}
}
Then you define your many:many relationship in your Account class like this:
/**
* The agents that belong to the account.
*/
public function agents()
{
return $this->belongsToMany(Agent::class)->using(AccountAgent::class);
}
Then in your code, instead of this:
foreach ($account->agents as $agent) {
echo $agent->fee;
}
... do this:
foreach ($account->agents as $agent) {
/** #var Fee $fee_object */
$fee_object = $agent->pivot->fee;
}
In that loop $fee_object is of class Fee (the model class that covers the fees table) so you could echo $fee_object->fee_amount or whatever other columns you need to use.

Eloquent Many to Many select User without certain Roles

So I have User & Role models with many-to-many relationship, I have 3 roles: super, admin and moderator with 4 users let's says: John, Mike, James and Larry.
John is a super, Mike has admin and moderator roles, James is an admin and Larry is a moderator. To displaying users who doesn't have certain roles I created this scope:
public function scopeDoesntHaveRoles($query, $roles = [], $column = 'id') {
return $query->whereDoesntHave('roles')->orWhereHas('roles', function ($q) use ($roles, $column) {
$q->whereNotIn($column, $roles);
});
}
When I call User::doesntHaveRoles([1])->lists('name', 'id') to get users who doesn't have super role, it works and returns:
{"2":"Mike","3":"James","4":"Larry"}
But, when I trying to list users who doesn't have admin role User::doesntHaveRoles([2])->lists('name', 'id'), yes James is not shown there but Mike is appeared while he is actually has admin role:
{"1":"John","2":"Mike","4":"Larry"}
I think it's because Mike is also has moderator role, do you see something wrong in my scope? or do you have other solutions?
Thanks
Edit:
Here is my pivot schema
Schema::create('user_roles', function (Blueprint $table) {
$table->integer('user_id')->unsigned();
$table->integer('role_id')->unsigned();
$table->primary([
'user_id', 'role_id'
]);
});
User model
public function roles()
{
return $this->belongsToMany(Role::class, 'user_roles');
}
Role Model
public function users()
{
return $this->belongsToMany(User::class, 'user_roles');
}
I would use whereNotIn instead of whereDoesntHave.
Given a Role stored in the variable $role you can get all users who don't have that role with:
/* #var Role $role */
User::whereNotIn(function('id', $query) use ($role) {
$query->select('user_id')
->from('user_roles')
->where('role_id', $role->id);
});
The inner query will return all IDs of users who has the given role. Using whereNotIn will return the opposite set of users. The folowing query will be created:
select *
from users
where user_id not in (
select user_id
from user_roles
where role_id = ?
);
Now having a Collection of roles stored in $roles you can get all users who don't have any of that roles with:
/* #var Collection|Role[] $roles */
User::whereNotIn(function('id', $query) use ($roles) {
$query->select('user_id')
->from('user_roles')
->whereIn('role_id', $roles->pluck('id');
});
The inner select will return IDs of all users who has one of the roles in the collection. With whereNotIn you will again get the opposite result. You can also use an array of role-ids instead of $roles->pluck('id').
The builder will create a query like
select *
from users
where user_id not in (
select user_id
from user_roles
where role_id in (?, ?, ..)
);

Resources