I have a relationship between work 'days' and projects of different types. So my 'days' record has a reference to my 'projects' table twice because I have two different types of projects called 'Series' and 'Event'.
In my 'days' resource I've created two fields as such:
BelongsTo::make('Series','series',Project::class)->sortable()->nullable(),
BelongsTo::make('Event','event',Project::class)->sortable()->nullable(),
What I'm trying to do is filter the projects by their types so I've created this:
public static function relatableProjects(NovaRequest $request, $query){
return $query->where('type', 'Series');
}
I've tried making relatableSeries and relatableEvents but they don't work. How can I make this connect to the fields correctly without having to create two separate tables for 'series' and 'events'.
The relatableQuery above winds up filtering both resource fields.
Because relatableQuery() is referencing a relatableModel() (so relatableProjects() references the Project model) I was able to create another model solely for the purpose of helping with this.
I created an Event model which references the same projects table and then was able to create a relatableEvents() method to use the where() filter query.
Note: I did have to also create an Event resource which references the Event model since this is how Nova works but was able to hide it from being accessed which you can find more information about here
See revised BelongsTo fields and new model below:
Day resource
/**
* Build a "relatable" query for the given resource.
*
* This query determines which instances of the model may be attached to other resources.
*
* #param \Laravel\Nova\Http\Requests\NovaRequest $request
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public static function relatableProjects(NovaRequest $request, $query){
return $query->where('type', 'Series');
}
/**
* Build a "relatable" query for the given resource.
*
* This query determines which instances of the model may be attached to other resources.
*
* #param \Laravel\Nova\Http\Requests\NovaRequest $request
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public static function relatableEvents(NovaRequest $request, $query){
return $query->where('type', 'Event');
}
/**
* Get the fields displayed by the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function fields(Request $request)
{
return [
ID::make()->hideFromIndex()->hideFromDetail()->hideWhenUpdating(),
BelongsTo::make('User','user',User::class)->sortable(),
BelongsTo::make('Budget','budget',Budget::class)->sortable()->nullable(),
BelongsTo::make('Series','series',Project::class)->sortable()->nullable(),
BelongsTo::make('Event','event',Event::class)->sortable()->nullable(),
DateTime::make('Last Updated','updated_at')->hideFromIndex()->readOnly(),
new Panel('Schedule',$this->schedule()),
new Panel('Time Entry',$this->timeEntries()),
];
}
Event model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Event extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'projects';
protected $casts = [
'starts_on' => 'date',
'ends_on' => 'date',
];
public function event(){
return $this->belongsTo('App\Event');
}
public function project(){
return $this->belongsTo('App\Project');
}
}
i know it is an old question but i was facing same problem with laravel nova belongsto field, in some resource i have a belongsto that relates to users but these should be of 'supervisor' role in another resource i hace a belongsto field relating to users but these should be of role 'guard', as laravel nova belongsto field just takes all users in both selects all users appeared and it seems nova belongsto field doe not have a way, or at least i did not find it to scope the query, so what i did was creating a php class named BelongstoScoped this class extends laravel nova field BelongsTo so i overwrote the method responsible of creating the query
<?php
namespace App\Nova\Customized;
use Laravel\Nova\Query\Builder;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Http\Requests\NovaRequest;
class BelongsToScoped extends BelongsTo
{
private $modelScopes = [];
//original function in laravel belongsto field
public function buildAssociatableQuery(NovaRequest $request, $withTrashed = false)
{
$model = forward_static_call(
[$resourceClass = $this->resourceClass, 'newModel']
);
$query = new Builder($resourceClass);
//here i chaned this:
/*
$query->search(
$request, $model->newQuery(), $request->search,
[], [], ''
);
*/
//To this:
/*
$query->search(
$request, $this->addScopesToQuery($model->newQuery()), $request->search,
[], [], ''
);
*/
//The method search receives a query builder as second parameter, i just passed the result of custom function
//addScopesToQuery as second parameter, thi method returns the same query but with the model scopes passed
$request->first === 'true'
? $query->whereKey($model->newQueryWithoutScopes(), $request->current)
: $query->search(
$request, $this->addScopesToQuery($model->newQuery()), $request->search,
[], [], ''
);
return $query->tap(function ($query) use ($request, $model) {
forward_static_call($this->associatableQueryCallable($request, $model), $request, $query, $this);
});
}
//this method reads the property $modelScopes and adds them to the query
private function addScopesToQuery($query){
foreach($this->modelScopes as $scope){
$query->$scope();
}
return $query;
}
// this method should be chained tho the field
//example: BelongsToScoped::make('Supervisores', 'supervisor', 'App\Nova\Users')->scopes(['supervisor', 'active'])
public function scopes(Array $modelScopes){
$this->modelScopes = $modelScopes;
return $this;
}
}
?>
In my users model i have the scopes for supervisors and guard roles like this:
public function scopeActive($query)
{
return $query->where('state', 1);
}
public function scopeSupervisor($query)
{
return $query->role('supervisor');
}
public function scopeSuperadmin($query)
{
return $query->role('superadmin');
}
public function scopeGuarda($query)
{
return $query->role('guarda');
}
So in the laravel nova resource i just included the use of this class
*remember the namespace depends on how you name your file, in my case i created the folder Customized and included the file there:
use App\Nova\Customized\BelongsToScoped;
In the fields in nova resource i used like this:
BelongsToScoped::make('Supervisor', 'supervisorUser', 'App\Nova\Users\User')
->scopes(['supervisor', 'active'])
->searchable()
So that way i could call the belongsto field in the nova resources which filter users depending on modle scopes.
I hope this helps someone, sorry if my English is not that good.
Related
I'm working inside a Laravel 9 project and am switching over my controller methods to scope the resources. For example, I have an Affiliate model where I'm grabbing the company ID and affiliate ID from the function, and then querying these.
I'm now trying to switch it over to defining the model in the function argument, but aren't sure how to parse company_id through to Affiliate?
This is the previous controller method:
/**
* Display the specified resource.
*
* #return \Illuminate\Http\Response
*/
public function show($company_id, $id)
{
$this->authorize('view', Affiliate::class);
$affiliate = Affiliate::where('company_id', $company_id)
->where('id', $id)
->first();
if (!$affiliate) {
return new ApiSuccessResponse(null, [
'message' => 'Affiliate not found or invalid affiliate ID.'
], 404);
}
return new ApiSuccessResponse($affiliate);
}
And here's my progress so far, how do I add the company_id check to the affiliate?
/**
* Display the specified resource.
*
* #return \Illuminate\Http\Response
*/
public function show($company_id, Affiliate $affiliate)
{
$this->authorize('view', $affiliate);
return new ApiSuccessResponse($affiliate);
}
My route looks like:
/api/company/1000/affiliates/2
And my api.php file:
Route::apiResource('company.affiliates', AffiliateController::class);
I'm working in a Laravel 9 project. I have a Buyer model which has a relationship called tiers, I need to load the count on here.
Right now, I'm doing it like:
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show(Company $company, Buyer $buyer)
{
$this->authorize('view', Buyer::class);
$buyer = Buyer::where('company_id', $company->id)
->where('id', $buyer->id)
->withCount('tiers')
->first();
if (!$buyer) {
return new ApiSuccessResponse(null, [
'message' => 'Buyer not found or invalid buyer ID.'
], 404);
}
return new ApiSuccessResponse($buyer);
}
There must be a better way since the buyer instance is already defined and working as the function argument.
Something like...
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show(Company $company, Buyer $buyer)
{
$this->authorize('view', Buyer::class);
$buyer = $buyer->withCount('tiers');
return new ApiSuccessResponse($buyer);
}
Why doesn't this work and what do I need to change to get it to work?
Laravel has deferred loading for counts, so all you need to do is use loadCount instead of withCount, and there's no need to reassign the variable:
public function show(Company $company, Buyer $buyer)
{
$this->authorize('view', Buyer::class);
$buyer->loadCount('tiers');
return new ApiSuccessResponse($buyer);
}
I'm stuck in a policy problem. Actually I have a OrderEventHistoryPolicy and a
class OrderEventHistoryPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can create the order event history.
*
* #param \App\User $user
* #param \App\Models\Order\Order $order
*/
public function create(User $user, OrderEventHistory $orderEventHistory, Order $order)
}
and a OrderEventHistoryController
class OrderEventHistoryController extends Controller
{
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Models\Order\Order $order
*/
public function store(Request $request, Order $order)
{
$this->authorize('create',OrderEventHistory::class, $order);
}
}
My aim is to pass the Order model to the policy class (OrderEventHistoryPolicy), but according to
Laravel 5.8 documentation, you can just pass the class name with action like 'OrderEventHistoryPolicy'. By
passing the class name I am not able to pass the Order model (of course).
Here's the Laravel documentation:
As previously discussed, some
actions like create may not require a model instance. In these
situations, you may pass a class name to the authorize method. The
class name will be used to determine which policy to use when
authorizing the action:
public function create(Request $request) {
$this->authorize('create', Post::class);
// The current user can create blog posts...
}
What I was thinking is to pass a empty OrderEventHistory model like this:
$this->authorize('create',new OrderEventHistory(), $order);
But I am not sure this is the "right" and "clean" way for doing it.
You can pass an array to authorize():
$this->authorize('create', [new OrderEventHistory(), $order]);
The elements of the array will be passed to the policy method as separate parameters, so your policy code is already correct.
I am building a small app in Laravel, but I needed to use some help. I read similar tasks but I don't see them related to what I have.
The app that I'm building is a dictionary and I really don't get the part where words are displayed by id in the URL. I tried to revert the logic for the curds (store, update, delete, show) so that they perform actions using the word name and not the word id. That worked. The redirecting to route app/{name} after the actions does work too.
Now, I presume this is not how it needs to be done, when the curds aren't taking actions on the id?
I also read about slugs, but since I'm building a dictionary, don't understand why I need to separate the word by a "-"? It's a single word, not like posts. And why do I need to have an extra field in my form for the slug, as the slug must be equal to the name? I'm getting here an error: the slug->unique() field must be not null.
If somebody has the time, please explain to me what's the best way for making the names of the words being displayed in the URL. Thank you, I appreciate it.
Edit
Here is my code for the update crud:
public function update(UpdateWordRequest $request, $name)
{
Word::query()->where('name', $name)->firstOrFail()->update($request->input());
return redirect()->route('dictionary.show', ['name' => $name])->with('success', 'Edited successfully!');
}
It's working fine, but when I re-edit the word and change it to word2, with a number, I then get this error: Trying to get property 'name' of non-object.
What do I need to make of it? Thank you :)!
I think you should be able to use a trait to fix this issue.
I would create a trait call hasSlug and have the following code:
<?php
namespace App\Traits;
trait HasSlug
{
protected static function bootHasSlug()
{
static::creating(function ($model) {
$columnName = static::getSlugColumn();
$model->$columnName = str_slug($model->name); //convert your name to slug
});
}
public function getSlugAttribute()
{
$columnName = static::getSlugColumn();
return (string) $this->attributes[$columnName];
}
protected static function getSlugColumn()
{
if (isset(static::$slugColumn)) {
return static::$slugColumn;
}
return 'slug';
}
}
So now if you use HasSlug trait to your model every time you create a record it will go and look for name field from your model and create a slug out of it.
Be sure to apply unique constrain to your name field if needed.
For more robust have a look at spatie slug package https://github.com/spatie/laravel-sluggable
Here is what I finally come up with.
Word.php
I used as #usrNotFound suggested spatie/laravel-sluggable.
<?php
namespace App;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
class Word extends Model
{
use HasSlug;
/**
* #return SlugOptions
*/
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('name')
->saveSlugsTo('slug')
->slugsShouldBeNoLongerThan(20);
}
/**
* #param $query
* #param $slug
* #return mixed
*/
public function scopeFindBySlug($query, $slug)
{
return $query->where('slug', $slug)
->get();
}
}
WordController.php
<?php
namespace App\Http\Controllers;
use App\Word;
use App\Http\Requests\CreateWordRequest;
use App\Http\Requests\UpdateWordRequest;
class WordController extends Controller
{
/**
* Store a newly created resource in storage.
* #param CreateWordRequest $request
* #return mixed
*/
public function store(CreateWordRequest $request)
{
$word = Word::query()
->create($request->input());
return redirect()
->route('dictionary.show', $word->slug)
->with('success', 'Created successfully!');
}
/**
* Display the specified resource.
* #param $slug
* #return mixed
*/
public function show($slug)
{
$word = Word::whereSlug($slug)
->firstOrFail();
return view('word.show')
->with('word', $word);
}
/**
* Update the specified resource in storage.
* #param UpdateWordRequest $request
* #param $slug
* #return mixed
*/
public function update(UpdateWordRequest $request, $slug)
{
Word::whereSlug($slug)
->firstOrFail()
->update($request->input());
return redirect()
->route('dictionary.show', $slug)
->with('success', 'Edited successfylly!');
}
/**
* Remove the specified resource from storage.
* #param $slug
* #return mixed
*/
public function destroy($slug)
{
Word::whereSlug($slug)
->delete();
return redirect()
->route('dictionary.index')
->with('success', 'Deleted successfully');
}
}
Route.php
Route::get('/dictionary/{slug}', 'WordController#show')
->name('dictionary.show')
->where('slug', '[-A-Za-z0-9_-]+');
Creating, updating, destroying new words works fine. Eventually there is a small problem. When I want to update a particular word and change the name of it, after saving the word, I'm getting
Sorry, the page you are looking for could not be found.
and if I look at the url I see the old name instead of the new one. Has this to do with my update method and how do I fix it?
Laravel newbie here, sorry if this is painfully obvious but I've been stuck on it for ages!
Objective: To mass-assign a Quote::create() database insertion with the full values from the form, plus set the User ID to the currently logged in user.
Problem: The user_id column is never written to the database. Every other column is, but user_id remains as 0.
I have of course tried adding user_id to the $fillable array, but I don't want it to be user-fillable - I want it to be set by Laravel's Auth::id() function.
Any ideas why this won't get stored? Is it because the $quote->create() function doesn't factor in previously set data and just takes its parameter as everything to be saved? If so how do I do this?
Here's my controller's store() function:
/**
* Stores a created quote in the database
*
* #param QuoteRequest $request
*
*/
public function store(QuoteRequest $request)
{
// This method will only get fired if QuoteRequest passes
$quote = new Quote;
$quote->user_id = Auth::id();
$quote->create($request->all());
echo 'Job done';
}
Here's my Quote model:
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
use Auth;
class Quote extends Model {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'quotes';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'quote_person',
'quote_value',
'quote_date'
];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [ ];
/*
* Request/User many-to-one relationship
*/
public function user()
{
return $this->belongsTo('App\User');
}
/*
* Belongs to current User scope
*/
public function scopeMine($query)
{
return $query->where('user_id', Auth::id());
}
}
Try this and see if it works.
public function store(QuoteRequest $request)
{
// This method will only get fired if QuoteRequest passes
$quote = new Quote;
$quote->fill($request->all());
$quote->user_id = Auth::id();
$quote->save();
echo 'Job done';
}
The create function is considered mass assignment and is thusly affected by $fillable / $guarded. It's also a static function, so $quote->create() is making an entirely new Eloquent instance - that's why your manually assigned user_id is lost.
You can use Model::unguard() to temporarily turn off the protection.