Skip to content

Instantly share code, notes, and snippets.

@fran-worley
Last active April 18, 2021 08:25
Show Gist options
  • Save fran-worley/5417e88fa9f293e6ff0f to your computer and use it in GitHub Desktop.
Save fran-worley/5417e88fa9f293e6ff0f to your computer and use it in GitHub Desktop.
Reform/ Dry-V validation examples
en:
errors:
rules:
title:
filled?: "can't be blank"
first_name:
filled?: "can't be blank"
last_name:
filled?: "can't be blank"
email:
filled?: "can't be blank"
unique_email?: "an active account already exists with that email address"
account_exists_for_email?: "an account can't be found for provided email address"
format?: "not a valid email address"
password:
filled?: "can't be blank"
min_size?: "must be at least %{num} characters long"
account_authenticated?: "is incorrect, please re-enter your password"
password_confirmation:
filled?: "can't be blank"
eql?: "doesn't agree to password"
assignment_id:
filled?: "can't be blank"
int?: "must be integer"
within_client_scope?: "invalid profile"
unique?: "you can only have one assignment per profile"
require 'reform/form/coercion'
require "reform/form/dry"
class LoginForm < Reform::Form
include Coercion
include Reform::Form::Dry::Validations
#form object without a AR model
#so override initialize
def initialize
super(OpenStruct.new)
end
properties :email, :password, type: String
property :remember_me, type: Virtus::Attribute::Boolean
attr_accessor :user
validation :default do
key(:email, &:filled?)
key(:password, &:filled?)
end
validation :account_exists, if: :default do
configure do |config|
config.messages_file = 'config/error_messages.yml'
end
key(:email) { |value| value.account_exists_for_email? }
def account_exists_for_email?(value)
form.user = User.find_by(email: value.downcase)
form.user.present?
end
end
validation :valid_password, if: :account_exists do
configure do |config|
config.messages_file = 'config/error_messages.yml'
end
key(:password) { |value| value.account_authenticated? }
def account_authenticated?(value)
form.user.authenticate(value)
end
end
end #class LoginForm
class User < ActiveRecord::Base
has_secure_password validations: false
has_many :assignments, dependent: :destroy
belongs_to :organisation
end
require 'reform/form/coercion'
require "reform/form/dry"
require 'disposable/twin/parent'
class UserForm < Reform::Form
feature Coercion
feature Parent
feature Reform::Form::Dry::Validations
properties :title, :first_name, :last_name, :suffix, :email, type: String
property :login, type: Virtus::Attribute::Boolean
property :password, read: false, type: String
property :password_confirmation, virtual: true, type: String
collection :assignments, populator: :assignments!, skip_if: :all_blank do
property :id, type: Integer, writeable: false
property :_destroy, virtual: true
property :assignment_id, type: Integer
property :role, type: String
validation :default do
configure { |config| config.messages_file = 'config/error_messages.yml' }
key(:assignment_id) {|assignment_id| assignment_id.filled? & assignment_id.int? }
end
validation :valid_assignment, if: :default do
configure { |config| config.messages_file = 'config/error_messages.yml' }
key(:assignment_id) { |assignment_id| assignment_id.within_client_scope? & assignment_id.unique? }
#makes use of disposables `feature Parent` to access siblings
#should return false if the assignment is not part of the correct organisation
def within_client_scope?(value)
Assignment.where(organisation_id: form.parent.organisation_id).pluck(:id).include?(value)
end
#makes use of disposables `feature Parent` to access siblings
#should return false is it is not unique
def unique?(value)
!form.parent.assignments.map{|a| a.assignment_id == value unless a == form }.include?(true)
end
end
end
validation :default do
configure { |config| config.messages_file = 'config/error_messages.yml' }
key(:title, &:filled?)
key(:first_name, &:filled?)
key(:last_name, &:filled?)
key(:password){ |password| password.none? | (password.filled? & password.min_size?(6)) }
key(:password_confirmation){ |pass_conf| password.none? | pass_conf.filled? }
key(:login, &:bool?)
#It appears that there are some issues with high-level rules.
#https://github.com/dryrb/dry-validation/issues/50
#at present this doesn't work:
#key(:email) { |email| email.none? | email.format?(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i) }
#rule(:require_email) do
# rule(:login).true? > rule(:email).filled?
#end
#So i'm temporarily using the form object that reform provides:
key(:email) do |email|
email.optional? | (email.filled? & email.format?(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i))
end
def optional?(value)
form.login != true
end
#error messages are not generated correctly for high level rules
#https://github.com/dryrb/dry-validation/issues/47
rule(:validate_password) do
rule(:login).true? > (rule(:password).filled? & rule(:password_confirmation).filled? & rule(:password_confirmation, eql?: [:password, :password_confirmation]))
end
#will become part of dry-v in future updates
def true?(value)
value === true
end
end
validation :unique_email, if: :default do
configure { |config| config.messages_file = 'config/error_messages.yml' }
key(:email) { |email| email.none? | email.unique_email?}
def unique_email?(value)
User.where(deleted: false, email: value).where.not(id: form.id).blank?
end
end
def assignments!(collection:, fragment:, index:, **)
return collection.insert(index, model.assignments.new) if fragment["id"].blank? && fragment["_destroy"] != "true"
deserialized_item = collection.find { |item| item.id.to_s == fragment["id"] }
if fragment["_destroy"] == "true" # don't process if it's getting removed!
collection.delete(deserialized_item)
return skip!
end
deserialized_item
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment