This is not important, I’m just curious about how it would be done.
The updated_at
field on my User gets bumped on every login, even if no fields changed. Auth0 already saves all the history about successful and unsuccessful logins, so I only want updated_at
to be bumped when one of the fields for the User resource is being changed to a different value.
I followed this guide and then made some changes and eded up with the following action:
create :register_with_auth0 do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
upsert? true
upsert_identity :unique_auth0_id
# Required if you have token generation enabled.
change AshAuthentication.GenerateTokenChange
# Required if you have the `identity_resource` configuration enabled.
change AshAuthentication.Strategy.OAuth2.IdentityChange
change fn changeset, _ ->
user_info = Ash.Changeset.get_argument(changeset, :user_info)
changes = %{
"email" => Map.get(user_info, "email"),
"auth0_id" => Map.get(user_info, "sub")
}
Ash.Changeset.change_attributes(
changeset,
changes
)
end
end
1 Like
If I’m reading the docs correctly when upsert_fields
is empty then all fields except those with defaults are set on conflict. This would lead me to believe that you shouldn’t be seeing the updated_at changing if there are no other changes. Is that not the case?
I just confirmed, updated_at
is definitely getting updated to now()
on every login.
I do not see upsert_fields
in my code or in the Ash.Changeset
so I can not make sure if it is empty or not.
There are for-sure no other fields changing, just the updated_at
.
Here is the changeset I am returning from the register_with_auth0
change:
Ash.Changeset<
action_type: :create,
action: :register_with_auth0,
attributes: %{
name: "De Wet Blomerus",
auth0_id: "redacted",
email: #Ash.CiString<"dewetblomerus@gmail.com">,
email_verified: true,
picture: "shortened"
}
Thanks for the confirmation. I believe this could be solved by #760. Keep an eye on that issue for updates.
1 Like
Yes, so since it’s a create or update, each login becomes an update. You can use the new {:replace_all_except, [:updated_at]}
option to prevent that behavior.
Thanks a lot for remembering and circling back.
I might have found an error scenario that needs some work.
I updated to the following:
ash 2.17.0 2.17.0 Up-to-date
ash_admin 0.9.5 0.9.5 Up-to-date
ash_authentication 3.11.16 3.11.16 Up-to-date
ash_authentication_phoenix 1.9.0 1.9.0 Up-to-date
ash_phoenix 1.2.23 1.2.23 Up-to-date
ash_postgres 1.3.60 1.3.60 Up-to-date
Then I tried this: upsert_fields { :replace_all_except, [:updated_at] }
I also tried this:
upsert_fields {
:replace_all_except,
[:updated_at, :auth0_id, :id, :created_at]
}
I also tried upsert_fields :replace_all
And since I was on a roll, I tried the following:
upsert_fields {
:replace,
[:name, :email, :picture, :email_verified]
}
All of the above yielded the same result. I get an error at login, but there is no error in the logs, just a rollback
. Here are the logs:
[debug] Processing with RedWeb.AuthController
Parameters: %{"code" => "the-code", "state" => "the-state"}
Pipelines: [:browser]
[debug] QUERY OK db=4.3ms idle=1857.9ms
begin []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1801
[debug] QUERY OK db=2.4ms
rollback []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1801
[info] Sent 401 in 945ms
Link to my entire user
Can you try submitting the action in iex with that upsert_fields
set? I’m not sure what the issue is currently.
While trying to get the output for this, I got stuck with something I have gotten stuck with a few times while using Ash. I am calling an Ash function with the wrong arguments, and the error message is trying to tell me something, but I am unable to translate the error message into how to fix it.
Here is what I tried:
auth0_id = "redacted"
email = "dewetblomerus@gmail.com"
user = Red.Accounts.User.get_by!(%{email: email})
user_info = %{
"name" => "De Wet",
"email" => email,
"sub" => auth0_id
}
user
|> Ash.Changeset.for_update(
:register_with_auth0,
%{
user_info: user_info,
oauth_tokens: %{}
}
)
|> Red.Accounts.create!()
When I run this, I get the following error:
** (Ash.Error.Invalid) Input Invalid
* attribute email is required
(ash 2.17.0) lib/ash/api/api.ex:2324: Ash.Api.unwrap_or_raise!/3
/Users/dewet/code/ash/red/README.livemd#cell:7cjnls2owec2pmsah6gswm3qjxo6sxou:20: (file)
I tried to put email: email
as part of the params
map or the opts
list arguments to Ash.Changeset.for_update
but nothing I tried changed the error.
changeset.errors
is as follows:
[
%Ash.Error.Changes.Required{
field: :email,
type: :attribute,
resource: Red.Accounts.User,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :invalid
}
]
This seems strange. Is this your own action? Do you have a change or something in the action attempting to set the email based on some input, but setting it to nil
?
This is my current action:
create :register_with_auth0 do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
upsert? true
upsert_identity :unique_auth0_id
change fn changeset, _ ->
user_info = Ash.Changeset.get_argument(changeset, :user_info)
changes =
user_info
|> Map.take([
"email_verified",
"email",
"name",
"picture"
])
|> Map.put("auth0_id", Map.get(user_info, "sub"))
Ash.Changeset.change_attributes(
changeset,
changes
)
end
end
It is still very close to what I had after following this guide: Ash Framework
I am seeing something else in the changeset that might help:
#Ash.Changeset<
action_type: :update,
action: :register_with_auth0,
attributes: %{name: "De Wet"},
relationships: %{},
arguments: %{
user_info: %{
"email" => "dewetblomerus@gmail.com",
"name" => "De Wet",
"sub" => "google-oauth2|redacted"
},
Under attributes
, it only has a name, but I passed in a name and email.
This code works 100% for signup and login.
Where are you inspecting that? After calling Ash.Changeset.change_attributes/2
?
@zachdaniel thanks for hanging in there with this long thread! Your question just made me find something really interesting.
I was not inspecting it, I was just looking at the return value in Livebook.
When I inspect the Ash.Changeset
after calling Ash.Changeset.change_attributes/2
, it is valid?: true
The only code where I am doing something wrong is where I am calling it form LiveBook.
changeset =
user
|> Ash.Changeset.for_update(
:register_with_auth0,
%{
user_info: user_info,
oauth_tokens: %{}
}
)
That is when I get the error about no email, but if I inspect inside the action, right after calling change_attributes/2
, it says there are no errors.
I tried to change it to Ash.Changeset.for_create
but then I get the following error:
** (ArgumentError) Initial must be a changeset with the action type of `:create`, or a resource.
Got: #Red.Accounts.User<
What confuses me about this error, is that it says it must be a changeset or a resource, my understanding is that it is a resource.
%Foo{}
is what we call a “record” or an instance of a resource. Foo
is the resource ![:slight_smile: :slight_smile:](https://elixirforum.com/images/emoji/apple/slight_smile.png?v=12)
GOT IT! Thank you!
This works perfectly from Livebook, which is what we were trying to get to with the last ~7 messages
Red.Accounts.User
|> Ash.Changeset.for_action(
:register_with_auth0,
%{
user_info: user_info,
oauth_tokens: %{}
}
)
|> Red.Accounts.create!()
And it does change updated_at
every time it is called, even if nothing changed.
What did you mean by upsert_fields
? I am setting the auth0_id
and email
.
I don’t recall what I was getting at
But I think what I was implying was try to call the action in iex (with the upsert_fields: {:replace_all_except, ...}
option passed that was giving you trouble) as opposed to submitting a form.
1 Like
Oh right! Here it is.
** (Ash.Error.Unknown) Unknown Error
* ** (Protocol.UndefinedError) protocol Enumerable not implemented for {:replace_all_except, [:updated_at]} of type Tuple. This protocol is implemented for the following type(s): DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Jason.OrderedObject, List, Map, MapSet, Phoenix.LiveView.LiveStream, Postgrex.Stream, Range, Stream, StreamData
(ash 2.17.0) lib/ash/api/api.ex:2324: Ash.Api.unwrap_or_raise!/3
/Users/dewet/code/ash/red/README.livemd#cell:7cjnls2owec2pmsah6gswm3qjxo6sxou:43: (file)
Looks like a regular old bug on that one. I think we haven’t added the logic for transforming the new formats for upsert_fields
when it’s provided as an option. Could you open an issue on Ash?
Thanks a million for all the effort you have put into something that was merely an annoyance for me. The least I can do is open an issue. I’ll also try to provide a more minimal example for reproducing it. I don’t think Ash Authentication or Phoenix would need to be involved.
1 Like