Ash Relationships with existing Ecto Repos

fekle:

Hi there, I’m currently working on integrating Ash into my existing Phoenix/Ecto/Postgres app. So far Ash looks amazing and seems to be exactly what I’m looking for - the power and balance of ecto but without having to create basic utility functions over and over and over again 😉

Sorry if my question has already been answered, I couldn’t find anything on GH, google, docs or Discord.

However I don’t yet want to migrate all my existing Ecto (Phoenix Context) resources all at once, but go step by step. Now, I added a new Repo, Registry and API with a new resource and that works fine. However now I want to add a :belongs_to in my new resource’s :relationships - and that needs to be a user, so the resource belongs to a user. Thing is, the User resource already exists as a normal Ecto schema that is used in my app, so ash tells me it can’t find the resource. Is there some way to have a “compatibility” layer between an existing ecto schema and an ash resource?

Thanks!

ZachDaniel:

Unfortunately there is nothing that lets you directly relate ecto schemas and ash resources at this time

fekle:

I understand, thanks for your swift reply! So i’ll just use a normal uuid field for now and manage the relationship manually, and when I migrate my user resource i should be able to reuse the existing uuid field.

ZachDaniel:

There also isn’t really a downside to having a duplicate ash resource for your ecto schema

fekle:

so that means i could have a new ash resource for my users which uses the same postgres table as my existing ecto schema, and keep using my ecto schema for existing functions? sounds like a good compromise

ZachDaniel:

Yep! Others have done pretty much exactly that.

fekle:

thanks a lot! I will do that 🙂

fekle:

One more quick question that I couldnt find the answer for: When using AshPhoenix.Form, is it possible to add values that are always applied when validating or submitting the form? For example a user id? I know that i can do this with a hidden input or by modifying the params before validating/submitting, but is there a more idiomatic way? The “params” option gets lost after the first validate.

ZachDaniel:

You can use the prepare_source option

ZachDaniel:

Form.for_create(..., prepare_source: fn changeset -> 
  Ash.Changeset.set_argument(....)
end)

ZachDaniel:

Actually I think in your case you want before_submit which is an option you add to the form submission

fekle:

Ah, i saw that before but that didnt work, as i want to set an attribute and not an argument. But using Ash.Changeset.change_attribute/3 in this works.

ZachDaniel:

Form.submit(..., before_submit: fn changeset -> 
  ...
end)

ZachDaniel:

I’d suggest doing it with before_submit to avoid malicious things like the user including the field and your value getting overwritten

ZachDaniel:

prepare_source -> put users params into action -> before_submit

fekle:

I understand, yeah thats the reason I dont like using hidden fields etc.

fekle:

before_submit unfortunately only works with submit and not validate, i need the attribute for validation

ZachDaniel:

🤔 interesting…maybe use both?

ZachDaniel:

I think there are definitely some ergonomics to be improved there.

fekle:

yes some kind of “unmodifyable values” that are always overwritten in the params. Maybe I’ll have a look at opening a PR for that 🙂

ZachDaniel:

So you need the attribute for validation because otherwise the form says its invalid, right?

ZachDaniel:

because you can technically submit an invalid form with force?: true on the submit

ZachDaniel:

so you could just let the form be invalid until then?

fekle:

yes, its a bit of an edge case. the item in the form belongs to a user, and has a number field - but the max value of the number field depends on a value in the user profile, so i need it for validation

ZachDaniel:

interesting. Then yeah I think you might be right. If the form accepts some values on form creation/validation like immutable_values: ... that would solve for it.

fekle:

In the meanwhile i’m thinking that maybe using an argument and handling the value in my actions is the better way do do this. there i could use the argument user_id for my validation and then always setting the attribute based on that argument

fekle:

thank you very much for your help, that cleared up things for me! 🙂