I am working with field MorphTo and I try to make conditions for the resources.
For example I have 4 resources:
Accounts
PaymentMethodCreditCard
PaymentMethodBankAccount
Transactions
Every Account can add as many Payment Methods as he wants.
And in the transaction I work with MorphTo to select the Payment Method that the account selected.
My problem starts when I try to create a transaction from Nova and get a list of all the Payment Methods in the db without any relation to the account.
My ideal idea was like that:
MorphTo::make('Transactions')
->withoutTrashed()
->dependsOn('Account', function (MorphTo $field, NovaRequest $request, FormData $formData) {
if(isset($formData->Account)){
$field->types([
PaymentMethodCreditCard::class => fn($q)=>$q->where('account_id', $formData->Account),
PaymentMethodBankAccount::class => fn($q)=>$q->where('account_id', $formData->Account),
]);
}
}),
But of course it will not work, Someone has any idea how I can add conditions to the resource?
I'll give you two answers. Since the question does not exactly clarify whether the Account value is changable during the edit process or it's predefined.
Account value does not change.
If the account value does not change after the resource is loaded, then the solution you are looking for is Relatable Filtering
public static function relatablePaymentMethods(NovaRequest $request, $query)
{
$resource = $request->findResourceOrFail();
return $query->where('account_id', $resource->Account);
}
Account value can change
If the account value can change, then you'll need to create your own "Morph To" behavior using Select and Hidden fields.
With Select field, you can utilize the dependsOn and options functions, to change the query results when the Account changes.
Related
I want to set the default value of a resource field to the authenticated user's id. I have a model called Note which has a one to many relationship with Game and User.
User hasMany Note
Game hasMany Note
Note belongsTo User
Note belongsTo Game
In Laravel Nova my fields looks like this for the note
ID::make()->sortable(),
Text::make('Note', 'note')->onlyOnIndex(),
Textarea::make('Note', 'note')->alwaysShow(),
BelongsTo::make('Game', 'game')->hideWhenCreating()->hideWhenUpdating(),
BelongsTo::make('Created By', 'user', 'App\Nova\User')->hideWhenCreating()->hideWhenUpdating(),
DateTime::make('Created At', 'created_at')->hideWhenCreating(),
DateTime::make('Updated At', 'updated_at')->hideWhenCreating(),
Because I am referencing the Note on the Game Nova resource, when I create a Note, the game_id column is populated correctly. But, I want the user_id column to be the value of the authenticated user. It does not seem to work like this, how would I accomplish it?
If I understand correctly from the line BelongsTo::make('Created By', 'user', 'App\Nova\User')->hideWhenCreating()->hideWhenUpdating() you're trying to set a default value for the column without showing the field on the form?
I don't think this is possible in this way. As soon as you use the hide functions the fields aren't rendered and will never be passed along with the request. I tried this, and the user_id field was never sent with the request.
I think there are two ways to do this:
Show the field in the form and set the default value using the metadata (and perhaps making the field read-only for good measure).
BelongsTo::make('Created By', 'user', 'App\Nova\User')->withMeta([
"belongsToId" => auth()->user()->id,
])
See this part of the Nova docs
Or use the Eloquent creating event. The following will go in your Note model.
public static function boot()
{
parent::boot();
static::creating(function($note)
{
$note->user_id = auth()->user()->id;
});
}
Granted, the above method is a bit simple. You'd be better off using proper event listeners.
Sidenote: from an architectural point of view, I'd go with option 2. Setting a default value without getting the end-user involved sounds like a job for the Eloquent model, not for a Nova form.
You can use a method resolveUsing(). An example
<?php
//...
Select::make('My Select', 'my_custom_name')
->options(['a' => 'a', 'b' => 'b', 'c' => 'c'])
->resolveUsing(function ($value, $resource, $attribute) {
// $value = model attribute value
// $attribute = 'my_custom_name'
return 'b';
});
I have a resource of type Process_type.
It has a BelongsToMany relationship with Process_event
It also stores a default Process_event as a BelongsTo relationship - I want the possible options for this to be limited to those in the BelongsToMany relationship, not the entire set of process events in the database.
I was expecting to be able to limit the options on a BelongsTo::make() field, but can't find a way to do this. So I'm falling back to using a Select field, and passing in the options via the options() method.
This is how my fields method looks currently
public function fields(Request $request)
{
$options = [];
$options['process_events'] = \App\Process_event::all()->pluck('title','id');
return [
ID::make()->sortable(),
Text::make('Title')->sortable(),
Select::make('Process Event')->options($options['process_events']),
BelongsToMany::make('Process Events', 'process_events', Process_event::class)
];
}
As it stands, I still have all process events as options. How can I access the current entity via the $request object passed in, so I can look up the ->process_events() on the model. I can't find any documentation on this.
Is this possible? If not, what is a better way to do this?
I have a view about holidays where a user uses a form to choose a place to travel and a hotel. It has two models: HolidaysPlaces and HolidaysHotels.
The user have to fill the form in this order using the view:
The user completes the fields called Place and City (related with the HolidaysPlaces model).
The user checked a checkbox if he/she wants to choose a hotel. It able a field called Hotel (related with HolidaysHotels model).
The user completes that field.
The user press a Create button.
The controller receives and saves both models.
But the problem is when the user doesn't select the checkbox (number 2 of the list): The Hotel fieldis still required (with the red asterisk as defined in its model file). So the Create button doesn't work in this case.
How can I disabled the required feature?
Add a scenario for this case in your HolidaysHotels model, and include only the fields that you want checked.
Example: If you have 3 fields name, date and age that are required, create a scenario for two only, and set the scenario in the controller. Only those two fields will be checked.
In model:
public function scenarios(){
$scenarios = parent::scenarios();
$scenarios['create'] = ['name', 'date'];
return $scenarios;
}
In controller:
$holiday = new HolidayHotels();
$holiday->scenario = 'create';
To know more about scenarios: http://www.yiiframework.com/doc-2.0/guide-structure-models.html#scenarios
You can add some condition based validation in your model rules. Here is the snippet for both client and server validation. You can many conditions inside the function block.
['field-1', 'required', 'when' => function ($model) {
return $model->check_box == '1';
}, 'whenClient' => "function (attribute, value) {
return $('#checkbox-id').is(':checked') ';
}"],
The easiest way to solve it is to send the model with empty strings. Then the controller checks if the strings are empty. If so, the model is not saved. Else, it is saved.
It was the only way that works for me.
I'm rather new to Laravel 4 and can't seem to find the right answer, maybe you can help:
A User in our application can have many Accounts and all data is related to an Account, not a User. The account the User is currently logged into is defined by a subdomain, i.e. accountname.mydomain.com.
We added a method account() to our User model:
/**
* Get the account the user is currently logged in to
*/
public function account()
{
$server = explode('.', Request::server('HTTP_HOST'));
$subdomain = $server[0];
return Account::where('subdomain', $subdomain)->first();
}
The problem is that there is always an extra query when we now use something like this in our view or controller:
Auth::user()->account()->accountname
When we want to get "Products" related to the account, we could use:
$products = Product::where('account_id', Auth::user()->account()->id)->get();
And yet again an extra query...
Somehow we need to extend the Auth::user() object, so that the account data is always in there... or perhaps we could create a new Auth::account() object, and get the data there..
What's the best solution for this?
Thanks in advance
Just set it to a session variable. This way, you can check that session variable before you make the database call to see if you already have it available.
Or instead of using ->get(), you can use ->remember($minutes) where $minutes is the amount of time you wish to keep the results of the query cached.
You should take a look at Eloquent relationships : http://laravel.com/docs/eloquent#relationships
It provides simple ways to get the account of a user and his products. You said that a user can have many accounts but you used a first() in your function I used a hasOne here.
Using Eloquent relationships you can write in your User model:
<?php
public function account()
{
// I assume here 'username' is the local key for your User model
return $this->hasOne('Account', 'subdomain', 'username');
}
public function products()
{
// You should really have a user_id in your User Model
// so that you will not have to use information from the
// user's account
return $this->hasMany('Product', 'account_id', 'user_id');
}
You should define the belongsTo in your Account model and Product model.
With Eager Loading you will not run a lot of SQL queries : http://laravel.com/docs/eloquent#eager-loading
You will be able to use something like
$users = User::with('account', 'products')->get();
To get all users with their account and products.
I think this is a good example for the purpose of Repositories.
You shouldn't query the (involved) models directly but wrap them up into a ProductRepository (or Repositories in general) that handles all the queries.
For instance:
<?php
class ProductRepository
{
protected $accountId;
public function __construct($accountId)
{
$this->accountId = $accountId;
}
public function all()
{
return Product::where('account_id', $this->accountId)->get();
}
}
//now bind it to the app container to make it globaly available
App::bind('ProductRepository', function() {
return new ProductRepository(Auth::user()->account()->id);
});
// and whenever you need it:
$productRepository = App::make('ProductRepository');
$userProducts = $productRepository->all();
You could group the relevant routes and apply a filter on them in order to bind it on each request so the account-id would be queried only once per repository instance and not on every single query.
Scopes could also be interesting in this scenario:
// app/models/Product.php
public function scopeCurrentAccount($query)
{
return $query->where('account_id', Auth::user()->account()->id);
}
Now you could simply call
$products = Product::currentAccount()->get();
I want to authorize a resource in my Laravel application, so that users can only change a foreign key id only to a value of an item, that belongs to the same organization as the user.
As an example:
A user belongs to an organization => user.organization_id
A organization has many item objects => item.organization_id
Now the user wants to update a resource that has some columns and four item columns item1,item2,item3,item4.
The user should only be allowed to set one of these 4 items with an id of an item that belongs to his organization.
I created a Controller and Policy for this case which contains the function
// Controller function
public function update(Request $request, ItemToUpdate $item)
{
//
}
// Policy function
public function update(User $user, ItemToUpdate $item)
{
//return true;
}
Now my question is: What is the most performant way to check if the user is allowed to update the resource with the ids, that are sent in the request.
Of course I could make a DB request for every item and check if item.organization_id === user.organization_id, but is this really the most performant and efficient way?
Thanks
In my opinion the best way is to create a custom Request and inside authorize method do your checks