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

[9.x] Scout sensitive attributes #491

Merged
merged 14 commits into from
Jul 7, 2021

Conversation

juampi92
Copy link
Contributor

@juampi92 juampi92 commented Jul 1, 2021

This small addition will prevent firing engine updates if sensitive attributes were not changed.

Problem

We noticed in our project that we were performing many small updates on attributes that are irrelevant for the search, yet every change was still firing an Algolia update job.

Even though update events are free in Algolia, our models have a toSearchableArray structure that requires many relationships to be loaded, so we have a lot to gain from not firing a job when possible.

Solution

This PR lets developers define the method scoutSensitiveAttributes to return an array of the sensitive attributes that only when changed, the engine should be updated.

Deleted and restored events

When restoring/soft-deleting a model, the model itself doesn't have any dirty attributes, so the sensitive attributes have to be ignored. For this scenario, we use the forceSaving (similar to the SoftDeletes forceDelete logic) where we skip the sensitive attributes check entirely.

Notes

I added a new test to test the soft delete flow in the ModelObserver, which wasn't being tested before.

Defining a list of sensitive attributes will prevent scout firing an engine update unless they are updated.
test that ModelObserver doesn't make searchable when searchShouldUpdate is false
On some scenarios (like deleting and restoring), we don't want to check for sensitive attributes, we simply want to update the model.
For that, we use forceSaved instead of saved.
Instead of always getting this value from the `config()`, we save it as a class property using the Config facade.

This allows us to mock the value of the config and test the SoftDeletes scenario
@taylorotwell
Copy link
Member

Is it possible to take an approach with a lighter touch that just gives you the hook for searchShouldUpdate and within that method you can do whatever you want. That way we don't have to introduce an entirely new concept of "Scout sensitive attributes".

@juampi92
Copy link
Contributor Author

juampi92 commented Jul 2, 2021

What about something like this?

    /**
     * Given a list of updated attribute keys, this method determines
     * if we should perform a search engine update or not.
     *
     * @param  array  $updatedAttributeKeys
     * @return bool
     */
    public function searchShouldUpdate(array $updatedAttributeKeys): bool
    {
        return true;
    }

And then to solve the problem of the sensitive attributes, this would be an example overwriting the hook:

    /**
     * Given a list of updated attribute keys, this method determines
     * if we should perform an engine update or not.
     *
     * @param  array  $updatedAttributeKeys
     * @return bool
     */
    public function searchShouldUpdate(array $updatedAttributeKeys): bool
    {
        $sensitiveAttributeKeys = ['published_at', 'title', 'content'];

        return count(array_intersect($updatedAttributeKeys, $sensitiveAttributeKeys)) > 0;
    }

(or you can even do it with collections as I did on the implementation)

Observations

I understand that being more generic and extensible allows devs to be creative, and this hook might help solve other problems too, which is great.

But I also can't stop thinking that it's up to the dev now to figure out how to use this method for something.
Having an out-of-the-box feature that prevents unnecessary updates based on which attributes were modified is something that won't make sense in all scenarios, but now devs will be aware of this problem and know that the package provides a solution for it (that is entirely optional).

I guess this will make more sense when writing the documentation. We could easily tell the dev that they can use searchShouldUpdate for this exact purpose.

@taylorotwell
Copy link
Member

We can document this use case as a possible use of this method in the documentation so that it spurs the idea in their head.

@juampi92
Copy link
Contributor Author

juampi92 commented Jul 4, 2021

Do you think that we should send the array $updatedAttributeKeys or let them do array_keys($model->getDirty()) by themselves ?

Perhaps, by sending the updatedAttributeKeys, we're forcing them into using the method a specific way, but then if they only use it to check which keys are being updated, it could save a bit of repetitive code

(I decided to not send the updated attributes, let me know what you think. I can add them if you consider them useful)

In order to not introduce a new concept to scout ("scout sensitive attributes"), we'll provide a hook `searchShouldUpdate` that can prevent an engine update, so the developers can implement the sensitive attributes feature themselves, or use it for other purposes.
@juampi92 juampi92 force-pushed the feature/sensitive-attributes branch from c0ee0e7 to 560d30a Compare July 4, 2021 18:46
@taylorotwell
Copy link
Member

I would also get rid of this concept of "force saved".

@taylorotwell
Copy link
Member

Please also fix the conflicts on this PR and mark as ready for review when done.

@taylorotwell taylorotwell marked this pull request as draft July 6, 2021 14:01
@juampi92
Copy link
Contributor Author

juampi92 commented Jul 6, 2021

Sure thing.

But I still have a concern regarding the soft-delete events:

When we create/update a model, the saved event will be fired before the attributes are synced with the originals, so we'll have them as dirty and we can make the check for the sensitive.
But when we delete/restore, the deleted_at attribute is not dirty. I guess the attributes get synced before the event is fired, so we never see any change (just like when forceDeleting).

I introduced the force saving concept to skip the searchShouldUpdate check when deleting/restoring, and I wanna find an alternative to it now that you said that you don't want it.

Do you think it makes sense to assume that if the dirty attributes are empty, it means that it's being deleted/restored?

I added that here

# Conflicts:
#	src/ModelObserver.php
#	tests/Fixtures/SearchableModelWithSoftDeletes.php
#	tests/Unit/ModelObserverTest.php
@juampi92 juampi92 marked this pull request as ready for review July 6, 2021 16:02
@taylorotwell
Copy link
Member

I see what you're saying - but there should not be a "forceSaved" method... it should all be internal. Add it back in and I can try to massage it how I want it to be.

When adding back the forcingSave, there's no need to test this scenario anymore given that searchShouldUpdate will never be called
@juampi92
Copy link
Contributor Author

juampi92 commented Jul 6, 2021

Done!

Curious to see what you'll end up doing. Thanks for the help man! Love your work

@taylorotwell taylorotwell merged commit 2dca8bf into laravel:9.x Jul 7, 2021
@taylorotwell
Copy link
Member

Made my changes and merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants