Examples of good style for more complicated changesets and/or changeset-function builders

Newbie here. I’m looking for both advice on idiomatic changeset code and pointers to projects that are good examples of idiomatic code. I worry that my habits don’t match Phoenix+Ecto.


How often do people have multiple changeset-creation functions for a schema? For example, consider an administrator creating a user:

  • The auth_id is required
  • The display_name is required

We want a changeset function with validate_required([:auth_id, :display_name, ...])

But now consider that user changing some of her attributes. She can’t change the auth_id (let’s assume that’s a business requirement, so not up for debate). So I do not want :auth_id in the list given to validate_required. Well, I could alternately put the :auth_id as a hidden field on the “change your settings” form and use the same changeset, but I’m doubtful that’s the right place for the responsibility. It seems to me that the fact “the auth_id cannot change” should be stated in the user.ex file.

My impulse - which may be wrong - is that each field in a schema should have its invariants declared somewhere separate from the other fields (the same way its type and default value are). That could be as a function for each field:

  defp check_attr(:auth_id = field, changeset) do
    changeset
    |> validate_length(field, min: 5, max: 100, count: :codepoints)
    |> unique_constraint(field, name: :unique_auth_id)
  end

… which means that the changeset function could call the appropriate different invariant-checker for each of the required fields.

As a longtime Lisp/Ruby/Clojure weenie, I’m inclined to want some DSL to describe invariants, something like this:

  param_handling(
    auth_id: [
      length: {5, 100},
      unique: :unique_auth_id
    ],
    display_name: [
      length: {2, 100},
    ],
    email: [
      :length: {5, 100},
    ],
...

… and then the creational changeset would be responsible for describing which fields were required or optional, not for what each field should look like.

Should I stop fussing with changeset ideas and just do things the idiomatic way? What is that idiomatic way?