Skip to content

Changeset-level validations (and maybe a changeset dsl?) #212

@faultyserver

Description

@faultyserver

Kind of expanding from #208 / #210, I'd like to be able to use validations at the changeset level rather than at the model level. For example:

class Accounts < Crecto::Model
  schema "accounts" do
    field :username, String
    field :password, String, virtual: true
    field :password_confirm, String, virtual: true
  end

  # When creating a User, validate that the username and password are both
  # given, and that the password confirmation matches.
  def create_changeset(user : self, attrs)
    changeset = user.cast(attrs, [:username, :password, :password_confirm])
    changeset.validate_required(:username, :password, :password_confirm)
    changeset.validate_matches(:password, :password_confirm)
    changeset
  end

  # When updating a User, they are not allowed to change their username, so
  # only cast and validate the password changes.
  def update_changeset(user : self, attrs)
    changeset = user.cast(attrs, [:password, :password_confirm])
    changeset.validate_matches(:password, :password_confirm)
    changeset
  end
end

The inspiration for this comes from reading this article on Ecto's Changesets, seeing this presentation at ElixirConf about breaking down User monoliths, and applying the multi-changeset pattern in real world applications. A large benefit of this style is being able to create different changesets for different purposes and really lock down what valid operations are for a given model.

 

It's a little verbose to keep writing changeset... for each validation line. There's the easy change of renaming the variable something like c, but another options would be adding another changeset method to the Model that accepts a block and acts a little like tap, returning the changeset at the end. Something like this:

def create_changeset(user : self, attrs)
  changeset(user) do |c|
    c.cast(attrs, [:username, :password, :password_confirm])
    c.validate_required(:username, :password, :password_confirm)
    c.validate_matches(:password, :password_confirm)
  end
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions