mixture 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/Gemfile +5 -1
- data/Gemfile.lock +3 -1
- data/README.md +25 -2
- data/lib/mixture/attribute.rb +6 -3
- data/lib/mixture/coerce/array.rb +20 -5
- data/lib/mixture/coerce/base.rb +62 -26
- data/lib/mixture/coerce/class.rb +12 -0
- data/lib/mixture/coerce/date.rb +10 -10
- data/lib/mixture/coerce/datetime.rb +10 -10
- data/lib/mixture/coerce/float.rb +9 -9
- data/lib/mixture/coerce/hash.rb +10 -4
- data/lib/mixture/coerce/integer.rb +9 -9
- data/lib/mixture/coerce/nil.rb +10 -10
- data/lib/mixture/coerce/object.rb +13 -13
- data/lib/mixture/coerce/rational.rb +9 -9
- data/lib/mixture/coerce/set.rb +12 -4
- data/lib/mixture/coerce/string.rb +9 -9
- data/lib/mixture/coerce/symbol.rb +4 -4
- data/lib/mixture/coerce/time.rb +10 -9
- data/lib/mixture/coerce.rb +28 -8
- data/lib/mixture/extensions/coercable.rb +2 -10
- data/lib/mixture/extensions.rb +15 -9
- data/lib/mixture/types/access.rb +45 -0
- data/lib/mixture/types/array.rb +13 -0
- data/lib/mixture/types/boolean.rb +20 -0
- data/lib/mixture/types/class.rb +16 -0
- data/lib/mixture/types/collection.rb +14 -0
- data/lib/mixture/types/date.rb +13 -0
- data/lib/mixture/types/datetime.rb +13 -0
- data/lib/mixture/types/enumerable.rb +14 -0
- data/lib/mixture/types/float.rb +12 -0
- data/lib/mixture/types/hash.rb +15 -0
- data/lib/mixture/types/integer.rb +12 -0
- data/lib/mixture/types/nil.rb +11 -0
- data/lib/mixture/types/numeric.rb +12 -0
- data/lib/mixture/types/object.rb +24 -0
- data/lib/mixture/types/rational.rb +13 -0
- data/lib/mixture/types/set.rb +16 -0
- data/lib/mixture/types/string.rb +12 -0
- data/lib/mixture/types/symbol.rb +14 -0
- data/lib/mixture/types/time.rb +13 -0
- data/lib/mixture/types/type.rb +171 -0
- data/lib/mixture/types.rb +110 -0
- data/lib/mixture/version.rb +1 -1
- data/lib/mixture.rb +22 -2
- data/mixture.gemspec +2 -3
- metadata +31 -37
- data/lib/mixture/type.rb +0 -188
data/lib/mixture/coerce.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require "mixture/coerce/base"
|
4
|
-
require "mixture/coerce/array"
|
5
4
|
require "mixture/coerce/date"
|
5
|
+
require "mixture/coerce/array"
|
6
6
|
require "mixture/coerce/datetime"
|
7
7
|
require "mixture/coerce/float"
|
8
8
|
require "mixture/coerce/hash"
|
@@ -32,24 +32,46 @@ module Mixture
|
|
32
32
|
#
|
33
33
|
# @return [Hash{Mixture::Type => Mixture::Coerce::Base}]
|
34
34
|
def self.coercers
|
35
|
-
@_coercers ||=
|
35
|
+
@_coercers ||= ThreadSafe::Hash.new
|
36
36
|
end
|
37
37
|
|
38
38
|
# Returns a block that takes one argument: the value.
|
39
39
|
#
|
40
|
-
# @param from [Mixture::Type]
|
40
|
+
# @param from [Mixture::Types::Type]
|
41
41
|
# The type to coerce from.
|
42
|
-
# @param to [Mixture::Type]
|
42
|
+
# @param to [Mixture::Types::Type]
|
43
43
|
# The type to coerce to.
|
44
|
-
# @return [Proc{(Object) => Object}]
|
44
|
+
# @return [Proc{(Object, Mixture::Types::Object) => Object}]
|
45
45
|
def self.coerce(from, to)
|
46
46
|
coercers
|
47
47
|
.fetch(from) { fail CoercionError, "No coercer for #{from}" }
|
48
48
|
.to(to)
|
49
49
|
end
|
50
50
|
|
51
|
+
# Performs the actual coercion, since blocks require a value and
|
52
|
+
# type arguments.
|
53
|
+
#
|
54
|
+
# @param type [Mixture::Types::Type] The type to coerce to.
|
55
|
+
# @param value [Object] The value to coerce.
|
56
|
+
# @return [Object] The coerced value.
|
57
|
+
def self.perform(type, value)
|
58
|
+
to = Types.infer(type)
|
59
|
+
from = Types.infer(value)
|
60
|
+
block = coerce(from, to)
|
61
|
+
|
62
|
+
begin
|
63
|
+
block.call(value, to)
|
64
|
+
rescue CoercionError
|
65
|
+
raise
|
66
|
+
rescue StandardError => e
|
67
|
+
raise CoercionError, "#{e.class}: #{e.message}", e.backtrace
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
51
71
|
# Registers the default coercions.
|
52
|
-
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
def self.finalize
|
53
75
|
register Array
|
54
76
|
register Date
|
55
77
|
register DateTime
|
@@ -64,7 +86,5 @@ module Mixture
|
|
64
86
|
register Symbol
|
65
87
|
register Time
|
66
88
|
end
|
67
|
-
|
68
|
-
load
|
69
89
|
end
|
70
90
|
end
|
@@ -7,6 +7,7 @@ module Mixture
|
|
7
7
|
# Performs the coercion for the attribute and the value.
|
8
8
|
# It is used in a `:update` callback.
|
9
9
|
#
|
10
|
+
# @see Coerce.perform
|
10
11
|
# @param attribute [Attribute] The attribute
|
11
12
|
# @param value [Object] The new value.
|
12
13
|
# @return [Object] The new new value.
|
@@ -14,16 +15,7 @@ module Mixture
|
|
14
15
|
# running.
|
15
16
|
def self.coerce_attribute(attribute, value)
|
16
17
|
return value unless attribute.options[:type]
|
17
|
-
|
18
|
-
value_type = Type.infer(value)
|
19
|
-
|
20
|
-
block = Coerce.coerce(value_type, attr_type)
|
21
|
-
|
22
|
-
begin
|
23
|
-
block.call(value)
|
24
|
-
rescue StandardError => e
|
25
|
-
raise CoercionError, "#{e.class}: #{e.message}", e.backtrace
|
26
|
-
end
|
18
|
+
Coerce.perform(attribute.options[:type], value)
|
27
19
|
end
|
28
20
|
|
29
21
|
# Called by Ruby when the module is included.
|
data/lib/mixture/extensions.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require "mixture/extensions/attributable"
|
4
|
+
require "mixture/extensions/coercable"
|
5
|
+
require "mixture/extensions/hashable"
|
6
|
+
require "mixture/extensions/validatable"
|
7
|
+
|
3
8
|
module Mixture
|
4
9
|
# All of the extensions of mixture. Handles registration of
|
5
10
|
# extensions, so that extensions can be referend by a name instead
|
@@ -32,14 +37,15 @@ module Mixture
|
|
32
37
|
@_extensions ||= {}
|
33
38
|
end
|
34
39
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
# Finalizes the extension module. It registers the extensions in
|
41
|
+
# Mixture.
|
42
|
+
#
|
43
|
+
# @return [void]
|
44
|
+
def self.finalize
|
45
|
+
register :attribute, Attributable
|
46
|
+
register :coerce, Coercable
|
47
|
+
register :hash, Hashable
|
48
|
+
register :validate, Validatable
|
49
|
+
end
|
44
50
|
end
|
45
51
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# Used by certain types to create subtypes of those types. This
|
6
|
+
# is useful in collections and hashes, wherein the collection
|
7
|
+
# members and hash keys/values all have types as well (and need
|
8
|
+
# to be coerced to them).
|
9
|
+
module Access
|
10
|
+
# Creates a subtype with the given member types. Any number of
|
11
|
+
# subtypes may be used. If a class hasn't been created with the
|
12
|
+
# subtypes, it creates a new one.
|
13
|
+
#
|
14
|
+
# @see #create
|
15
|
+
# @param subs [Object] The subtypes to use.
|
16
|
+
# @return [Class] The new subtype.
|
17
|
+
def [](*subs)
|
18
|
+
options[:types].fetch([self, subs]) do
|
19
|
+
create(subs)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Actually creates the subtype. This should never be called
|
26
|
+
# outside of {.[]}. If `:noinfer` is set in the supertype's
|
27
|
+
# options, it doesn't infer the type of each subtype; otherwise,
|
28
|
+
# it does.
|
29
|
+
#
|
30
|
+
# @param subs [Array<Object>] The subtypes.
|
31
|
+
# @return [Class] The new subtype.
|
32
|
+
def create(subs)
|
33
|
+
subtype = ::Class.new(self)
|
34
|
+
members = if options[:noinfer]
|
35
|
+
subs
|
36
|
+
else
|
37
|
+
subs.map { |sub| Types.infer(sub) }
|
38
|
+
end
|
39
|
+
name = "#{inspect}[#{members.join(', ')}]"
|
40
|
+
subtype.options.merge!(members: members, name: name)
|
41
|
+
options[:types][[self, subs]] = subtype
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# The array type. This is a collection, and as such, can be used
|
6
|
+
# with the `.[]` accessor.
|
7
|
+
class Array < Collection
|
8
|
+
options[:primitive] = ::Array
|
9
|
+
options[:method] = :to_array
|
10
|
+
as :array
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# The boolean type. Unfortunately, ruby doesn't have a clear cut
|
6
|
+
# boolean primitive (it has `TrueClass` and `FalseClass`, but no
|
7
|
+
# `Boolean` class), so we set the primitive to nil to prevent
|
8
|
+
# odd stuff from happening. It can still be matched by other
|
9
|
+
# values.
|
10
|
+
class Boolean < Object
|
11
|
+
options[:primitive] = nil
|
12
|
+
as :bool, :boolean, true, false
|
13
|
+
|
14
|
+
constraints.clear
|
15
|
+
constraint do |value|
|
16
|
+
[TrueClass, FalseClass].include?(value.class)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A class type. This can be subtyped, and is subtyped for
|
6
|
+
# non-primitive classes.
|
7
|
+
class Class < Object
|
8
|
+
options[:primitive] = ::Class
|
9
|
+
options[:noinfer] = true
|
10
|
+
options[:member] = Object
|
11
|
+
options[:method] = :to_class
|
12
|
+
options[:types] = ThreadSafe::Cache.new
|
13
|
+
extend Access
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A collection. This is recognized as an actual set of values,
|
6
|
+
# rather than just being enumerable. The set of values have a
|
7
|
+
# defined type.
|
8
|
+
class Collection < Enumerable
|
9
|
+
options[:members] = [Object]
|
10
|
+
options[:types] = ThreadSafe::Cache.new
|
11
|
+
extend Access
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A date type. I don't know why ruby has Date, DateTime, and
|
6
|
+
# Time, but someone thinks we need it.
|
7
|
+
class Date < Object
|
8
|
+
options[:primitive] = ::Date
|
9
|
+
options[:method] = :to_date
|
10
|
+
as :date
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A datetime type. I don't know why Ruby has a Date, DateTime, and
|
6
|
+
# Time type, but someone thinks we needs it.
|
7
|
+
class DateTime < Object
|
8
|
+
options[:primitive] = ::DateTime
|
9
|
+
options[:method] = :to_datetime
|
10
|
+
as :datetime
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# An enumerable. This is any value that inherits `Enumerable`.
|
6
|
+
class Enumerable < Object
|
7
|
+
options[:primitive] = ::Enumerable
|
8
|
+
constraint do |value|
|
9
|
+
# include Enumerable adds Enumerable to the ancestors list.
|
10
|
+
value.class.ancestors.include?(::Enumerable)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A hash. This is also accessable, and expects two member types;
|
6
|
+
# one for the keys, and one for the values.
|
7
|
+
class Hash < Object
|
8
|
+
options[:primitive] = ::Hash
|
9
|
+
options[:members] = [Object, Object]
|
10
|
+
options[:method] = :to_hash
|
11
|
+
options[:types] = ThreadSafe::Cache.new
|
12
|
+
extend Access
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A numeric. This is inherited by {Integer}, {Float}, and
|
6
|
+
# {Rational}. Those should be used for coercion instead of this.
|
7
|
+
# This just helps represent the type hirearchy.
|
8
|
+
class Numeric < Object
|
9
|
+
options[:primitive] = ::Numeric
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# An object. This adds the basic constraints all types (that
|
6
|
+
# inherit from Object) have; i.e., it must be an object, and
|
7
|
+
# it must be the type's primitive.
|
8
|
+
class Object < Type
|
9
|
+
options[:primitive] = ::Object
|
10
|
+
options[:method] = :to_object
|
11
|
+
as :object
|
12
|
+
|
13
|
+
constraint do |value|
|
14
|
+
# This may seem a bit odd, but this returns false for
|
15
|
+
# BasicObject; and since this is meant to represent Objects,
|
16
|
+
# we want to make sure that the value isn't a BasicObject.
|
17
|
+
# rubocop:disable Style/CaseEquality
|
18
|
+
::Object === value
|
19
|
+
# rubocop:enable Style/CaseEquality
|
20
|
+
end
|
21
|
+
constraint { |value| value.is_a?(options[:primitive]) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A rational. I've personally never used this, but I don't see it
|
6
|
+
# as a bad thing.
|
7
|
+
class Rational < Numeric
|
8
|
+
options[:primitive] = ::Rational
|
9
|
+
options[:method] = :to_rational
|
10
|
+
as :rational
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A set. This is similar to array, but with brilliant iteration
|
6
|
+
# and indexing(?) times.
|
7
|
+
#
|
8
|
+
# @see Array
|
9
|
+
# @see http://ruby-doc.org/stdlib/libdoc/set/rdoc/Set.html
|
10
|
+
class Set < Collection
|
11
|
+
options[:primitive] = ::Set
|
12
|
+
options[:method] = :to_set
|
13
|
+
as :set
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A symbol. Don't really use this for coercion; in Ruby 2.2.2,
|
6
|
+
# they added garbage collection for symbols; however, it is still
|
7
|
+
# not a brilliant idea to turn user input into symbols.
|
8
|
+
class Symbol < Object
|
9
|
+
options[:primitive] = ::Symbol
|
10
|
+
options[:method] = :to_symbol
|
11
|
+
as :symbol
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A time type. I'm not sure why Ruby has a Date, DateTime, and
|
6
|
+
# Time object, but someone thinks we need it.
|
7
|
+
class Time < Object
|
8
|
+
options[:primitive] = ::Time
|
9
|
+
options[:method] = :to_time
|
10
|
+
as :time
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mixture
|
4
|
+
module Types
|
5
|
+
# A single type. A type is _never_ instantized; it is used as a
|
6
|
+
# class to represent a type of value.
|
7
|
+
class Type
|
8
|
+
private_class_method :new
|
9
|
+
private_class_method :allocate
|
10
|
+
|
11
|
+
# The options for the type. This is inherited by subtypes.
|
12
|
+
#
|
13
|
+
# @return [Hash{Symbol => Object}]
|
14
|
+
def self.options
|
15
|
+
@options ||= ThreadSafe::Hash.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Constraints on the type. A value will not match the type if
|
19
|
+
# any of these constraints fail. This is inherited by subtypes.
|
20
|
+
#
|
21
|
+
# @return [Array<Proc{(Object) => Boolean}>]
|
22
|
+
def self.constraints
|
23
|
+
@constraints ||= ThreadSafe::Array.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns all of the names that this type can go under. This is
|
27
|
+
# used for {Types.mappings} and for inference.
|
28
|
+
#
|
29
|
+
# @see Types.mappings
|
30
|
+
# @return [Array<Symbol, Object>]
|
31
|
+
def self.mappings
|
32
|
+
@mappings ||= ThreadSafe::Array.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sets some names that this type can go under. This is used
|
36
|
+
# for {Type.mappings} and for inference.
|
37
|
+
#
|
38
|
+
# @see Types.mappings
|
39
|
+
# @see .mappings
|
40
|
+
# @param names [Symbol, Object]
|
41
|
+
# @return [void]
|
42
|
+
def self.as(*names)
|
43
|
+
mappings.concat(names)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Called by ruby when a class inherits this class. This just
|
47
|
+
# propogates the options and constraints to the new subclass.
|
48
|
+
#
|
49
|
+
# @param sub [Class] The new subclass.
|
50
|
+
# @return [void]
|
51
|
+
def self.inherited(sub)
|
52
|
+
Types.types << sub unless sub.anonymous?
|
53
|
+
sub.options.merge!(options)
|
54
|
+
sub.constraints.concat(constraints)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Checks if the given value passes all of the constraints
|
58
|
+
# defined on this type. Each constraint is executed within the
|
59
|
+
# context of the class, to provide access to {.options}.
|
60
|
+
#
|
61
|
+
# @param value [Object] The object to check.
|
62
|
+
# @return [Boolean]
|
63
|
+
def self.matches?(value)
|
64
|
+
constraints.all? do |constraint|
|
65
|
+
class_exec(value, &constraint)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Determines if this type is equal to another type. This is
|
70
|
+
# used by Ruby's hash, and is used to make an anonymous type
|
71
|
+
# equal to its supertype (e.g.
|
72
|
+
# `Types::Array[Types::Integer] == Types::Array`), mainly for
|
73
|
+
# coercion.
|
74
|
+
#
|
75
|
+
# @param other [Object]
|
76
|
+
# @return [Boolean]
|
77
|
+
def self.eql?(other)
|
78
|
+
if anonymous?
|
79
|
+
superclass == other
|
80
|
+
elsif other.respond_to?(:anonymous?) && other.anonymous?
|
81
|
+
other.superclass.eql?(self)
|
82
|
+
else
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# (see .eql?)
|
88
|
+
def self.==(other)
|
89
|
+
eql?(other)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Used by ruby's Hash, this determines the hash of the type. If
|
93
|
+
# this is anonymous, it uses its supertype's hash.
|
94
|
+
#
|
95
|
+
# @see .eql?
|
96
|
+
# @return [Numeric]
|
97
|
+
def self.hash
|
98
|
+
if anonymous?
|
99
|
+
superclass.hash
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# If this class is anonymous. This is counting on the fact that
|
106
|
+
# the name of an anonymous class is nil; however, assigning a
|
107
|
+
# class to a constant on initialization of a class will make
|
108
|
+
# that class non-anonymous.
|
109
|
+
#
|
110
|
+
# @return [Boolean]
|
111
|
+
def self.anonymous?
|
112
|
+
name.nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
# Inspects the class. If the class is anonymous, it uses the
|
116
|
+
# `:name` value in the options if it exists. Otherwise, it
|
117
|
+
# passes it up the chain.
|
118
|
+
#
|
119
|
+
# @return [String]
|
120
|
+
def self.inspect
|
121
|
+
to_s
|
122
|
+
end
|
123
|
+
|
124
|
+
# (see .inspect)
|
125
|
+
def self.to_s
|
126
|
+
if anonymous? && options.key?(:name)
|
127
|
+
options[:name]
|
128
|
+
else
|
129
|
+
super
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# This is used to determine if a specific object is this type.
|
134
|
+
# There can be many constraints, and they're all used to check
|
135
|
+
# the given object.
|
136
|
+
#
|
137
|
+
# @note Constraints are not meant for _validation_. Constraints
|
138
|
+
# are **purely** meant for identification, and should be used
|
139
|
+
# as such.
|
140
|
+
#
|
141
|
+
# @overload self.constraint(value)
|
142
|
+
# Adds the value as a constraint. Ideally, this should
|
143
|
+
# respond to `#call`.
|
144
|
+
#
|
145
|
+
# @example Subclass constraint.
|
146
|
+
# constraint(->(value) { value.is_a?(String) })
|
147
|
+
# @param value [#call] The constraint to add.
|
148
|
+
# @return [void]
|
149
|
+
#
|
150
|
+
# @overload self.constraint(&block)
|
151
|
+
# Adds the block as a constraint.
|
152
|
+
#
|
153
|
+
# @example Subclass constraint.
|
154
|
+
# constraint { |value| value.is_a?(String) }
|
155
|
+
# @yield [value]
|
156
|
+
# @yieldparam value [Object] The value to check.
|
157
|
+
# @yieldreturn [Boolean] If the constraint was passed.
|
158
|
+
# @return [void]
|
159
|
+
def self.constraint(value = Undefined, &block)
|
160
|
+
if block_given?
|
161
|
+
constraints << block
|
162
|
+
elsif value != Undefined
|
163
|
+
constraints << value
|
164
|
+
else
|
165
|
+
fail ArgumentError, "Expected an argument or a block, " \
|
166
|
+
"got neither"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|