-
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
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