Skip to content

Instantly share code, notes, and snippets.

@havenwood
Created July 1, 2024 18:46
Show Gist options
  • Save havenwood/04a583651d62d68b845d83cc5a66802b to your computer and use it in GitHub Desktop.
Save havenwood/04a583651d62d68b845d83cc5a66802b to your computer and use it in GitHub Desktop.
An example immutable (Dry) and mutable (Active Model) Args interface
require 'dry-struct'
require 'dry-types'
require 'dry-validation'
class WordTally
class Args < Dry::Struct
module Types
include Dry.Types()
end
SORT_OPTIONS = %i[asc desc unsorted].freeze
CASE_OPTIONS = %i[original upper lower].freeze
schema schema.strict
attribute :sort, Types::Symbol.default(:desc)
attribute :case, Types::Symbol.default(:lower)
attribute :min_chars, Types::Integer.default(1)
attribute :min_count, Types::Integer.default(1)
attribute :delimiter, Types::String.default(' ')
attribute :output, Types::String.default('-')
attribute :verbose, Types::Bool.default(false)
attribute :debug, Types::Bool.default(false)
ValidationContract = Dry::Validation.Contract do
params do
required(:sort).filled(:symbol, included_in?: SORT_OPTIONS)
required(:case).filled(:symbol, included_in?: CASE_OPTIONS)
required(:min_chars).filled(:integer, gteq?: 1)
required(:min_count).filled(:integer, gteq?: 1)
required(:delimiter).filled(:string, min_size?: 1)
end
end
def self.new(...)
instance = super(...)
instance.instance_variable_set '@review', ValidationContract.call(instance.to_h)
instance
end
class ValidationError < ArgumentError
def initialize(errors)
messages = errors.map { |error| "`#{error.path.join('.')}` #{error.text}" }
super messages.join('; ')
end
end
def validate
raise ValidationError, @review.errors if @review.failure?
end
def valid? = @review.success?
def invalid? = @review.failure?
def errors = @review.errors
end
end
require 'active_model'
class WordTally
class Args
include ActiveModel::AttributeAssignment
include ActiveModel::Attributes
include ActiveModel::Validations
class Types
class Symbol < ActiveModel::Type::Value
def cast(value) = value.to_sym
end
end
ActiveModel::Type.register(:symbol, Types::Symbol)
SORT_OPTIONS = %i[asc desc unsorted].freeze
CASE_OPTIONS = %i[original upper lower].freeze
attribute :sort, :symbol, default: :desc
attribute :case, :symbol, default: :lower
attribute :min_chars, :integer, default: 1
attribute :min_count, :integer, default: 1
attribute :delimiter, :string, default: ' '
attribute :output, :string, default: '-'
attribute :verbose, :boolean, default: false
attribute :debug, :boolean, default: false
validates :sort, inclusion: {in: SORT_OPTIONS}
validates :case, inclusion: {in: CASE_OPTIONS}
validates :min_chars, :min_count, numericality: {only_integer: true, greater_than_or_equal_to: 1}
validates :delimiter, length: {minimum: 1}
def initialize(attributes = {})
super()
assign_attributes(attributes)
end
def []=(attribute, value)
public_send("#{attribute}=", value)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment