Decoupling forms from models

How do you decouple your application’s forms from your models/ schemas?

Background: So far my forms have just been 1–1 interfaces for my schemas. I mostly just continued the pattern which the Phoenix generators suggest and it worked fine. However, I’ve now run into a situation where this approach doesn’t really work and I feel like there should be another layer between contexts and controllers.

In my schema clients I have a client_number field. Now in my form I want to have a checkbox autogenerate_client_number? which determines if a client number should be generated on saving or the user input should be used. Using the generator’s approach I quickly added the autogenerate_client_number? virtual field to my schema and had everything working in no time. But as it’s just for one single form, it doesn’t really belong to my client model, so this feels wrong. Also, what if I need more settings in the future?

I thought about creating modules like AppWeb.Forms.NewClientForm that would be embedded schemas and be used in the controller. If valid, I could then pass the data to my context functions. This works, but the issue I see is that it leads to a lot of duplication. And how would I handle changeset errors that come from my model’s schema from the context function?

I’m now using a middle way by having modules like AppWeb.Forms.NewClientForm which them embed my model’s schema. This way I can add additional fields next to it and still get the errors from my real changeset, which is especially important for constraints. The negative part is that it leads to a lot of “manual” dealing with changesets and bloated controllers like in pre-context Phoenix apps.

One alternative I haven’t tried yet is building a small Form module that generates changesets on the fly.

So how do you decouple your forms from your schemas? I have searched the web and forums but couldn’t find much about this (just one post actually). Thanks a lot :slight_smile:

I‘ve been using this approach lately https://luizdamim.com/blog/reorganizing-your-phoenix-contexts-as-use-cases/

6 Likes

Nice, thanks! Do you use this along contexts or instead of them?
Edit: This question doesn’t make too much sense as you’d probably continue using them for non mutating functions anyway. I’d love to see an example how this integrates into controllers, would you mind sharing one?