holotype 0.14.0 → 0.15.1
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/lib/holotype/attribute/definition/default_conflict_error.rb +14 -0
- data/lib/holotype/attribute/definition/no_collection_class_error.rb +18 -0
- data/lib/holotype/attribute/definition/no_value_class_error.rb +17 -0
- data/lib/holotype/attribute/definition/required_conflict_error.rb +14 -0
- data/lib/holotype/attribute/definition.rb +123 -0
- data/lib/holotype/attribute/frozen_modification_error.rb +15 -0
- data/lib/holotype/attribute/immutable_value_error.rb +15 -0
- data/lib/holotype/attribute/read_only_error.rb +15 -0
- data/lib/holotype/attribute.rb +43 -0
- data/lib/holotype/attributes_already_defined_error.rb +9 -0
- data/lib/holotype/collection_normalizer/expected_array_like_collection_error.rb +16 -0
- data/lib/holotype/collection_normalizer/expected_hash_like_collection_error.rb +16 -0
- data/lib/holotype/collection_normalizer.rb +155 -0
- data/lib/holotype/inheritance_disallowed_error.rb +9 -0
- data/lib/holotype/missing_required_attributes_error.rb +34 -0
- data/lib/holotype/value_normalizer.rb +27 -0
- data/lib/holotype/version.rb +3 -0
- data/lib/holotype.rb +182 -0
- metadata +23 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8de676ea732eeaafa954422a48c138e2e0fb6a8
|
4
|
+
data.tar.gz: d52556331331d5b80e9d79635895f1364e4115a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d615c9697c7d70431b710fe7882f03867f2949503ecb85fc6465b2e0051ce85b91b886ea3de9a30cc4f3382ca5e9302e97094ecb319f768a18da01d6687364fe
|
7
|
+
data.tar.gz: 17e20c875c8fd863a3536753228a258aed88b0556120a1af750c5b8b2066507fa182f8cb6a6830fbc174dac7f52e62a81b90ee74d1a64a1d9a997f2283a40807
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Holotype
|
2
|
+
class Attribute
|
3
|
+
class Definition
|
4
|
+
class DefaultConflictError < StandardError
|
5
|
+
def message; MESSAGE; end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
MESSAGE = 'Attribute definitions cannot have both a default value ' \
|
10
|
+
'and a default block'.freeze
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Holotype
|
2
|
+
class Attribute
|
3
|
+
class Definition
|
4
|
+
class NoCollectionClassError < StandardError
|
5
|
+
attr_reader :definition
|
6
|
+
|
7
|
+
def initialize definition
|
8
|
+
@definition = definition
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"No collection class for attribute definition: #{definition.name}"
|
13
|
+
.freeze
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Holotype
|
2
|
+
class Attribute
|
3
|
+
class Definition
|
4
|
+
class NoValueClassError < StandardError
|
5
|
+
attr_reader :definition
|
6
|
+
|
7
|
+
def initialize definition
|
8
|
+
@definition = definition
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"No value class for attribute definition: #{definition.name}".freeze
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Holotype
|
2
|
+
class Attribute
|
3
|
+
class Definition
|
4
|
+
class RequiredConflictError < StandardError
|
5
|
+
def message; MESSAGE; end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
MESSAGE = 'Attribute definitions cannot both be required and provide ' \
|
10
|
+
'a default'.freeze
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
%w[
|
2
|
+
default_conflict_error
|
3
|
+
no_collection_class_error
|
4
|
+
no_value_class_error
|
5
|
+
required_conflict_error
|
6
|
+
].each { |file| require_relative "definition/#{file}" }
|
7
|
+
|
8
|
+
require_relative 'definition/default_conflict_error.rb'
|
9
|
+
|
10
|
+
class Holotype
|
11
|
+
class Attribute
|
12
|
+
class Definition
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
def initialize name, **options, &default_block
|
16
|
+
@collection = options.fetch :collection, false
|
17
|
+
@immutable = options.fetch :immutable, false
|
18
|
+
@name = name
|
19
|
+
@read_only = options.fetch :read_only, false
|
20
|
+
@required = options.fetch :required, false
|
21
|
+
|
22
|
+
if options.key? :collection_class
|
23
|
+
@collection = true
|
24
|
+
@has_collection_class = true
|
25
|
+
@collection_class = options[:collection_class]
|
26
|
+
else
|
27
|
+
if collection?
|
28
|
+
@has_collection_class = true
|
29
|
+
@collection_class = Array
|
30
|
+
else
|
31
|
+
@has_collection_class = false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if options.key? :value_class
|
36
|
+
@has_value_class = true
|
37
|
+
@value_class = options[:value_class]
|
38
|
+
else
|
39
|
+
@has_value_class = false
|
40
|
+
end
|
41
|
+
|
42
|
+
if default_block
|
43
|
+
raise DefaultConflictError.new if options.key? :default
|
44
|
+
raise RequiredConflictError.new if @required
|
45
|
+
|
46
|
+
@default = default_block
|
47
|
+
@default_type = :dynamic
|
48
|
+
elsif options.key? :default
|
49
|
+
raise RequiredConflictError.new if @required
|
50
|
+
|
51
|
+
@default = options[:default]
|
52
|
+
@default_type = :constant
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def default receiver
|
57
|
+
case @default_type
|
58
|
+
when :constant then @default
|
59
|
+
when :dynamic then receiver.instance_exec(&@default).freeze
|
60
|
+
else nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def normalize value
|
65
|
+
if collection?
|
66
|
+
normalize_collection value
|
67
|
+
else
|
68
|
+
normalize_single value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def required?
|
73
|
+
!!@required
|
74
|
+
end
|
75
|
+
|
76
|
+
def has_value_class?
|
77
|
+
@has_value_class
|
78
|
+
end
|
79
|
+
|
80
|
+
def value_class
|
81
|
+
raise NoValueClassError.new self unless has_value_class?
|
82
|
+
@value_class
|
83
|
+
end
|
84
|
+
|
85
|
+
def collection?
|
86
|
+
!!@collection
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_collection_class?
|
90
|
+
@has_collection_class
|
91
|
+
end
|
92
|
+
|
93
|
+
def collection_class
|
94
|
+
return @collection_class if has_collection_class?
|
95
|
+
raise NoCollectionClassError.new self
|
96
|
+
end
|
97
|
+
|
98
|
+
def read_only?
|
99
|
+
!!@read_only
|
100
|
+
end
|
101
|
+
|
102
|
+
def immutable?
|
103
|
+
!!@immutable
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def normalize_single value
|
109
|
+
ValueNormalizer
|
110
|
+
.new(self)
|
111
|
+
.normalize value
|
112
|
+
end
|
113
|
+
|
114
|
+
def normalize_collection values
|
115
|
+
CollectionNormalizer.new(self).normalize values
|
116
|
+
end
|
117
|
+
|
118
|
+
def symbolize_keys hash
|
119
|
+
Hash[hash.map { |key, value| [key.to_sym, value] }]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Holotype
|
2
|
+
class Attribute
|
3
|
+
class FrozenModificationError < StandardError
|
4
|
+
attr_reader :attribute_name
|
5
|
+
|
6
|
+
def initialize attribute_name
|
7
|
+
@attribute_name = attribute_name.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
"Cannot modify value of `#{attribute_name}` in frozen object".freeze
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Holotype
|
2
|
+
class Attribute
|
3
|
+
class ImmutableValueError < StandardError
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize name
|
7
|
+
@name = name.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
"Cannot modify value of `#{name}` in immutable class".freeze
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
%w[
|
2
|
+
definition
|
3
|
+
frozen_modification_error
|
4
|
+
immutable_value_error
|
5
|
+
read_only_error
|
6
|
+
].each { |file| require_relative "attribute/#{file}" }
|
7
|
+
|
8
|
+
class Holotype
|
9
|
+
class Attribute
|
10
|
+
attr_reader :definition, :owner
|
11
|
+
|
12
|
+
def initialize owner, definition, **options
|
13
|
+
@definition = definition
|
14
|
+
@owner = owner
|
15
|
+
|
16
|
+
set_value options[:value] if options.key? :value
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
definition.name
|
21
|
+
end
|
22
|
+
|
23
|
+
def value
|
24
|
+
set_value definition.default owner unless @has_value
|
25
|
+
@value
|
26
|
+
end
|
27
|
+
|
28
|
+
def value= new_value
|
29
|
+
raise ImmutableValueError.new name if definition.immutable?
|
30
|
+
raise FrozenModificationError.new name if owner.frozen?
|
31
|
+
raise ReadOnlyError.new name if definition.read_only?
|
32
|
+
|
33
|
+
set_value new_value
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def set_value new_value
|
39
|
+
@has_value = true
|
40
|
+
@value = definition.normalize new_value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Holotype
|
2
|
+
class CollectionNormalizer
|
3
|
+
class ExpectedArrayLikeCollectionError < StandardError
|
4
|
+
attr_reader :attribute
|
5
|
+
|
6
|
+
def initialize attribute
|
7
|
+
@attribute = attribute
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
"Attribute `#{attribute}` expected Array-like collection, received " \
|
12
|
+
"Hash-like collection".freeze
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Holotype
|
2
|
+
class CollectionNormalizer
|
3
|
+
class ExpectedHashLikeCollectionError < StandardError
|
4
|
+
attr_reader :attribute
|
5
|
+
|
6
|
+
def initialize attribute
|
7
|
+
@attribute = attribute
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
"Attribute `#{attribute}` expected Hash-like collection, received " \
|
12
|
+
"Array-like collection".freeze
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
%w[
|
2
|
+
expected_array_like_collection_error
|
3
|
+
expected_hash_like_collection_error
|
4
|
+
].each { |file| require_relative "collection_normalizer/#{file}" }
|
5
|
+
|
6
|
+
class Holotype
|
7
|
+
class CollectionNormalizer
|
8
|
+
extend Memorandum
|
9
|
+
|
10
|
+
ARRAY_LIKE_METHODS = %i[
|
11
|
+
[]
|
12
|
+
[]=
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
HASH_LIKE_METHODS = %i[
|
16
|
+
[]
|
17
|
+
[]=
|
18
|
+
keys
|
19
|
+
values
|
20
|
+
]
|
21
|
+
|
22
|
+
attr_reader :definition
|
23
|
+
|
24
|
+
def initialize definition
|
25
|
+
@definition = definition
|
26
|
+
end
|
27
|
+
|
28
|
+
def normalize collection
|
29
|
+
check_likeness_of collection
|
30
|
+
|
31
|
+
result = classify normalized collection
|
32
|
+
|
33
|
+
if definition.immutable?
|
34
|
+
result.freeze
|
35
|
+
else
|
36
|
+
result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
memo def value_normalizer
|
43
|
+
ValueNormalizer.new definition
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_likeness_of collection
|
47
|
+
if hash_like?
|
48
|
+
raise ExpectedHashLikeCollectionError.new definition.name \
|
49
|
+
unless collection.nil? || object_is_hash_like?(collection)
|
50
|
+
elsif array_like?
|
51
|
+
raise ExpectedArrayLikeCollectionError.new definition.name \
|
52
|
+
unless collection.nil? || object_is_array_like?(collection)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def hash_like?
|
57
|
+
return false unless definition.has_collection_class?
|
58
|
+
|
59
|
+
class_is_hash_like? definition.collection_class
|
60
|
+
end
|
61
|
+
|
62
|
+
def array_like?
|
63
|
+
return false unless definition.has_collection_class?
|
64
|
+
return false if hash_like?
|
65
|
+
|
66
|
+
class_is_array_like? definition.collection_class
|
67
|
+
end
|
68
|
+
|
69
|
+
def class_is_array_like? klass
|
70
|
+
return false if class_is_hash_like? klass
|
71
|
+
|
72
|
+
instance_methods = klass.instance_methods
|
73
|
+
|
74
|
+
ARRAY_LIKE_METHODS.all? do |method|
|
75
|
+
instance_methods.include? method
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def class_is_hash_like? klass
|
80
|
+
instance_methods = klass.instance_methods
|
81
|
+
|
82
|
+
HASH_LIKE_METHODS.all? do |method|
|
83
|
+
instance_methods.include? method
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def object_is_array_like? object
|
88
|
+
return false if object_is_hash_like? object
|
89
|
+
|
90
|
+
ARRAY_LIKE_METHODS.all? do |method|
|
91
|
+
object.respond_to? method
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def object_is_hash_like? object
|
96
|
+
HASH_LIKE_METHODS.all? do |method|
|
97
|
+
object.respond_to? method
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def hash_like collection
|
102
|
+
return collection unless definition.has_collection_class?
|
103
|
+
|
104
|
+
definition
|
105
|
+
.collection_class
|
106
|
+
.new
|
107
|
+
.tap do |result|
|
108
|
+
collection.each { |key, value| result[key] = value }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def array_like collection
|
113
|
+
return collection unless definition.has_collection_class?
|
114
|
+
|
115
|
+
definition
|
116
|
+
.collection_class
|
117
|
+
.new
|
118
|
+
.tap do |result|
|
119
|
+
collection.each { |value| result << value }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def classify collection
|
124
|
+
if object_is_hash_like? collection
|
125
|
+
hash_like collection
|
126
|
+
else
|
127
|
+
array_like collection
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def normalized collection
|
132
|
+
if hash_like?
|
133
|
+
normalized_hash collection
|
134
|
+
else
|
135
|
+
normalized_array collection
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def normalized_hash collection
|
140
|
+
collection ||= Hash[]
|
141
|
+
|
142
|
+
Hash[
|
143
|
+
collection.map do |key, value|
|
144
|
+
[key, value_normalizer.normalize(value)]
|
145
|
+
end
|
146
|
+
]
|
147
|
+
end
|
148
|
+
|
149
|
+
def normalized_array collection
|
150
|
+
collection ||= []
|
151
|
+
|
152
|
+
collection.map { |value| value_normalizer.normalize value }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Holotype
|
2
|
+
class MissingRequiredAttributesError < StandardError
|
3
|
+
attr_reader :attributes, :original_class
|
4
|
+
|
5
|
+
def initialize original_class, attributes
|
6
|
+
@attributes = attributes
|
7
|
+
@original_class = original_class
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
"Class `#{original_class.name}` requires the following attributes:" \
|
12
|
+
"#{format_list required_attributes}" \
|
13
|
+
"\n\n" \
|
14
|
+
"Missing attributes:" \
|
15
|
+
"#{format_list attributes}".freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def required_attributes
|
21
|
+
original_class
|
22
|
+
.attributes
|
23
|
+
.values
|
24
|
+
.select(&:required?)
|
25
|
+
.map(&:name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def format_list attributes
|
29
|
+
attributes
|
30
|
+
.map { |name| "\n * #{name}" }
|
31
|
+
.join
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Holotype
|
2
|
+
class ValueNormalizer
|
3
|
+
attr_reader :definition
|
4
|
+
|
5
|
+
def initialize definition
|
6
|
+
@definition = definition
|
7
|
+
end
|
8
|
+
|
9
|
+
def normalize value
|
10
|
+
result = if definition.has_value_class?
|
11
|
+
if value.nil?
|
12
|
+
nil
|
13
|
+
else
|
14
|
+
definition.value_class.new value
|
15
|
+
end
|
16
|
+
else
|
17
|
+
value
|
18
|
+
end
|
19
|
+
|
20
|
+
if definition.immutable?
|
21
|
+
result.freeze
|
22
|
+
else
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/holotype.rb
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
%w[
|
2
|
+
memorandum
|
3
|
+
].each { |gem| require gem }
|
4
|
+
|
5
|
+
%i[
|
6
|
+
attribute
|
7
|
+
attributes_already_defined_error
|
8
|
+
collection_normalizer
|
9
|
+
inheritance_disallowed_error
|
10
|
+
missing_required_attributes_error
|
11
|
+
value_normalizer
|
12
|
+
version
|
13
|
+
].each { |name| require_relative "holotype/#{name}.rb" }
|
14
|
+
|
15
|
+
class Holotype
|
16
|
+
# Singleton Definition
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def attribute name, **options, &default
|
20
|
+
# symbolize name
|
21
|
+
name = name.to_sym
|
22
|
+
|
23
|
+
# prepare options
|
24
|
+
processed_options = if immutable?
|
25
|
+
[
|
26
|
+
__default_attribute_options,
|
27
|
+
options,
|
28
|
+
IMMUTABLE_OPTION,
|
29
|
+
].reduce :merge
|
30
|
+
else
|
31
|
+
[
|
32
|
+
__default_attribute_options,
|
33
|
+
options,
|
34
|
+
].reduce :merge
|
35
|
+
end
|
36
|
+
|
37
|
+
# create attribute definition
|
38
|
+
attribute = Attribute::Definition.new name,
|
39
|
+
**processed_options,
|
40
|
+
&default
|
41
|
+
|
42
|
+
# store the attribute definition
|
43
|
+
attributes[name] = attribute
|
44
|
+
|
45
|
+
# create an attribute reader
|
46
|
+
define_method name do
|
47
|
+
self.attributes[name].value
|
48
|
+
end
|
49
|
+
|
50
|
+
# create an attribute writer
|
51
|
+
define_method "#{name}=" do |value|
|
52
|
+
self.attributes[name].value = value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def attributes
|
57
|
+
@attributes ||= Hash[]
|
58
|
+
end
|
59
|
+
|
60
|
+
def make_immutable
|
61
|
+
raise AttributesAlreadyDefinedError.new if attributes.count != 0
|
62
|
+
|
63
|
+
define_singleton_method :inherited do |_|
|
64
|
+
raise InheritanceDisallowedError.new
|
65
|
+
end
|
66
|
+
|
67
|
+
@immutable = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def immutable?
|
71
|
+
!!@immutable
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_attribute_options **options
|
75
|
+
@default_attribute_options = options.freeze
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
IMMUTABLE_OPTION = Hash[immutable: true].freeze
|
81
|
+
|
82
|
+
def __default_attribute_options
|
83
|
+
@default_attribute_options || Hash[]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Instance Definition
|
88
|
+
|
89
|
+
attr_reader :attributes
|
90
|
+
|
91
|
+
def initialize **attributes
|
92
|
+
__holotype_check_for_missing_required attributes
|
93
|
+
__holotype_store attributes
|
94
|
+
end
|
95
|
+
|
96
|
+
def frozen?
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_hash
|
101
|
+
Hash[
|
102
|
+
attributes
|
103
|
+
.map do |key, attribute|
|
104
|
+
definition = attribute.definition
|
105
|
+
|
106
|
+
value = __holotype_hashify attribute.value
|
107
|
+
|
108
|
+
[key, value]
|
109
|
+
end
|
110
|
+
]
|
111
|
+
end
|
112
|
+
|
113
|
+
def == other
|
114
|
+
return false unless self.class == other.class
|
115
|
+
|
116
|
+
attributes.all? do |name, attribute|
|
117
|
+
attribute.value == other.attributes[name].value
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def with **attributes
|
122
|
+
self.class.new to_hash.merge attributes
|
123
|
+
end
|
124
|
+
|
125
|
+
def inspect
|
126
|
+
data = to_hash
|
127
|
+
.map { |attribute, value| "#{attribute}: #{value.inspect}" }
|
128
|
+
.join(', ')
|
129
|
+
|
130
|
+
"#{self.class.name}(#{data})"
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def __holotype_hashify value
|
136
|
+
if value.respond_to? :to_hash
|
137
|
+
value.to_hash
|
138
|
+
elsif value.kind_of? Enumerable
|
139
|
+
value.map { |value| __holotype_hashify value }
|
140
|
+
else
|
141
|
+
value
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def __holotype_check_for_missing_required attributes
|
146
|
+
self
|
147
|
+
.class
|
148
|
+
.attributes
|
149
|
+
.flat_map do |name, attribute|
|
150
|
+
# skip non-required attributes
|
151
|
+
next [] unless attribute.required?
|
152
|
+
|
153
|
+
# skip attributes with provided values
|
154
|
+
next [] if attributes.key? name
|
155
|
+
|
156
|
+
[name]
|
157
|
+
end
|
158
|
+
.tap do |missing_attributes|
|
159
|
+
next if missing_attributes.empty?
|
160
|
+
raise MissingRequiredAttributesError.new self.class, missing_attributes
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def __holotype_store attributes
|
165
|
+
@attributes = Hash[
|
166
|
+
self
|
167
|
+
.class
|
168
|
+
.attributes
|
169
|
+
.map do |name, definition|
|
170
|
+
options = if attributes.key? name
|
171
|
+
Hash value: attributes[name]
|
172
|
+
else
|
173
|
+
Hash[]
|
174
|
+
end
|
175
|
+
|
176
|
+
attribute = Attribute.new self, definition, **options
|
177
|
+
|
178
|
+
[name, attribute]
|
179
|
+
end
|
180
|
+
].freeze
|
181
|
+
end
|
182
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: holotype
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Lude
|
@@ -58,20 +58,38 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 2.1
|
61
|
+
version: '2.1'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 2.1
|
68
|
+
version: '2.1'
|
69
69
|
description:
|
70
70
|
email: rob@ertlu.de
|
71
71
|
executables: []
|
72
72
|
extensions: []
|
73
73
|
extra_rdoc_files: []
|
74
|
-
files:
|
74
|
+
files:
|
75
|
+
- lib/holotype.rb
|
76
|
+
- lib/holotype/attribute.rb
|
77
|
+
- lib/holotype/attribute/definition.rb
|
78
|
+
- lib/holotype/attribute/definition/default_conflict_error.rb
|
79
|
+
- lib/holotype/attribute/definition/no_collection_class_error.rb
|
80
|
+
- lib/holotype/attribute/definition/no_value_class_error.rb
|
81
|
+
- lib/holotype/attribute/definition/required_conflict_error.rb
|
82
|
+
- lib/holotype/attribute/frozen_modification_error.rb
|
83
|
+
- lib/holotype/attribute/immutable_value_error.rb
|
84
|
+
- lib/holotype/attribute/read_only_error.rb
|
85
|
+
- lib/holotype/attributes_already_defined_error.rb
|
86
|
+
- lib/holotype/collection_normalizer.rb
|
87
|
+
- lib/holotype/collection_normalizer/expected_array_like_collection_error.rb
|
88
|
+
- lib/holotype/collection_normalizer/expected_hash_like_collection_error.rb
|
89
|
+
- lib/holotype/inheritance_disallowed_error.rb
|
90
|
+
- lib/holotype/missing_required_attributes_error.rb
|
91
|
+
- lib/holotype/value_normalizer.rb
|
92
|
+
- lib/holotype/version.rb
|
75
93
|
homepage: https://www.github.com/robertlude/holotype
|
76
94
|
licenses:
|
77
95
|
- MIT
|
@@ -92,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
110
|
version: '0'
|
93
111
|
requirements: []
|
94
112
|
rubyforge_project:
|
95
|
-
rubygems_version: 2.
|
113
|
+
rubygems_version: 2.6.11
|
96
114
|
signing_key:
|
97
115
|
specification_version: 4
|
98
116
|
summary: Simple models
|