Skip to content

Instantly share code, notes, and snippets.

@wtnabe
Last active July 4, 2025 15:30
Show Gist options
  • Save wtnabe/bb522987611e846d38008c42d02d450c to your computer and use it in GitHub Desktop.
Save wtnabe/bb522987611e846d38008c42d02d450c to your computer and use it in GitHub Desktop.
A concept code of reusable schema and Value Object with Dry::Schema and Data ( Ruby 3.2+ )
require "dry/schema"
module GuideRail
#
# @param [String|Symbol] klass
#
def class_name(klass)
@_class = klass
end
#
# @param [Dry::Schema::Params] schema
#
def schema(schema)
@_schema = schema
end
#
# @param [Proc] block
#
def yield_block(&block)
@_block = block
end
#
# @return [Data]
#
def create_class!
Object.const_get @_class
rescue NameError
@_attrs = @_schema.key_map.to_a.map { |k| k.name.to_sym }
klass = Data.define(*@_attrs, &@_block)
Object.const_set(@_class, klass)
klass
end
#
# @param [Hash] data
#
def from(data)
acceptance = accept(data)
create_class!
result = @_schema.call(acceptance)
Object.const_get @_class
if result.success?
Object.const_get(@_class).new(*acceptance.values_at(*@_attrs))
else
result
end
end
#
# @param [unknown] data
# @return [Hash]
#
def accept(data)
if data.respond_to? :attributes
# ActiveModel
data.attributes
elsif data.respond_to? :to_h
# Struct, OpenStruct, etc...
data.to_h
elsif data.is_a? Hash
data
elsif data.respond_to? :to_hash
# Hash convertible
data.to_hash
end
end
end
NameSchema = Dry::Schema.Params do
required(:name).filled(:string)
end
class NamedDataCreator
extend GuideRail
class_name :NamedData
schema NameSchema
yield_block do
def initialize(name: nil)
p ["constructor", name]
super
end
end
end
p NamedDataCreator.from(name: "a") # => ["constructor", "a"]\n#<data NamedData name="a">
p NamedDataCreator.from(foo: 1) # => #<Dry::Schema::Result{} errors={:name=>["is missing"]} path=[]>
p NamedData.new(name: "a") # => ["constructor", "a"]\n#<data NamedData name="a">
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment