Skip to content

Instantly share code, notes, and snippets.

@mariuszkapcia
Last active June 3, 2020 13:53
Show Gist options
  • Save mariuszkapcia/f6dbcccf20c742a6f1a2bcf5f4cf9a50 to your computer and use it in GitHub Desktop.
Save mariuszkapcia/f6dbcccf20c742a6f1a2bcf5f4cf9a50 to your computer and use it in GitHub Desktop.
module Users
class ChangeAvatar < Command
attribute :avatar_url, Types::Strict::String
end
end
module Users
class ChangePassword < Command
attribute :password, Types::Strict::String
attribute :password_confirmation, Types::Strict::String
end
end
require 'dry-struct'
class Command < Dry::Struct
Invalid = Class.new(StandardError)
def self.new(*)
super
rescue Dry::Struct::Error => error
raise Invalid, error
end
end
class Form
include ActiveModel::Model
include ActiveModel::Validations
include ActiveModel::Conversion
class ValidationError < StandardError
attr_reader :errors
def initialize(errors)
@errors = errors
end
end
def self.validation_options(options)
@options ||= {}
@options.merge!(options)
end
def self.command(cmd_class)
cmd_attributes = cmd_class.schema.keys.map(&:name)
cmd_attributes.each do |attribute|
send(:attr_accessor, attribute)
end
@attributes ||= []
@attributes += cmd_attributes
@attributes = @attributes.uniq
@commands ||= {}
@commands[cmd_class] = cmd_attributes
end
class << self
alias_method :activemodel_validates, :validates
alias_method :activemodel_validate, :validate
end
def self.validates(*attributes)
@validations ||= []
@validations.push(attributes)
self
end
def self.validate(*args, &block)
if args[0].class.eql?(Symbol)
@custom_validations ||= []
@custom_validations.push(args)
self
else
super
end
end
def self.options
@options ||= { skip_unchanged_attributes: false }
end
def self.attributes
@attributes
end
def self.commands
@commands
end
def self.validations
@validations
end
def self.custom_validations
@custom_validations
end
def options
self.class.options
end
def attributes
self.class.attributes
end
def commands
self.class.commands
end
def validations
self.class.validations
end
def custom_validations
self.class.custom_validations
end
def verify!
return if valid?
raise ValidationError, errors.to_hash.map { |k,v| v }.flatten
end
def valid?
self.class.clear_validators!
skip = options[:skip_unchanged_attributes]
validations.each do |validation|
next if skip && validation[0].in?(@changed_attributes)
self.class.activemodel_validates(*validation)
end
custom_validations.each do |validation|
next if skip && validation[0].in?(@changed_attributes)
self.class.activemodel_validate(*validation[1])
end
super
end
def apply_changes(params)
params.each do |attribute, value|
attribute = attribute.to_sym
next unless attribute.in?(attributes)
if value_changed?(attribute, value)
@changed_attributes += [attribute]
end
assign_value(attribute, value)
end
self
end
def build_commands!
commands.map do |command, attributes|
next if (@changed_attributes & attributes).length.eql?(0)
build_command(command, attributes)
end.compact
end
private
def initialize(object = nil)
@changed_attributes = []
attributes.each do |attribute|
assign_value(attribute, object.try(:[], attribute))
end
end
def build_command(command, attributes)
attributes = attributes.map do |attribute|
[attribute, fetch_value(attribute)]
end
command.new(Hash[attributes])
end
def value_changed?(attribute, new_value)
send(attribute) != new_value
end
def assign_value(attribute, value)
send("#{attribute}=", value)
end
def fetch_value(attribute)
send(attribute)
end
end
class ProfileForm
command Users::ChangeAvatar
command Users::ChangePassword
validation_options skip_unchanged_attributes: true
validates :avatar_url,
presence: { message: 'avatar_url_is_missing' }
validates :password,
presence: { message: 'password_is_missing' }
validates :password_confirmation,
presence: { message: 'password_confirmation_is_missing' }
validate :password_confirmation_match
private
def password_confirmation_match
if !password.eql?(password_confirmation)
errors.add(:password_confirmation, 'password_confirmation_mismatch')
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment