mixture 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|