Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for custom builder methods #415

Merged
merged 1 commit into from Jan 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 30 additions & 3 deletions lib/dry/types.rb
Expand Up @@ -36,7 +36,7 @@ module Types

# @see Dry.Types
def self.module(*namespaces, default: :nominal, **aliases)
Module.new(container, *namespaces, default: default, **aliases)
::Module.new(container, *namespaces, default: default, **aliases)
end
deprecate_class_method :module, message: <<~DEPRECATION
Use Dry.Types() instead. Beware, it exports strict types by default, for old behavior use Dry.Types(default: :nominal). See more options in the changelog
Expand Down Expand Up @@ -129,22 +129,49 @@ def self.identifier(klass)
#
# @api private
def self.type_map
@type_map ||= Concurrent::Map.new
@type_map ||= ::Concurrent::Map.new
end

# @api private
def self.const_missing(const)
underscored = Inflector.underscore(const)

if container.keys.any? { |key| key.split(".")[0] == underscored }
raise NameError,
raise ::NameError,
"dry-types does not define constants for default types. "\
'You can access the predefined types with [], e.g. Dry::Types["integer"] '\
"or generate a module with types using Dry.Types()"
else
super
end
end

# Add a new type builder method. This is a public API for defining custom
# type constructors
#
# @example simple custom type constructor
# Dry::Types.define_builder(:or_nil) do |type|
# type.optional.fallback(nil)
# end
#
# Dry::Types["integer"].or_nil.("foo") # => nil
#
# @example fallback alias
# Dry::Types.define_builder(:or) do |type, fallback|
# type.fallback(fallback)
# end
#
# Dry::Types["integer"].or(100).("foo") # => 100
#
# @param [Symbol] method
# @param [#call] block
#
# @api public
def self.define_builder(method, &block)
Builder.define_method(method) do |*args|
block.(self, *args)
end
end
end

# Export registered types as a module with constants
Expand Down
2 changes: 1 addition & 1 deletion lib/dry/types/builder.rb
Expand Up @@ -42,7 +42,7 @@ def |(other)
#
# @api public
def optional
Types["strict.nil"] | self
Types["nil"] | self
end

# Turn a type into a constrained type
Expand Down
16 changes: 16 additions & 0 deletions spec/dry/types_spec.rb
Expand Up @@ -57,4 +57,20 @@ def self.[](value)
}.to raise_error(NameError, /dry-types does not define constants for default types/)
end
end

describe ".define_builder" do
it "adds a new builder method" do
Dry::Types.define_builder(:or_nil) { |type| type.optional.fallback(nil) }
constructed = Dry::Types["integer"].or_nil

expect(constructed.("123")).to be_nil
end

it "has support for arguments" do
Dry::Types.define_builder(:or) { |type, fallback| type.fallback(fallback) }
constructed = Dry::Types["integer"].or(300)

expect(constructed.("123")).to eql(300)
end
end
end