Public changeset functions

For awhile now I’ve been a big fan of simple rules while programming, especially related to code organization. It’s really nice when working on a team to have simple rules to follow so that everyone know where things go. A pretty simple rule with phoenix is to put database CRUD related code that interacts with the Repo inside of the Phoenix Context’s. If this rule is followed it allows anyone to pick up a phoenix application and know where database related code would be. :+1: :+1:

Another common rule I’ve heard of in the Elixir/Phoenix community is that public Ecto Schema file functions should always return a changeset. Don’t put business logic functions on the schema.

I’ve recently ran into a code review at work that challenged this rule. The code that was added was a public function that is both used in the changeset validation but also used in the rendering of the template.

I’ve created a basic example application to show what I mean: bike shop

The story behind this example application is that we are a bike shop, and we are using this application to keep track of what bikes we make. Right now the only difference between bikes are their color. We are a small startup bike shop so we’ve kept our colors small, we only sell red and blue bikes. In the future, if we get enough requests from customers we might add more colors.

The code added to the schema is this:

It’s a public function that exposes what colors we have available for bikes.

We use that for validation via validate_inclusion:

But we also want to use those options when rendering the form for a dropdown when creating or editing a bike:

So the question is, does this available_colors/0 function belong on the schema? If the rule is that all public functions of a schema should always return a changeset then the obvious answer is no, it does not belong here.

So where does it belong? The context? I thought the context was mainly for CRUD related functions though, this wouldn’t seem to fit very well there.

Where do you store things like this?


The reason behind reusing the available_colors/0 is to DRY things up, and to have one place to go change if we decide to sell a different color bike in the future, but this will break that simple rule of public functions always returning changesets.

So what’s the new rule?
How do you prevent more complex logic creeping into the schema file?
Is there a better way to do this with changesets?

1 Like

I don’t think it matters much where it goes but the rule I personally follow is to keep the things without side-effects in the schema (so that includes business logic because guess what, validations are business logic too :smiley:) and everything else goes in the context.

EDIT: to add to this, the only reason I keep this distinction is for understanding/simplicity/testing but I don’t fret too much about it.


@josevalim ok that’s a great rule and exactly the type of thing I was searching for :clap: :tada:

1 Like