Skip to content

Commit

Permalink
Merge pull request #466 from dry-rb/infer-nested-structs
Browse files Browse the repository at this point in the history
Infer types from nested structs (fixes #464)
  • Loading branch information
flash-gordon authored Aug 26, 2023
2 parents 3613f98 + fe34d2d commit 19007e8
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ group :tools do
end

group :benchmarks do
gem "actionpack", "~> 5.0"
gem "activemodel", "~> 5.0"
gem "actionpack"
gem "activemodel"
gem "benchmark-ips"
gem "hotch", platform: :mri
gem "virtus"
Expand Down
28 changes: 25 additions & 3 deletions lib/dry/schema/extensions/struct.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,42 @@
module Dry
module Schema
module Macros
class StructToSchema < ::Dry::Struct::Compiler
def call(struct)
visit(struct.to_ast)
end

# strip away structs from AST
def visit_struct(node)
_, ast = node
visit(ast)
end
end

Hash.option :struct_compiler, default: proc { StructToSchema.new(schema_dsl.config.types) }

Hash.prepend(::Module.new {
def call(*args)
if args.size >= 1 && args[0].is_a?(::Class) && args[0] <= ::Dry::Struct
if args.size >= 1 && struct?(args[0])
if block_given?
raise ArgumentError, "blocks are not supported when using "\
"a struct class (#{name.inspect} => #{args[0]})"
end

super(args[0].schema, *args.drop(1))
type(schema_dsl.types[name].constructor(args[0].schema))
schema = struct_compiler.(args[0])

super(schema, *args.drop(1))
type(schema_dsl.types[name].constructor(schema))
else
super
end
end

private

def struct?(type)
type.is_a?(::Class) && type <= ::Dry::Struct
end
})
end

Expand Down
32 changes: 32 additions & 0 deletions spec/extensions/struct_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,38 @@
)
end
end

context "complex struct" do
let(:struct) do
Class.new(Dry::Struct) do
attribute :name, Types::String
attribute :addresses, Types::Array do
attribute :city, Types::String
attribute? :street, Types::String
end
end
end

context "with valid input" do
let(:input) do
{foo: {name: "Jane", addresses: [{city: "New York"}]}}
end

it "has no errors" do
expect(schema.(input).errors.to_h).to eq({})
end
end

context "with empty nested array" do
let(:input) do
{foo: {name: "Jane", addresses: []}}
end

it "has no errors" do
expect(schema.(input).errors.to_h).to eq({})
end
end
end
end

context "value macro" do
Expand Down

0 comments on commit 19007e8

Please sign in to comment.