Updating updated_at Field for Related Records in Ent ORM

107 Views Asked by At

I'm working with the Ent ORM and I've defined two tables: user and password. The relationship is such that a user can have multiple passwords, but each password can be associated with only one user.

In the password schema, I've set up a hook for the updateOp operation. The issue I'm encountering is when I try to update the user and add a password to it using:

user.Update().AddPasswords(fetchedPassword).SaveX(ctx)

The updated_at field in the password table doesn't get updated. Is there a way to ensure the password's updated_at field is updated simultaneously without resorting to:

fetchedPassword.Update().SetUser(user).SaveX(ctx)

I'm looking for a more streamlined approach that doesn't require separate update operations for each table. Any suggestions or insights would be greatly appreciated.

1

There are 1 best solutions below

0
On

In Ent, hooks work by modifying mutations (essentially middleware for mutations), so this means they only run if there is a mutation for the model. In your case the mutation is of the User model, not the Password model, thus the hooks for the Password model do not run.

To get your desired functionality I think you will have to use a separate operation for the password table. There are a few approaches that come to mind here.

Approach 1: Use a default value

As I understand your setup, every time a user changes their password, you add a new password record, so in this sense the updated_at field is more of a "created at" from the perspective of the password (where the password is an immutable record). In this case you can just set the default value for the updated_at field to be the current time, like so (in your ent/schema/password.go file):

func (Password) Fields() []ent.Field {
    return []ent.Field{
        field.Time("updated_at").
            Default(time.Now),
    }
}

Approach 2: Use a hook and update the Password instead of User

If the password records do need to be updated, however, then you'll have to use the approach you outlined in your question: mutate the password rather than the user and have your hook run.

Example hook:

func (Password) Hooks() []ent.Hook {
    return []ent.Hook{
        hook.On(
            func(next ent.Mutator) ent.Mutator {
                return hook.PasswordFunc(func(ctx context.Context, m *gen.PasswordMutation) (ent.Value, error) {
                    if anyChanged := len(m.Fields()) > 0; anyChanged {
                        m.SetUpdatedAt(time.Now())
                    }
                    return next.Mutate(ctx, m)
                })
            },
            ent.OpUpdate|ent.OpUpdateOne,
        ),
    }
}

Approach 3: Use a database trigger

This approach requires you to leave Ent's handling, which may not be a good idea if you have other hooks/logic in Ent that depend on this field and it makes the logic harder to trace since this happens in the database directly, but it is an approach nonetheless.

Example trigger (for PostgreSQL):

CREATE OR REPLACE FUNCTION set_updated_at__password() RETURNS TRIGGER AS $$
BEGIN
    IF (TG_OP = 'UPDATE' AND OLD.* IS DISTINCT FROM NEW.*) THEN
        NEW."updated_at" := NOW();
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE TRIGGER "set_updated_at_trigger__password"
AFTER INSERT OR UPDATE OR DELETE ON "password"
FOR EACH ROW
EXECUTE PROCEDURE set_updated_at__password();