Skip to content

use_database_state hook #39

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

Closed
1 task done
Archmonger opened this issue Jan 10, 2022 · 8 comments
Closed
1 task done

use_database_state hook #39

Archmonger opened this issue Jan 10, 2022 · 8 comments

Comments

@Archmonger
Copy link
Contributor

Archmonger commented Jan 10, 2022

Old Behavior

State is generated on a per-render basis and has no method of being permanently stored.

Additionally, there is no convenient way to perform DB queries within components (as of Django 4.1)

New Behavior

Allow for persistent state that is databased backed.

Implementation Details

Persistently store/restore context based on a database model.

The interface should take in a single database object...

from my_django_app.models import GeneralSettings

state, set_state = hooks.use_database_state(GeneralSettings.objects.get(id=20))
state.my_database_field = True

# This saves the values to the database and issues a re-render.
set_state(state)

In the background, the set state is doing the following...

def set_state(state):
    state.save()
    ...

Code of Conduct

@Archmonger Archmonger changed the title useDatabaseState Hook use_database_state Hook Jan 10, 2022
@rmorshea
Copy link
Contributor

rmorshea commented Feb 5, 2022

I imagine the usage should look like either:

results = use_database(lambda: Model.objects.all())

@use_database
def results():
    return Model.objects.all()

The implementation might look like:

def use_database(function, initial_value=None):
    result, set_result = use_state(initial_value)

    query_function = use_callback(query_function)
    async_function = use_memo(lambda: database_sync_to_async(function))

    @use_effect
    async def exec_function():
        output = await async_function()
        if isinstance(output, QuerySet):
            bool(output)
        set_result(output)

    return result

@Archmonger
Copy link
Contributor Author

With that kind of interface, how would someone edit an entry and then save changes?

There needs to be some way of performing .save() operations via database_sync_to_async

@rmorshea
Copy link
Contributor

rmorshea commented Feb 5, 2022

Wouldn't one typically do that in response to some sort of event, in which case you'd be able to define an async handler that does this?

@rmorshea
Copy link
Contributor

rmorshea commented Feb 5, 2022

You're right though, that does feel a bit disjointed. Maybe the interface ought to be more like use_state for single record access.

model, set_model = use_one_model(
    Model.objects.get(...)  # assuming query exec is delayed
)

async def handle_event():
    await set_model(attr=...)

@rmorshea
Copy link
Contributor

rmorshea commented Feb 5, 2022

I'm not really sure how this should work for bulk access though...

@rmorshea
Copy link
Contributor

rmorshea commented Feb 5, 2022

Actually, there's a deeper issue here around object identity where the declarative nature of IDOM and the imperative nature of Django clash - Django expects you to modify an object to update it in the database, but IDOM uses object identity to infer whether something has changed. For example, imagine using a model in a use_memo hook:

model, set_model = use_one_model(...)
something_expensive = use_memo(lambda: compute_something_expensive(model))

A naive implementation of use_one_model would mutate model, call model.save() and, on the next render, return the same model instance. But in order to know whether something_expensive needs to be re-computed, IDOM looks at the identity of model. If model is the same object, then something_expensive won't be re-computed. Thus you'd encounter a bug where, despite calling set_model, something_expensive would not receive a corresponding update.

It almost feels like there needs to be a thin declarative wrapper around Django's models...

@Archmonger
Copy link
Contributor Author

Archmonger commented Feb 6, 2022

While Django models might be mutable (haven't confirmed), I know that Django querysets are immutable. The lazy nature of things is the biggest issue though. I haven't been able to find a good solution to that yet.

Also, I believe accessing Model.objects needs to occur within a sync context.

I honestly think the best solution to this might end up being a monkey patch to the Django Model object...

@Archmonger Archmonger changed the title use_database_state Hook use_database_state hook Jan 16, 2023
@Archmonger
Copy link
Contributor Author

Archmonger commented Jan 18, 2023

Closing this.

I think it no longer makes sense given the existence of use_query, and may not be technologically feasible.

@Archmonger Archmonger closed this as not planned Won't fix, can't repro, duplicate, stale Jan 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants