-
Notifications
You must be signed in to change notification settings - Fork 43
Description
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
endThe 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