Creating two relationships from the same controller method in laravel - laravel

I am trying to allow users to post pictures and comment on pictures. The pictures are related to the users through a one to many relationship and the comments are related to the pictures through a one to many relationship. I am now trying to simultaneously relate both users and pictures to comments whenever a comment is made. Currently, I am able to relate comments to either the picture or the user but not both.
Below is the controller method which handles comment uploads:
public function postComment(Request $request, $picture_id)
{
$this->validate($request, [
"comment" => ['required', 'max:1000'],
]);
$picture = Picture::find($picture_id);
$picture->status()->create([
'body' => $request->input('comment'),
]);
Auth::user()->statuses()->update([
'body' => $request->input('comment'),
]);
return redirect()->back();
}
I have tried creating one relationship and then creating the other relationship using update(). This did not work and resulted in the value remaining null.
Thank you for your help.

If i am understanding your database model correctly, when creating the status model for your picture all you need to do is to also include the user id like so:
public function postComment(Request $request, $picture_id)
{
$this->validate($request, [
"comment" => ['required', 'max:1000'],
]);
$picture = Picture::find($picture_id);
$picture->status()->create([
'body' => $request->input('comment'),
'user_id' => Auth::id()
]);
return redirect()->back();
}
Also make sure to add user_id to the $fillable property in the Status model to allow it to be assigned through the create method
protected $fillable = [
'body', 'user_id',
];
This should link the status you just created to both the picture and user class. You can then retrieve it elsewhere with something like Auth::user()->statuses

Related

Laravel validate if an ID has an instance of the relation

This if kind of a finicky problem, and the title may be a bit confusing, so I'll try and explain as best as I can.
Basically, I am creating a Recipe application, where users can create lists and add recipes to them.
I want the lists to only be able to have one instance of each recipe, so for example, if I add Recipe 10 to List number 1, I don't want the user to be able to add Recipe 10 any more to List 1.
Right now I've only used validation for checking if the meal_id (the id for the recipe) already exists in the recipes table, but with this I am only able to add each recipe to one list.
$this->validate($request, [
'name' => 'required',
'meal_id' => 'required|unique:recipes,meal_id',
'list_id' => 'required'
]);
My relations are simply that a Recipe hasOne RecipeList, and a RecipeList hasMany Recipes, but I'm fairly certain changing that up wont be the fix.
Recipe model
class Recipe extends Model
{
use HasFactory;
protected $fillable = [
'name',
'meal_id',
'list_id'
];
public function lists()
{
return $this->hasOne(RecipeList::class);
}
}
RecipeList model
class RecipeList extends Model
{
use HasFactory;
protected $fillable = [
'name',
'user_id'
];
public function user()
{
return $this->belongsTo(User::class);
}
public function recipes()
{
return $this->hasMany(Recipe::class);
}
}
Recipes table
public function up()
{
Schema::create('recipes', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('meal_id');
$table->unsignedBigInteger('list_id');
$table->timestamps();
});
}
However maybe I need to set up some form of pivot table?
Does anyone have any ideas on some smart way of achieving what I want through the validation, or any other ideas?
You can use Rule and create a custom validation to add your conditions.
Here I assume your condition is to add a unique restriction on both list_id and meal_id
$this->validate($request, [
'name' => 'required',
'list_id' => 'required',
'meal_id' => [
required,
unique:recipes,meal_id,
\Illuminate\Validation\Rule::unique('recipes')->where(function($query) use ($request){
$query->where('list_id', $request->list_id)
->where('meal_id', $request->meal_id);
}),
],
]);
Use firstOrCreate(). Read more here: https://laravel.com/docs/8.x/eloquent#retrieving-or-creating-models
Example:
$recipeData = $request->validate([
'name' => 'required',
'meal_id' => 'required',
'list_id' => 'required'
]);
$recipe = Recipe::firstOrCreate($recipeData);
This will use your 3 fields, name, meal_id & list_id and will either select one or create it if it doesn't exist already.
Alternatively you can do a query to check if it exists. If you want to return an error/message to the user explaining that the new one is a duplicate.
$existingRecipe = Recipe::where([
'meal_id' => $recipeData['meal_id'],
'list_id' => $recipeData['list_id'],
'name' => $recipeData['name']])
->first();
if(!is_null($existingRecipe)) {
//return exists
}
Of course, using rules will be more efficient, depends on what you want your UI to express.
See BABAK ASHRAFI's answer

Eloquent eager loading specific columns

I have two models :Product and category
which are linked by a one-to-many relationship. A category has several products. I would like to select specific columns from each model.
Here is the query I have, but I have all the columns with category_id, but I want the category name instead of id. How can I do that. Thank you in advance.
here is the method in controller
$products = Product::with('categories:id,name')->get();
if ($products) {
$response = ['api_status' => 1, 'api_message' => 'success', 'data' => $products];
return response()->json($response);
} else {
$response = ['api_status' => 0, 'api_message' => 'Error'];
return response()->json($response);
}
Here is category model
class Categorie extends Model
{
use HasFactory, SoftDeletes;
protected $fillable =['name','redirect'];
public function products()
{
return $this->hasMany(product::class);
}
}
and the product model is:
class Product extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'description',
'detail', 'img',
'categorie_id', 'onSale',
'costPrice', 'inStock', 'salePrice'
];
public function categories()
{
return $this->belongsTo(Categorie::class);
}
}
here is the response:
To modify the output of your model I'd suggest using an API resource. This will give you more granular control about how a resource is returned by the API. A resource is also the best point to modify certain values.
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'detail' => $this->detail,
'img' => $this->img,
'category_id' => $this->categorie->name,
'category_name' => $this->categorie->name,
'onSale' => $this->onSale,
'costPrice' => $this->costPrice,
'inStock' => $this->inStock,
'salePrice' => $this->salePrice,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
'categories' => $this->categories ?? null,
];
}
}
This way you can manually specify which values your response should have.
In your controller you can include the populated array in your response by manually filling the toArray method with the current request object or just by using the resolve method which basically does the previous task for you:
$response = [
'api_status' => 1,
'api_message' => 'success',
'data' => ProductResource::collection($products)->resolve()
];
You can select particular fields from the relationship but you always need to select any keys involved in the relationship:
$products = Product::with('categories:id,name')->get();
Now each Product has its 'categories' loaded and those Category models only have the id and name fields.
Importantly:
The relationship categories is named incorrectly, it should be categorie in this case as the foreign key on Product is categorie_id and it is a singular relationship, it does not return multiple results.
Product::with('categorie:id,name')->get()
If you want to keep the name categories you would have to define the foreign key used when defining the belongsTorelationship, the second argument.
If you need to transform the structure of any of this that is a different thing and you will be walking into transformers or an API Resource.
Not sure how you want your data to look but this is the structure you will have by eager loading records, so if you need a different structure then what you get you will have to show an example.

Add the inserted id to the pivot table

I have a users, items, user_item tables. I need to populate the user_item table with a user_id and item_id when a user is created.
so far, I have a basic registration function
public function register(Request $request) {
$user = User::create([
'name' => $request->name,
'user_type' => $request->user_type,
'email' => $request->email,
'password' => bcrypt($request->password)
]);
$token = auth()->login($user);
return $this->respondWithToken($token);
}
So far it saves only to the users table ofcourse.
I've looked at some documentations on using attach(), however, I got stuck on this one..
In the register function, i added a $item array:
$item = Item::create([
'user_id' => !!!!, -> How do I get the id of the inserted user
'instrument_id' => $request->instrument_id
]);
$user->role()->attach($item)
Also, what is role()? is it a built-in laravel function?
Note that I haven't tried running this function since I got stuck on these problems. So I don't event know if it's gonna work.
Anyone help me on this one? I'm a laravel newbie and got really confused on the documentations.
the method create return the model it self after loading it's attributes from db,
so when you want the user id just use $user->id after create() method.
for the default permission that shipped with laravel it is
spatie/laravel-permission
and to assign role to a user you can use:
$user->assignRole('writer'); // just set role name

Laravel avoid duplicate entry from model

I'm building a Laravel API. I have a models called Reservations. I want to avoid that a user creates two reservations for the same product and time period.
I have the following:
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
Edit after comments:
I'm also using validation
$validator = Validator::make($request->all(), [
'listing_id' => 'required|exists:listings,id',
'user_id_from' => 'required|exists:users,id',
'start_date' => 'required|date_format:"Y-m-d"|after:today',
'end_date' => 'required|date_format:"Y-m-d"|after:start_date'
]);
if ($validator->fails()) {
return response()->json(['error' => 'Validation failed'], 403);
}
Validation is working properly.
End of Edit
In my model I have casted the start_date and end_date as dates.
class Reservation extends Model
{
protected $fillable = ['listing_id', 'start_date', 'end_date'];
protected $dates = [
'start_date',
'end_date'
];
....
....
Documentation says:
The firstOrCreate method will attempt to locate a database record
using the given column / value pairs
However I notice that I'm still able to insert entries with the same attributes.
Any idea what I'm doing wrong or suggestions to fix it?
Probably there's a better way than this, but you can create an static method on Reservation to do this, like:
public static function createWithRules($data) {
$exists = $this->where('product_id', $data['product_id'])->whereBetween(*date logic that i don't remember right now*)->first();
if(!$exists) {
* insert logic *
} else {
* product with date exists *
}
}
So you can call Reservation::createWithRules($data)
You can achieve this using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
public function store(){
$this->validate($request, [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
], $this->messages);
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
}
With this, you're validating users $request with by saying that specified columns are required and that they need to be unique, in order for validation to pass.
In your controller, you can also create messages function to display error messages, if the condition isn't met.
private $messages = [
'listing_id.required' => 'Listing_id is required',
'title.unique' => 'Listing_id already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StoreReservation
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StoreReservation $request)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $request->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.

Two store() methods, one doesn't work (Call to undefined method save())

One of two similar store methods doesn't work. Could you clarify this for me?
Relations
A Team hasMany Users <> A User belongsTo a Team
A User hasMany Characters <> A Character belongsTo a User
Working Code (CharacterController)
public function store()
{
$fighters = Fighter::pluck('name')->toArray();
$this->validate(request(), [
'name' => 'required|min:3|max:25|alpha_num|not_in:'.Rule::notIn($fighters).'unique:characters',
'fighter' => 'required|in:'.Rule::in($fighters),
]);
auth()->user()->characters()->save(new Character([
'name' => request('name'),
'fighter' => request('fighter'),
]));
return redirect()->route('character.index');
}
Not Working (TeamController)
public function store()
{
$this->validate(request(), [
'name' => 'required|min:3|max:25|alpha_num|unique:teams',
]);
auth()->user()->team()->save(new Team([
'name' => request('name'),
'fame' => 0,
]));
return redirect()->route('team.index');
}
Questions
Why is the same method not available? Is it relation stuff?
Is the create method better? Should I try to use it?
Thought I know what I'm doing, now it turns out I don't...
Thank you for helping.
team() is a belongsTo relation, you probably have a team_id col in your user table which you want to associate with the team.
public function store()
{
$this->validate(request(), [
'name' => 'required|min:3|max:25|alpha_num|unique:teams',
]);
// create and save team
$team = new Team([
'name' => request('name'),
'fame' => 0,
]);
$team->save();
// associate current authenticated user with team (set foreign key) and save user
auth()->user()->team()->associate($team)->save();
return redirect()->route('team.index');
}

Resources