How to pattern match on a union type field before and after it has been saved

When saving the contents of a union type, you can simply pass in a map of the data, such as:

x = %MainResource{union_field: %{foo: :bar}}
# Before saving, you can do
x.union_field.foo
# and pattern match on
%{union_field: %{foo: val}}

But when you get the same one back from the db after saving, it is in the form:

x.union_field.value.foo
# and pattern match happens on
%{union_field: %{value: %{foo: val}}}

I have code that needs to pattern match on the unsaved and saved version, and wondering if there is a way to do this without needing 2 functions to handle both versions. I’m using map_with_tag for storage.

Thanks!

I don’t totally follow what you mean by two functions, like two differently named functions? You can certainly make this work with one function with either two heads or a case statememtn. I’m not sure of the exact API you want but something like this:

def get_value(%MainResource{union_field: %{value: value}}), do: value
def get_value(%MainResource{union_field: value}), do: value

Then you can do:

get_value(%MainResource{union_field: %{foo: bar}})
# %{foo: :bar}
get_value(%MainResource{union_field: %{value: %{foo: bar}}}
# %{foo: :bar}

Is that what you mean or are you talking about the . syntax?

1 Like

Thanks! That’s what I am doing, but I’m up to over 20 of these, and more will be needed as more and more fields are added that have to be matched against. So I’m at the point where I (or the next person) may forget to make two pattern match versions. (I guess I somehow add an intermediary function that then calls all these others?)

Luckily, using dot notation only ever happens on saved data, so that makes life easier.

I think subconsciously I wrote this topic to make sure I was using union types correctly, and that this was the intended behavior.

Ohhhh, I neglected to notice that this was posted in the Ash Forum so I was a bit confused. There may well be a better answer! Sorry about that.

Hmm…something worth trying is to first attempt to cast the value as the underlying union type. If successful it will be in the second format only. You could write a general purpose helper that takes a value and a type and does the castin, maybe with a function of what to do with the caster value as well perhaps?

Taking Zach’s answer into consideration, I did want to ask how you are using these accessors and why they are deemded necessary, but very hard without more context (and especially since my Ash knowledge is unforunately limited). At first I was thinking that it’s always literally :union_field, but now based on your answer (and the bit of docs I read) assuming that is dynamic while :value is literally :value. Assuming this, you can make a 2-arity accessor:

def get_value(resource, union_field) do
  case resource do
    %{^union_field => %{value: value}} ->
      value

    %{^union_field => value} ->
      value
  end
end

Then:

get_value(%MainResource{some_field: %{foo: :bar}}, :some_field)
# %{foo: :bar}
get_value(%MainResource{some_other_field: %{value: %{foo: :bar}}}, :some_other_field)
# %{foo: :bar}
(apologies, I'm noticing the typo of `bar` vs `:bar` in my original post)

Again, not super sure this is what you’re after but this is my next best attempt :smiley:

2 Likes