insert relationship with save method using Laravel - laravel

I have 2 models with a many to many relationship using Laravel and I want to insert into the relationship table upon using the save method.
I have save method creating the new row for the child and I have access to the parent id.
I need to insert a new row into the relationship table using the parent id that I have access to already along with the id created by the create function within the controller
MODELS
class Objective extends Model
{
public function subjects() {
return $this->belongsToMany('App\Models\Subject');
}}
class Subject extends Model
{
public function objectives() {
return $this->belongsToMany('App\Models\Objective');
}}
The function subject receives the id of the parent from the url and the create function should takes in this id too.
CONTROLLER
class ObjectiveController extends Controller
{
public function subject($id)
{
$subjects = Subject::find($id);
$objectives = DB::table('objectives')->get();
return view('admin.objectives.subject',['objectives' => $objectives],['subjects' =>
$subjects],compact('objectives','subjects'));
}
public function create($id)
{
$post = new Objective;
$post->name = 'NEW OBJ';
$post->save();
return redirect('objectives');
}
}
ROUTE
Route::get('objectives/create/{id}', [
'uses' => 'App\Http\Controllers\ObjectiveController#create',
'as' => 'admin.objectives'
]);
VIEW
<a class="card-header-action" href="{{url('objectives/create', ['id' => $subjects->id]) }}"><small class="text-muted">Add New</small></a>

You can use attach to add to the pivot table:
...
$post->save();
$post->subjects()->attach($id);

Related

laravel 8 store request with foreign key user_id not working

I would like to store the corresponding logged in user when adding a new School data. What I'm trying to do is store the logged in user_id in the schools table, in order to know on who added the school data. I have a users table already, which will establish the relation in the schools table.
My goal is when an admin is logged in, he/she can see all of the School records, otherwise if it's a user, then only fetch the records he/she added. The problem is that I can't figure out on when and where to insert the user_id data during the store request as I'm getting an error "user id field is required". Here's what I've tried so far:
Migration:
class CreateSchoolsTable extends Migration
{
public function up()
{
Schema::create('schools', function (Blueprint $table) {
$table->id();
$table->string('school_name');
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
}
School Model:
class School extends Model
{
use HasFactory;
protected $fillable = ['school_name', 'user_id'];
public function User() {
return $this->belongsTo(User::class);
}
}
Store Request:
class StoreSchoolRequest extends FormRequest
{
public function rules(): array
{
return [
'school_name' => 'required|string|max:255',
'user_id' => 'required|exists:users,id'
];
}
}
Controller:
class SchoolController extends Controller
{
public function store(StoreSchoolRequest $request) {
$school_data = $request->validated();
$user_id = \Auth::user()->id;
$school_data['user_id'] = $user_id;
School::create($school_data );
return Redirect::route('schools.index');
}
}
Any inputs will be of big help! Thanks.
Laravel has elegant way to bind authenticated user_id. Remove user_id from request class and chaining method. Also setup relationship from User model to School Model
Form Request Class
class StoreSchoolRequest extends FormRequest
{
public function rules(): array
{
return [
'school_name' => 'required|string|max:255',
];
}
}
User Model
protected $fillable = ['school_name', 'user_id'];
...
// new line
public function schools() {
return $this->hasMany(School::class);
}
Your Controller
class SchoolController extends Controller
{
public function store(StoreSchoolRequest $request) {
auth()->user()->schools()->create($request->validated());
return Redirect::route('schools.index');
}
}
UPDATE ANSWER
Since user_id value is school name (based on image link from comment), probably there's something wrong either in User or School model. Here the quick fix
Your Controller
class SchoolController extends Controller
{
public function store(StoreSchoolRequest $request) {
auth()->user()->schools()->create(
array_merge(
$request->validated(),
['user_id' => auth()->id()]
)
);
return Redirect::route('schools.index');
}
}
You can add 'created_by' and 'updated_by' fields to your table. so you can register in these fields when additions or updates are made.
Then you can see who has added or updated from these fields.
class School extends Model
{
use HasFactory;
protected $fillable = ['school_name', 'user_id', 'created_by', 'updated_by'];
public function User() {
return $this->belongsTo(User::class);
}
}
Your controller part is correct but since you get the logged in user, you wont be having user_id in the request. So you should remove the rules about user_id from your StoreSchoolRequest.
class StoreSchoolRequest extends FormRequest
{
public function rules(): array
{
return [
'school_name' => 'required|string|max:255'
];
}
}
Problem is here ..
$school_data = $request->validated();
Since you are using $request->validated()..
You have to safe()->merge user_id into it , here Docs : .
$validated = $request->safe()->merge(['user_id' => Auth::user()->id]);
Then put this $validated into create query , Thanks. –

Laravel query builder with the User and Auth

How to List all rows from a table (agendas) in my DB where records are saved by the connected user.I'm using default Auth from Laravel.
public function index ($id = null)
{
$agendas = Agenda::where('id', Auth::user()->id)->get();
$users = User::all();
return view('admin.agendas.index', compact('agendas','users','id'));
}
My controller here.
Need help
Assuming
agendas table (containing records for Agenda model) has a column user_id which references the id column on users table
User hasMany Agenda
Agenda belongTo User
class User extends Model
{
public function agendas()
{
return $this->hasMany(Agenda::class);
}
//... rest of class code
}
class Agenda extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
//... rest of class code
}
public function index($id = null)
{
$user = User::with('agendas')->findOrFail(auth()->id());
return view('admin.agendas.index', [
'user' => $user,
'agendas' => $user->agendas,
'id' => $id
]);
}
Your User model should have a relationship:
public function agendas()
{
return $this->hasMany(Agenda::class);
}
In your controller, you could then simplify the use as such:
public function index (Request $request, $id = null)
{
$agendas = $request->user()->agendas;
$users = User::all();
return view('admin.agendas.index', compact('agendas','users','id'));
}
if you wanna get data related to another data , you have to join those tables together by a field. in this case i guess you have a column in Agenda table named 'user_id'.
you have two way :
join tables in your Model.
search in Agenda table in your controller
if you want to use joining your tables from model :
// in App\User.php
...
class User ...{
...
public function Agenda(){
return $this->hasMany(Agenda::class);
}
...
}
...
then you can access to all of "Agenda" from everywhere like this :
Auth()->user()->agenda
if you want to search in table from your controller you can do :
Agenda::where('id', Auth::user()->id)
you can read more about eloquent-relationships in : https://laravel.com/docs/8.x/eloquent-relationships

How to make Laravel Resources work with polymorphic relation

I have a polymorphic relation in a Laravel application. I want a user of the website to be able to give a rating to both a User model as well as Product model.
I have following models and relations
class Rating extends Model
{
public function ratable()
{
return $this->morphTo();
}
}
class User extends Authenticatable
{
public function ratings()
{
return $this->morphMany('App\Rating', 'ratable');
}
}
class Product extends Model
{
public function ratings()
{
return $this->morphMany('App\Rating', 'ratable');
}
}
and the following database migration:
class CreateRatingsTable extends Migration
{
public function up()
{
Schema::create('ratings', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('ratable_id');
$table->string('ratable_type');
$table->double('rating');
$table->text('comment');
$table->timestamps();
});
}
}
I have defined two routes:
1) Route::post('products/{product}/rating', 'ProductController#setRating')->name('products.rating');
2) Route::post('users/{user}/rating', 'UserController#setRating')->name('users.rating');
I have the following code in the controller (will only show the Product example)
public function setRating(Request $request, Product $product)
{
$rating = new Rating();
$rating->rating = $request->rating;
$rating->comment = $request->comment;
$product->ratings()->save($rating);
}
The above works perfectly and the correct records get inserted in the database depending on whether the Product route or the User route is called.
Now, all the rest of my code is using Laravel Resources, and for consistency reasons, I have also defined a resource for Rating:
class RatingResource extends JsonResource
{
public function toArray($request)
{
return [
'ratable_id' => $this->ratable_id,
'ratable_type' => $this->ratable_type,
'rating' => $this->rating,
'comment' => $this->comment
];
}
}
I'm also changing the ProductController code to use this resource
public function setRating(Request $request, Product $product)
{
return new RatingResource(Rating::create([
'ratable_id' => $product->id,
'ratable_type' => $product,
'rating' => $request->rating,
'comment' => $request->comment,
]));
}
In postman, I'm calling the REST API:
http://{{url}}/api/products/1/rating with body:
rating: 4
comment: "Test"
Yet, I always get following error message
"SQLSTATE[HY000]: General error: 1364 Field 'ratable_id'
doesn't have a default value (SQL: insert into ratings (rating,
comment, updated_at, created_at) values (4, test, 2019-09-07
13:44:22, 2019-09-07 13:44:22))"
I'm not passing the ratable_id and ratable_typeas I'm filling these in already in the controller code.
I somehow need to pass the resource that it's a Productor a UserI'm giving a rating for.
How can I make this work?
The problem probably is that ratable_id is missing from $fillable.
Try $product->ratings()->create([...data...]) so you don't have to set ratable_id and ratable_type yourself:
public function setRating(Request $request, Product $product)
{
return new RatingResource(
$product->ratings()->create([
'rating' => $request->rating,
'comment' => $request->comment,
])
);
}

Eloquent Injected Multiple Model Relationships

So I learned in JeffreyWay's screencasts that I can use Eloquent to get the associated id from a model injected to another model.
I'm following his series about Laravel 5.4.
Here, I have a one-to-many relationships of user to posts.
App/Post
public function user()
{
return $this->belongsTo(User::class);
}
In my User Model, I have a publish method where the Post Model is injected. The publish method is used to create a post entry into the database.
App/User
public function posts()
{
return $this->hasMany(Post::class);
}
public function publish(Post $post)
{
$this->posts()->save($post);
}
I then have a store method in my PostsController that calls the publish method inside my User Model.
PostsController
class PostsController extends Controller
{
public function __construct()
{
$this->middleware('auth')->except(['index', 'show']);
}
public function store()
{
auth()->user()->publish(
new Post(request(['title', 'body']))
);
}
}
When the publish method is called, the injected Post class automatically sets the user_id to the save method.
My question is, how do I make a relationship like this in a situation where for every posts, there are comments. These comments are associated to the Post and the User that created the comment.
In short, I should have both user_id and post_id when I call the addComment method.
User Model:
public function posts(){
return $this->hasMany(Post::class);
}
public function comments(){
return $this->hasMany(Comments::class);
}
Posts Model
public function user(){
return $this->belongsTo(User::class);
}
public function comments(){
return $this->hasMany(Comments::class);
}
Comments Model
public function post(){
return $this->belongsTo(Post::class);
}
public function user(){
return $this->belongsTo(User::class);
}
Example Problems:
1) Get user comments:
Solution: auth()->user()->comments()->get(); <- collection of user
comments .
2) Get user from the given comment:
Solution: Comment::find($someCommentId)->user()->first()->name; <-
User name from a specific comment.
3) Get all comments for a specific post .
Solution: Post::first()->comments()->get(); or eager load
Post::with('comments')->first(); <- A collection that contains post
information within it u can find a collection of comments for that
post.
4) Load user when loading a comment:
Solution: Comment::with('user')->first(); <- single collection
containing a collection with user info and comment info.
5) Load a specific user post and comments for that post at the same time:
Solution: User::with('posts.comments')->first(); <- Contains a
collection with user info and collection of all user posts with each
post containing comments.
In your question you wrote:
In short, I should have both user_id and post_id when I call the addComment method.
Which is absolutely correct and no problem. You don't have to set these properties through a method like $user->posts()->save($post) - this is just a convenience method that does the job for you (see save($model) and related setForeignAttributesForCreate($model) in the framework code; these methods just set the foreign key property for you).
In fact, the following three ways to create a new post are interchangeable:
// what you did
$user->posts->save(
new Post([
'title' => 'Hello',
'body' => 'World!',
])
);
// equivalent
Post::create([
'user_id' => \Auth::user()->id, // or \Auth::id()
'title' => 'Hello',
'body' => 'World!',
]);
// also equivalent
$post = new Post([
'user_id' => \Auth::user()->id, // or \Auth::id()
'title' => 'Hello',
'body' => 'World!',
]);
$post->save();
When storing a new comment, you will most likely have a controller like this, because a comment always belongs to a post and you therefore will need a reference of the post:
class CommentsController extends Controller
{
public function __construct()
{
$this->middleware('auth')->except(['index', 'show']);
}
public function store(Post $post)
{
$comment = new Comment(request(['body']));
$comment->user_id = \Auth::user()->id;
$comment->post_id = $post->id;
$comment->save();
}
}
You could also abbreviate it and write:
Comment::create(
array_merge(request(['body']), ['user_id' => \Auth::id(), 'post_id' => $post->id])
);

Laravel - Seeding Many-to-Many Relationship

I have a users table and a roles table that has a many-to-many relationship. These two tables are connected to a junction table called role_user.
This is a model of the tables and its connections.
Below are the Models in my Laravel project:
User
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
Role
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\User');
}
}
Below is the Factory file in the Laravel project:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('secret'),
];
});
$factory->define(App\Role::class, function (Faker\Generator $faker) {
return [
'role' => $faker->realText($maxNbChars = 2),
'description' => $faker->realText($maxNbChars = 20),
];
});
Below is the Seed file in the Laravel project:
public function run()
{
factory(App\User::class, 50)->create()->each(function ($u) {
$u->roles()->save(factory(App\Role::class)->make());
});
factory(App\Role::class, 20)->create()->each(function ($u) {
$u->users()->save(factory(App\User::class)->make());
});
}
This should populate the users table and the roles table but how do I go about populating the role_user table? (I don't have a Model file for the junction table.)
I'm very new at this so any help would be appreciated. Thanks.
You can use attach() or sync() method on a many-to-many relationship.
There are multiple ways you can approach this. Here one of them:
// Populate roles
factory(App\Role::class, 20)->create();
// Populate users
factory(App\User::class, 50)->create();
// Get all the roles attaching up to 3 random roles to each user
$roles = App\Role::all();
// Populate the pivot table
App\User::all()->each(function ($user) use ($roles) {
$user->roles()->attach(
$roles->random(rand(1, 3))->pluck('id')->toArray()
);
});
Another way is to use saveMany() function
public function run()
{
factory(App\User::class,3)->create();
$roles = factory(App\Role::class,3)->create();
App\User::All()->each(function ($user) use ($roles){
$user->roles()->saveMany($roles);
});
}
A much cleaner method can be: after you define the factory for App\User and App\Roles you can call the afterCreating method like this:
$factory->define(App\User::class, function ...);
$factory->define(App\Role::class, function ...);
$factory->afterCreating(App\User::class, function ($row, $faker) {
$row->roles()->attach(rand(1,20));
});
Then in Seeds you first create the roles, then the users
public function run()
{
factory(App\Role::class, 20)->create();
factory(App\User::class, 50)->create();
}
Now you have 50 users each of them with one role attached.
Just for a seeder you can use something like this:
for ($i = 0; $i < 50; $i++) {
$user = factory(App\User::class)->create();
$role = factory(App\Role::class)->create();
DB::table('role_user')->insert([
'user_id' => $user->id,
'role_id' => $role->id
]);
}
But normally you need to define relation like has many through https://laravel.com/docs/5.4/eloquent-relationships#has-many-through
Then you will be able to use:
$user->roles()->save($role);
Better to use this structure:
App\Role::factory()->count(20)->create();
// Populate users
App\User::factory()->count(50)->create();
// Getting all roles and saving them to variable is not too good idea.
// Instead, get count of rows.
$rolesCount = App\Role::count();
// Populate the pivot table
App\User::all()->each(function ($user) use ($rolesCount) {
$user->roles()->attach(
App\Role::all()->random(rand(1, $rolesCount))->pluck('id')->toArray()
);
});

Resources