Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] trashed users aren't showing up in select2_from_ajax field #1252

Open
prescriptionlifeline opened this issue Dec 9, 2024 · 9 comments
Assignees

Comments

@prescriptionlifeline
Copy link

prescriptionlifeline commented Dec 9, 2024

Bug report

What I did

In my User CRUD I have this:

        CRUD::field([
            'name' => 'spouse',
            'label' => 'Spouse',
            'type' => 'select2_from_ajax',
            'entity' => 'spouse',
            'attribute' => 'lastNameFirstName',
            'data_source' => backpack_url('user/fetch/spouse'),
            'method' => 'POST',
        ]);

Here' my FetchOperation:

    protected function fetchSpouse()
    {
        return $this->fetch([
            'model' => User::class, // required
            'searchable_attributes' => ['lastNameFirstName'],
            'paginate' => 10, // items to show per page
            'searchOperator' => 'LIKE',
            'query' => function($model) {
                //return $model->withTrashed();
                return $model->withTrashed()->where(function($query) {
                    $query->whereNull('deleted_at')
                          ->orWhereNotNull('deleted_at');
                });
            } // to filter the results that are returned
        ]);
    }

And here's my spouse method in my User model:

    public function spouse()
    {
        return $this->belongsTo(User::class, 'spouseID')->withTrashed();
    }

What I expected to happen

With all of this I can add trashed spouses to non trashed users without issue. My expectation is that the newly added members would then show up when I refresh the page.

If I do User::findOrFail(...)->spouse in php artisan tinker that returns the spouse even if the spouse isn't trashed. I just can't get the select2_from_ajax to behave the same way as php artisan tinker.

What happened

After adding a trashed spouse to a non-trashed user and then refreshing the trashed spouse does not show up.

What I've already tried to fix it

I posted #1139 but I think at this point this prob needs to be seen as a bug in Laravel Backpack.

Is it a bug in the latest version of Backpack?

After I run composer update backpack/crud the bug... is it still there?

Yes

Backpack, Laravel, PHP, DB version

When I run php artisan backpack:version the output is:

### PHP VERSION:
8.3.3

### PHP EXTENSIONS:
Core, bcmath, calendar, ctype, date, filter, hash, iconv, json, SPL, pcre, random, readline, Reflection, session, standard, mysqlnd, tokenizer, zlib, libxml, dom, PDO, openssl, SimpleXML, xml, xmlreader, xmlwriter, curl, gmp, pdo_mysql, mysqli, fileinfo, sqlite3, pdo_sqlite, mbstring, Phar, sodium

### LARAVEL VERSION:
10.48.20.0

### BACKPACK PACKAGE VERSIONS:
backpack/basset: 1.3.5
backpack/crud: 6.7.43
backpack/filemanager: 3.0.8
backpack/generators: v4.0.5
backpack/pro: 2.2.14
backpack/theme-tabler: 1.2.11
Copy link

Based on the information provided in your bug report, it appears that you're experiencing an issue with trashed users not showing up in the select2_from_ajax field, even though you've configured the relationship and fetch operation to include trashed records. This seems to be a potential bug in Backpack. Let's analyze the problem and suggest a possible workaround.

The issue you're facing is similar to the one discussed in this GitHub issue, where there were problems with select2_from_ajax and the FetchOperation.

Here's what we can deduce from your setup:

  1. You've correctly set up the spouse relationship in your User model to include trashed records using withTrashed().
  2. Your fetchSpouse method in the FetchOperation is also correctly using withTrashed().
  3. The select2_from_ajax field is configured to use this fetch operation.

However, the trashed spouses are still not showing up after being added and the page is refreshed. This behavior is inconsistent with what you're seeing in php artisan tinker, which suggests a potential issue with how Backpack is handling the fetched data for the select2_from_ajax field.

As a workaround, you could try modifying your fetchSpouse method to explicitly include trashed records in the query. Here's a suggestion:

protected function fetchSpouse()
{
    return $this->fetch([
        'model' => User::class,
        'searchable_attributes' => ['lastNameFirstName'],
        'paginate' => 10,
        'searchOperator' => 'LIKE',
        'query' => function($model) {
            return $model->withTrashed()->orWhereNotNull('deleted_at');
        }
    ]);
}

This modification explicitly includes trashed records by adding orWhereNotNull('deleted_at') to the query.

If this workaround doesn't resolve the issue, it would be advisable to report this as a bug to the Backpack team, as it seems to be an inconsistency in how trashed records are handled in the select2_from_ajax field.

For further assistance or to report this issue, you can use the Backpack community forum or open a new issue on the Backpack CRUD GitHub repository.

ℹ️ Please note our AI Bot does not reply to follow-ups, this will be the only AI answer in this thread. If the AI helped you find the answer, please mark it as answered or write details below so it can learn. Then close the conversation. If not, please give more information for when a human reads this. ℹ️

Relevant sources:

Github Issue (closed): [Bug] select2_from_ajax with FetchOperation doesn't work
exclude user from FetchOperation
weird error in middleware when using select2_from_ajax with FetchOperation
Github Issue (closed): [Bug] select2_ajax filter and select2_from_ajax field pagination not working
Github Issue (closed): [Bug] select2_from_ajax doesn't ever call the function to fetch data
Github Issue (closed): [Bug] Search is not working correctly
Github Issue (closed): select2_from_ajax with entity false not working
Github Issue (closed): [4.0.58] select2_ajax filter breaks "Remove filters" button (again)
Github Issue (closed): [Bug] Select2 keep showing "Searching" in ajax with pivot (n-n)
Updated Select2 to the latest version in all fields that use Select2
Github Issue (closed): [Bug] select2_ajax filter and select2_from_ajax field pagination not working AGAIN

@jcastroa87
Copy link
Member

Hello @prescriptionlifeline if you see the data save on table, there is the spouse value?

Cheers.

@jcastroa87
Copy link
Member

Hello @prescriptionlifeline, I found the issue here.

To solve, you can override the select2_from_ajax field, following these steps

Then replace this line:

$item = $connected_entity->find($old_value);

With:

$item = $connected_entity->withTrashed()->find($old_value);

That's should work.

Cheers.

@prescriptionlifeline
Copy link
Author

I'll try that out after I get to work - thanks!

@prescriptionlifeline
Copy link
Author

I wasn't able to get this working.

The only file that has $item = $connected_entity->find($old_value); in it is vendor/pro/resources/views/fields2/select2_from_ajax.blade.php. As a sanity check I first tried to modify that file as you suggested. After making said changes I did php artisan cache:clear and then reloaded the page and the trashed user was still not showing up.

I then renamed that file to zzz.select2_from_ajax.blade.php and did php artisan cache:clear and the page loads without issue, like it's not even looking at that file when it's building the page.

I then similarly renamed every select2* file and did php artisan cache:clear and, once again, the page loaded just fine...

@jcastroa87
Copy link
Member

Hi @prescriptionlifeline

There are many errors here.

First, there is no "fields2" folder in vendor, it is just "fields".
Then you didn't follow instructions on our docs, you should not modify file in vendor, need to publish them:

php artisan backpack:field --from=select2_from_ajax

I try on my side and is working, here are the examples:

I create Tag (Spouses) and Category (User), both use Soft Deletes.

So in "Category" Controller I add this:

...
use \Backpack\CRUD\app\Http\Controllers\Operations\FetchOperation;
...

protected function setupCreateOperation()
    {
        ...
        CRUD::addField([
            'name' => 'tag_id',
            'label' => 'Tag',
            'type' => 'select2_from_ajax',
            'entity' => 'tag',
            'attribute' => 'name',
            'data_source' => backpack_url('category/fetch/tag'),
            'placeholder' => 'Select a tag',
            'minimum_input_length' => 0,
            'method' => 'POST',
        ]);
    }

...

protected function fetchTag()
    {
        return $this->fetch([
            'model' => \App\Models\Tag::class,
            'searchable_attributes' => ['name'],
            'paginate' => 10,
            'searchOperator' => 'LIKE',
            'query' => function($model) {
                return $model->withTrashed();
            }
        ]);
    }

In "Category" Model:

...
public function tag()
    {
        return $this->belongsTo(Tag::class)->withTrashed();
    }

I publish "select2_from_ajax"

php artisan backpack:field --from=select2_from_ajax

And replace

$item = $connected_entity->find($old_value);

With

$item = $connected_entity->withTrashed()->find($old_value);

Now here are the screenshots:

Item "test2" from Tag is deleted
image

In Category i still can see "test2"
image

I save the Category
image

I see the Category and deleted Tag on list
image

When I edit, there still the deleted Tag
image

Please be careful and follow all the steps in our docs, you should not just replace the files in vendor and if you want to customize the Trashed Operation need to follow the steps in these docs.

Cheers.

@prescriptionlifeline
Copy link
Author

prescriptionlifeline commented Dec 12, 2024

First, there is no "fields2" folder in vendor, it is just "fields".

I copied the fields folder to fields2 before I made my changes. I guess I copy / pasted the wrong path when I made my post. I guess everything I've ever done is now forever tainted by that and I should just throw in the towel and start flipping burgers at McDonalds 🙄

Then you didn't follow instructions on our docs, you should not modify file in vendor, need to publish them****

As I said in the post you're replying to, I changed that file "As a sanity check". Allow me to give another example of the concept of a sanity check.

One time, many years ago, I thought I was on the prod server (this was before Docker and before Composer). I was trying to make a change directly to that server and nothing I did was showing up. I then decided, as a sanity test, I just wanted to see if I could affect any change on the prod server, even if it wasn't the one I set out to make. So then I started renaming directories. I renamed the parent directory and nothing. I renamed the includes/ directory and the website still worked. I then renamed the DocumentRoot directory that the httpd.conf file was pointing to and the website still worked. Turns out I was connected to a dev server instead of the prod server that I thought I was connected.

And you're right - I shouldn't have to modify the vendor directory. That wasn't the point of my tests. As observed in another post, not everything can be published. Sometimes you just need to straight up copy files. I was modifying the file in the vendor/ directory , as a sanity check, because I wanted to see if that worked before trying and possibly failing to publish [1]. Like if publishing didn't work (which, as has already been established, it doesn't, always) I could then manually copy it but then what if I got the directory I was manually copying it to wrong? Like if I'm trying to debug why shit isn't working I'd just assume reduce the number of sources for error rather then maximize them.

[1] For example, https://backpackforlaravel.com/docs/6.x/crud-fields#overwriting-default-field-types-1 mentions being able to publish to
/resources/views/vendor/backpack/crud/fields but not necessarily to /resources/views/vendor/backpack/pro/fields

Anyway, I'll take a look at the rest of your post later today. The thoroughness of it does look encouraging - the less I have to fill in the gaps the less likely I am to encounter errors.

@prescriptionlifeline
Copy link
Author

I figured out the issue.

So in the setupCreateOperation() method of my UserCrudController I had this:

        CRUD::field([
            'name' => 'spouse',
            'label' => 'Spouse',
            'type' => 'select2_from_ajax',
            'entity' => 'spouse',
            'attribute' => 'name',
            'data_source' => backpack_url('user/fetch/spouse'),
            'method' => 'POST',
            'hint' => 'Search by last name <i>or</i> first name. Adding a spouse to this account will also result in this person being added as a spouse to the spouse\'s account.',
        ]);

In the setupUpdateOperation() method I was calling $this->setupCreateOperation() and was then doing this:

        CRUD::field([
            'name' => 'spouse',
            'data_source' => backpack_url('user/fetch/spouse?exclude=' . $this->crud->getCurrentEntry()->id),
            'hint' => 'Search by last name <i>or</i> first name. Adding a spouse to this account will also result in this person being added as a spouse to the spouse\'s account.',
        ]);

If it weren't for the data_source bit, which was changed per #1131, it wouldn't be necessary to do any additional configuring of the spouse field in setupUpdateOperation().

Anyway, it turns out that adding 'type' => 'select2_from_ajax', to the spouse field in setupUpdateOperation() does the trick.

Like I had been assuming that array keys left undefined would be inherited from the CRUD::field() calls in setupCreateOperation() but I guess I was wrong. I mean, sometimes, setupUpdateOperation() seemed to inherit from the CRUD::field() calls in setupCreateOperation() but sometimes it didn't. For example, I also have this in setupCreateOperation():

        CRUD::field([
            'name' => 'password',
            'type' => 'password',
            'attributes' => ['disabled' => 'disabled'],
            'hint' => 'This field is disabled because <a tabindex="-1" href="/admin/setting/0/edit#new-user-email" target="_blank">an email with a randomly generated password</a> (eight character alpha-numeric password) will be sent out when the Save button is clicked (unless you uncheck the checkbox at the bottom of this page)'
        ]);

...and then I have this in setupUpdateOperation():

        CRUD::field([
            'name' => 'password',
            'attributes' => ['id' => 'password'],
            'hint' => "<span id='passwordHint'>$hint</span>",
        ]);

Like even though I'm not telling Laravel Backpack that the type is password in the setupUpdateOperation call it just knows. I assumed that that was due to it falling back on previously submitted option but I guess that's not the case. Like I just tried changing type to text for the password field in setupCreateOperation and that change did not get reflected in setupUpdateOperation. So I guess when the type parameter isn't present it infers it and it may not always infer it correctly.

TLDR I was making some unfounded assumptions about how the whole thing worked and your initial reply did contain the answer.

I still stand by the sanity checks I performed but that's neither here nor there.

Thank you for your time.

@pxpm
Copy link

pxpm commented Dec 13, 2024

This is worth investigating.
In my opinion you are right, attributes should be inherited from the first field definition. If you have the field in setupCreateOperation and you do $this->setupCreateOperation() in your update operation is totally expected that the exact same field definitions are set, and you should be able to change only the things you need.

I've re-opened this discussion so that it can be investigated a bit more 👍

Thanks for your time and for providing such a good explanation of the issue 🙏

Cheers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

No branches or pull requests

3 participants