Reading private attributes stopped working

I recently started not getting back private attributes in my update action. Record looks good via Resource.read, but attribute is nil. Perhaps due to a recent regression or change in semantics?
24 Replies
ZachDaniel
ZachDaniel3y ago
🤔 interesting. So on an update the result doesn't contain private attributes, but when you read it back they are there? Are the attribute values nil on the way in?
\ ឵឵឵
\ ឵឵឵OP3y ago
I've just noticed this, and the action I was working on is a manual, getting the attributes with Changeset.get_attribute. I'm on ash trunk and just updated. Not nil in Resource.read, looks good in Postgres.
ZachDaniel
ZachDaniel3y ago
🤔 oh, so you're talking about before the update happens i.e
change fn changeset, _ ->
Ash.Changeset.get_attribute(changeset, :attr) #<- this is nil?
end
change fn changeset, _ ->
Ash.Changeset.get_attribute(changeset, :attr) #<- this is nil?
end
\ ឵឵឵
\ ឵឵឵OP3y ago
Yep
ZachDaniel
ZachDaniel3y ago
So generally speaking, that is caused by you passing in records to an update action that don't have all of the attributes selected I'd like to add an elegant "reselection" process to update actions that allow you to express the data that you need and have it be reselected before the action but until then, you'll have to make sure you've selected everything on a record before providing it for an update, or do the reselection yourself TO confirm that its nil because it hasn't been selected:
change fn changeset, _ ->
Ash.Resource.selected?(changeset.data, :attr) #<- will tell you if the attr was selected
Ash.Changeset.get_attribute(changeset, :attr)
end
change fn changeset, _ ->
Ash.Resource.selected?(changeset.data, :attr) #<- will tell you if the attr was selected
Ash.Changeset.get_attribute(changeset, :attr)
end
\ ឵឵឵
\ ឵឵឵OP3y ago
Ok, right on. Something along the lines of:
actions do
update do
reselecting :attr
# ...
end
end
actions do
update do
reselecting :attr
# ...
end
end
ZachDaniel
ZachDaniel3y ago
You could write a generic tool for this:
defmodule Reselect do
use Ash.Resource.Change

def change(changeset, opts, _) do
changeset
|> Ash.Changeset.before_action(fn changeset ->
if Enum.all?(opts[:attrs], &Ash.Resource.selected?(changeset.data, &1)) do
changeset
else
%{changeset | data: changeset.api.reload!(changeset.data)}
end
end)
end
end

change {Reselect, attrs: [:foo, :bar, :baz]}
defmodule Reselect do
use Ash.Resource.Change

def change(changeset, opts, _) do
changeset
|> Ash.Changeset.before_action(fn changeset ->
if Enum.all?(opts[:attrs], &Ash.Resource.selected?(changeset.data, &1)) do
changeset
else
%{changeset | data: changeset.api.reload!(changeset.data)}
end
end)
end
end

change {Reselect, attrs: [:foo, :bar, :baz]}
\ ឵឵឵
\ ឵឵឵OP3y ago
Great! Thanks a bunch. Hmm, it doesn't seem to be working for the action in question. (still getting nil)
ZachDaniel
ZachDaniel3y ago
Can you confirm if this: Ash.Resource.selected?(changeset.data, :attr) #<- will tell you if the attr was selected is returning true/false where you expect it to? or show me where you are doing get_attribute for example
\ ឵឵឵
\ ឵឵឵OP3y ago
Ash.Resource.selected?(cs.data, :attr) == true Weirdly
ZachDaniel
ZachDaniel3y ago
And what does changeset.attributes have in it? Does it contain %{attr: nil}?
\ ឵឵឵
\ ឵឵឵OP3y ago
Nope, it is %{}
ZachDaniel
ZachDaniel3y ago
:thinkies: what is cs.data.attr
\ ឵឵឵
\ ឵឵឵OP3y ago
It is KeyError 😁
ZachDaniel
ZachDaniel3y ago
what? is :attr not an attribute of your resource? To be clear, when I say :attr I mean whatever attribute you're checking with
\ ឵឵឵
\ ឵឵឵OP3y ago
In that case, it is nil.
ZachDaniel
ZachDaniel3y ago
So the passed in record has a nil value for the attribute, and it was selected, meaning that it should be nil Why should it not be nil?
\ ឵឵឵
\ ឵឵឵OP3y ago
Righto, the passed in record does have it as nil, I was thinking we were trying to indicate that it should be reselected. (the DB record is not nil)
ZachDaniel
ZachDaniel3y ago
gotcha. Well you could remove that if Enum.all check on your reselect change or add an option in there to force reselect some of them
\ ឵឵឵
\ ឵឵឵OP3y ago
Right on, then it would simply indicate to reload the entire record, which is just fine 🙂
ZachDaniel
ZachDaniel3y ago
You could also use changeset context for that
if changeset.context[:reload?] do
if changeset.context[:reload?] do
and then
Ash.Changeset.set_context(changeset, %{reload?: true})
Ash.Changeset.set_context(changeset, %{reload?: true})
where you call the action if you want to make it opt-in
\ ឵឵឵
\ ឵឵឵OP3y ago
I think it's probably acceptable to have it, but it seems more idiomatic to ensure that the callers are threading it through rather than pushing this into the actions themselves. Actually, a change to do a reload if the indicated attributes are nil would probably be a fine middleground.
defmodule Reselecting do
use Ash.Resource.Change

def change(changeset, opts, _) do
changeset
|> Ash.Changeset.before_action(fn changeset ->
if Enum.all?(opts[:attrs], &(changeset.data.[&1] != nil)) do
changeset
else
%{changeset | data: changeset.api.reload!(changeset.data)}
end
end)
end
end
defmodule Reselecting do
use Ash.Resource.Change

def change(changeset, opts, _) do
changeset
|> Ash.Changeset.before_action(fn changeset ->
if Enum.all?(opts[:attrs], &(changeset.data.[&1] != nil)) do
changeset
else
%{changeset | data: changeset.api.reload!(changeset.data)}
end
end)
end
end
ZachDaniel
ZachDaniel3y ago
I think you'll want Map.get there but yeah
\ ឵឵឵
\ ឵឵឵OP3y ago
Yep, works great, thanks very much mate! 🙂

Did you find this page helpful?