Laravel Nova Actions BelongsTo field not working - laravel

I have this simple action:
/**
* Perform the action on the given models.
*
* #param \Laravel\Nova\Fields\ActionFields $fields
* #param \Illuminate\Support\Collection $models
* #return mixed
*/
public function handle(ActionFields $fields, Collection $models)
{
foreach ($models as $model) {
$model->update([
'user_id' => $fields->user
]);
}
}
/**
* Get the fields available on the action.
*
* #return array
*/
public function fields()
{
return [
BelongsTo::make('User', 'user', User::class),
];
}
At first, it seems fine, but when I select User from BelongsTo relation and try to save exception is throwing:
Argument 1 passed to Laravel\Nova\Fields\BelongsTo::getRelationForeignKeyName() must be an instance of Illuminate\Database\Eloquent\Relations\Relation, instance of Illuminate\Support\Fluent given, called in /Users/rd/Sites/bns-crm/vendor/laravel/nova/src/Fields/BelongsTo.php on line 212

Yes i know i'm late but - here's a solution for this:
Use a Select-Field instead of BelongsTo and Pluck your options to build Key-Value pairs:
public function fields()
{
return [
Select::make('debitor')->options(\App\Models\Debitor::pluck('Name', 'id'))
];
}
Then in the handle you should geht the ids in $fields:
public function handle(ActionFields $fields, Collection $models) {
Log::info($fields);
}

Maybe I'm late, but, for the ones like me wanting to use the BelongsTo searchable field because the model they want to search in contains too much records to pack them in a normal Select field here is the solution I found:
Create a class in App\Nova\Fields with this code:
<?php
namespace App\Nova\Fields;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Http\Requests\NovaRequest;
class BelongsToForActions extends BelongsTo
{
public function fillForAction(NovaRequest $request, $model)
{
$attribute = $this->attribute;
if ($request->exists($attribute)) {
$value = $request[ $attribute ];
$model->{$attribute} = $this->isNullValue($value) ? null : $value;
}
}
}
Then use it like you would use a normal BelongsTo field. Just remember to fill the 3 arguments on the make, so, for example:
BelongsToForActions::make('User', 'relation', \App\Nova\User::class)->searchable()
Remember that 'relation' must exist.

Check your namespaces. Did you imported right class? User class must be resource class
public function fields()
{
return [
BelongsTo::make('User', 'user', User::class),
];
}

I actually fixed this by mocking the key value pair used in this relationship.
First I build an array with the ID column as key and the name column as value.
$clients = Client::all()
->keyBy('id')
->map(fn($client): string => $client['name'])
->toArray();
Then I use the Select nova field to display it.
Select::make('Klant', 'client')
->searchable()
->options($clients)
->rules('required'),

Related

Laravel Scout map in toSearchableArray relationship fields

Is it possible to map in toSearchableArray relationship fields as well. What I mean, having User model I try to search in related model fields as well like;
/**
* Get the indexable data array for the model.
*
* #return array
*/
public function toSearchableArray()
{
return [
'name' => $this->name,
'plz' => $this->account->zip,
];
}
/**
* #return HasOne
*/
public function account(): HasOne
{
return $this->hasOne(Member::class);
}
In controller searching after results
if($request->has('s')) {
$founds = User::search($request->get('s'))->get();
}
will throw Attempt to read property "zip" on null
I do not really find any infos in documentation related to this question
I do have two ways to do it of which I consider one of them as crude.
Method 1:
Here's an example implementation where you're searching all of one model and then a relationship (accounts)
public function toSearchableArray()
{
$array = $this->toArray();
$array = $this->transform($array);
$array['country'] = $this->countries->map(function ($data) {
return $data['name'] ?? '';
})->toArray();
return $array;
}
Method 2:
public function toSearchableArray()
{
$array = $this->toArray();
// Customize array...
$array = [
'user_name' => $this->user_name,
'country' => $this->getCountryNameById($this->country_id),
...
];
return $array;
}
where relationship is defined in helpers or you can make a separate trait and import it in model with method.
Here relationship for country is defined within the model as
//Get Country name By id
function getCountryNameById($id) {
$country = \App\Country::select('name')->find($id);
return $country->name ?? '';
}

Using Eloquent Relationships

Laravel 8
I have a couple models using eloquent relationships.
A User which can have many blog posts
public function blogPosts() {
return $this->hasMany(BlogPost::class);
}
And a BlogPost which belongs to the author
public function author() {
return $this->belongsTo(User::class);
}
My issue is that when I attempt to use one of these relationships, I am returned an empty object.
We can focus on the belongsTo() relationship.
I am attempting to use this relationship here:
class BlogPostResourceCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return $this->collection->map(function($post) {
return [
'id' => $post->id,
'author' => $post->author(),
'title' => $post->title,
];
});
}
}
Why am I getting an empty object for author?
Edit: the blog post table does have a foreign key user id column.
All the problem is laravel by default get the relation from the method name and you add custom names so to fix it you need to add the second argument for each relation like this
User Model
public function blogPosts() {
return $this->hasMany(BlogPost::class, 'user_id');
}
BlogPost
public function author() {
return $this->belongsTo(User::class, 'user_id');
}
At the end in your BlogPostResourceCollection you need to access the author data all you need is to write ->author not ->author()
for more info, you could check the One-To-Many relation here

Laravel - Collection::delete method does not exist

I am trying to test the boot() static::deleting method, which should fire when a model is deleted through Eloquent.
The command in tinker App\User::find(6)->delete(); returns a 'method [...]Collection::delete does not exist'.
If I try to use App\User::where('id', 6)->delete(); then the static::deleting method does not get triggered since Eloquent is not loaded. If I load Eloquent with ->first() then I get the same error that states method does not exist.
Here is the entire user model
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
public function profile() {
return $this->hasOne(Profile::class);
}
public function posts() {
return $this->hasMany(Post::class);
}
public function tempUploads() {
return $this->hasMany(TempUploads::class);
}
protected static function boot() {
parent::boot();
static::created(function ($user) {
$user->profile()->create(['id' => $user->username, 'avatar' => '/storage/avatars/edit-profile.png']);
mkdir(public_path() . "/storage/images/" . $user->username , 0755);
// $data = [
// 'user_id' => $user->username
// ];
// Mail::to($user->email)->send(new WelcomeMail($data));
});
static::deleting(function ($user) {
$user->posts->delete();
if ($user->profile->avatar != '/storage/avatars/edit-profile.png') {
if ($user->profile->cover != NULL && $user->profile->cover != '') {
$oldAvatar = $_SERVER['DOCUMENT_ROOT'] . $user->profile->avatar;
$oldCover = $_SERVER['DOCUMENT_ROOT'] . $user->profile->cover;
if (is_file($oldAvatar) && is_file($oldCover)) {
unlink($oldAvatar);
unlink($oldCover);
} else {
die("Грешка при изтриване на стария файл. File does not exist in profile deleting method.");
}
}
}
$user->profile->delete();
});
}
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'username', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
I have spent hours now looking through google for possible solutions but nothing has yet.
How should I properly delete a User model while triggering the boot deleting method ?
In your deleting listener you are trying to delete something else, which is a Collection which is causing the error.
$user->posts is a relationship to Posts which is a plural which is a hasMany relationship (most likely) so it returns a Collection always. Collections do not have a delete method. You will have to iterate through the collection and call delete on each Post
// calling `delete()` on a Collection not a Model
// will throw the error you see
$user->posts->delete();
// iterate through the Collection
foreach ($user->posts as $post) {
$post->delete();
}
Side Note: you can not do any action in bulk with Models and queries and have the events be fired. All Model events are based on single instances of the Models. A direct query bypasses the Model.
You can optimise lagbox's answer by using only one query to delete all of the posts. In his example he's executing a delete query for every post attached to the user.
For a single delete query either use the query builder of the relationship directly:
$user->posts()->delete();
or use the pluck method of the collection and a separate query:
Post::where('id', $user->posts->pluck('id'))->delete();
You can use higher order messages as well:
$user->posts->each->delete();
$user->posts->map->delete()
I used this in my Controller File to delete the Database Entry:
public function destroy(Item $id) {
$id->destroy($id->id);
//return view('inv.delete', compact('id'));
return redirect('/inv');
}
$user->posts()->delete() will work
$user->posts->delete() will not work
Because $user->posts() is a query , not a collection

Using HasMany relationship when Model and table have different names

Like the title says, my Field model and application_fields table have different names. I already set the table name on the Field model to the correct table name. A set the I have another model, Application, and its controller, ApplicationController. In the index method of the ApplicationController, I am trying to retrieve the application and its fields. The Field model has a belongsTo relationship to Application, and the Application model has a hasMany relationship to Field.
The code seems to work, except it pulls the values for the rows from the applications table. So if the applications table has the following rows:
1 'AppOne' 'An App'
2 'AppTwo' 'Another App'
3 'AppThree' 'A third App'
AppOne, AppTwo, and AppThree are returned as the values for the fields.
Can someone give me some advice?
Here is the code for them( I only copied what was related to the issue, not the entire file):
Application model:
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'application_fields';
public function fields()
{
return $this->hasMany('App\Field');
}
Field model:
public function application()
{
return $this->belongsTo('App\Application');
}
ApplicationController:
/**
* Display the dashboard for a single application.
*
* #param String $id
* #return View
*/
public function show( $id )
{
$app = Application::where('id', $id)->first();
$fields = $app::with('application_fields')->get();
return view('applications.show', [
'application' => $app,
'fields' => $fields,
]);
}
View( I stripped out the HTML):
{{ $application->name }}
{{ $application->description }}
#foreach($fields as $field)
{{ $field->name}}
#endforeach
I would appreciate any advice on what I am doing wrong. Thanks
your show method is wrong, eager loading does not work that way :)
public function show( $id ) {
$app = Application::with('fields')->find($id);
$fields = $app->fields;
return view('applications.show', [
'application' => $app,
'fields' => $fields,
]);
}
you can also use model Binding if your route is like that : applications/{app}
public function show(Application $app) {
$fields = $app->fields;
return view('applications.show', [
'application' => $app,
'fields' => $fields,
]);
}

Laravel - Filter a collection by a dynamic field

The model has 3 fields that are attributes of the model and the 4th field is a dynamic field that is called "fullTitle"
[
{
"id": 1,
"hours": 2000,
"car_id": 3,
"fullTitle": "Test",
}
}
<?php
namespace App;
use App\Car;
class VenderModel extends Model
{
use TranslationTrait;
protected $guarded = ['*'];
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = [
'fullTitle'
];
/**
* Accessor for dynamic property fullTitle to use as selector name.
* #param string $locale
* #return null|string|string[]
*/
public function getFullTitleAttribute($locale = 'es')
{
if (null === $this->hasTranslation()) {
return trans('errors.No name');
}
return preg_replace('/\s{2,}/', ' ', $this->code.' '. $this->translationOrFirst($locale)->translation);
}
}
public function index(Request $request): JsonResponse
{
$limit = $request->input('limit', 25);
$title = $request->input('title');
$query = VenderModel::all();
$query->when($request->query('title'), function ($q) use ($title) {
return $q->where('fullTitle', 'like', '%' . $title . '%');
});
return response()->json($query, 200);
}
What I'm trying to do is pick up the elements with the fullTitle but doesnt work
You cannot ask your database to search for something that does not actually exist in the table.
Custom attributes are only called when you ask for the derived property. They cannot be used in the query.
Alternatively, you can grab ALL the rows in memory and then filter it with PHP, but it's not a good way.
Also you can think about storing translation of specific property in related table and then search in database by relation.
$collection = VenderModel::get();
$collection->filter(function($item) use($title) {
return preg_match('/.*'.$title.'.*/', $item->fullTitle) !== false;
});
return response()->json($collection, 200);
I think this should solve it. You can't use query on a field that is not actually field in your table, but you should be able to filter it as a collection

Resources