wardrobe 0.1.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 +7 -0
- data/.codeclimate.yml +10 -0
- data/.gitignore +19 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +18 -0
- data/Guardfile +42 -0
- data/LICENSE +19 -0
- data/README.md +227 -0
- data/Rakefile +20 -0
- data/examples/new_york_times_article.rb +55 -0
- data/lib/wardrobe/attribute.rb +76 -0
- data/lib/wardrobe/attribute_store.rb +16 -0
- data/lib/wardrobe/block_setup.rb +124 -0
- data/lib/wardrobe/boolean.rb +6 -0
- data/lib/wardrobe/class_methods.rb +127 -0
- data/lib/wardrobe/getter_setter.rb +48 -0
- data/lib/wardrobe/instance_methods.rb +56 -0
- data/lib/wardrobe/module_methods.rb +51 -0
- data/lib/wardrobe/option.rb +30 -0
- data/lib/wardrobe/option_store.rb +22 -0
- data/lib/wardrobe/plugin.rb +39 -0
- data/lib/wardrobe/plugin_store.rb +28 -0
- data/lib/wardrobe/plugins/alias_setters.rb +35 -0
- data/lib/wardrobe/plugins/coercible/refinements/array.rb +63 -0
- data/lib/wardrobe/plugins/coercible/refinements/boolean.rb +36 -0
- data/lib/wardrobe/plugins/coercible/refinements/date.rb +31 -0
- data/lib/wardrobe/plugins/coercible/refinements/date_time.rb +29 -0
- data/lib/wardrobe/plugins/coercible/refinements/float.rb +23 -0
- data/lib/wardrobe/plugins/coercible/refinements/hash.rb +65 -0
- data/lib/wardrobe/plugins/coercible/refinements/integer.rb +23 -0
- data/lib/wardrobe/plugins/coercible/refinements/object.rb +15 -0
- data/lib/wardrobe/plugins/coercible/refinements/set.rb +33 -0
- data/lib/wardrobe/plugins/coercible/refinements/string.rb +21 -0
- data/lib/wardrobe/plugins/coercible/refinements/symbol.rb +21 -0
- data/lib/wardrobe/plugins/coercible/refinements/time.rb +30 -0
- data/lib/wardrobe/plugins/coercible/refinements/unsupported_error.rb +11 -0
- data/lib/wardrobe/plugins/coercible.rb +52 -0
- data/lib/wardrobe/plugins/configurable/configurable_store.rb +26 -0
- data/lib/wardrobe/plugins/configurable.rb +40 -0
- data/lib/wardrobe/plugins/default.rb +33 -0
- data/lib/wardrobe/plugins/dirty_tracker.rb +66 -0
- data/lib/wardrobe/plugins/equality.rb +19 -0
- data/lib/wardrobe/plugins/html_initializer.rb +38 -0
- data/lib/wardrobe/plugins/immutable.rb +164 -0
- data/lib/wardrobe/plugins/ivy_presenter.rb +42 -0
- data/lib/wardrobe/plugins/json_initializer.rb +41 -0
- data/lib/wardrobe/plugins/nil_if_empty.rb +27 -0
- data/lib/wardrobe/plugins/nil_if_zero.rb +19 -0
- data/lib/wardrobe/plugins/optional_getter.rb +21 -0
- data/lib/wardrobe/plugins/optional_setter.rb +24 -0
- data/lib/wardrobe/plugins/presenter/refinements/array.rb +15 -0
- data/lib/wardrobe/plugins/presenter/refinements/hash.rb +15 -0
- data/lib/wardrobe/plugins/presenter/refinements/object.rb +15 -0
- data/lib/wardrobe/plugins/presenter/refinements/time.rb +19 -0
- data/lib/wardrobe/plugins/presenter/refinements.rb +6 -0
- data/lib/wardrobe/plugins/presenter.rb +25 -0
- data/lib/wardrobe/plugins/validation/block_handler.rb +83 -0
- data/lib/wardrobe/plugins/validation/class_methods.rb +13 -0
- data/lib/wardrobe/plugins/validation/deep_merge.rb +27 -0
- data/lib/wardrobe/plugins/validation/error_store.rb +27 -0
- data/lib/wardrobe/plugins/validation/instance_methods.rb +39 -0
- data/lib/wardrobe/plugins/validation/refinements/_size.rb +30 -0
- data/lib/wardrobe/plugins/validation/refinements/array.rb +26 -0
- data/lib/wardrobe/plugins/validation/refinements/date.rb +15 -0
- data/lib/wardrobe/plugins/validation/refinements/hash.rb +27 -0
- data/lib/wardrobe/plugins/validation/refinements/integer.rb +21 -0
- data/lib/wardrobe/plugins/validation/refinements/nil_class.rb +19 -0
- data/lib/wardrobe/plugins/validation/refinements/numeric.rb +31 -0
- data/lib/wardrobe/plugins/validation/refinements/object.rb +35 -0
- data/lib/wardrobe/plugins/validation/refinements/set.rb +25 -0
- data/lib/wardrobe/plugins/validation/refinements/string.rb +55 -0
- data/lib/wardrobe/plugins/validation/refinements/symbol.rb +26 -0
- data/lib/wardrobe/plugins/validation/refinements.rb +13 -0
- data/lib/wardrobe/plugins/validation/validation.rb +63 -0
- data/lib/wardrobe/plugins/validation/validation_error.rb +19 -0
- data/lib/wardrobe/plugins/validation/validaton_runner.rb +38 -0
- data/lib/wardrobe/plugins/validation/validator.rb +119 -0
- data/lib/wardrobe/plugins/validation.rb +28 -0
- data/lib/wardrobe/root_config.rb +31 -0
- data/lib/wardrobe/store.rb +62 -0
- data/lib/wardrobe/stores.rb +106 -0
- data/lib/wardrobe/version.rb +5 -0
- data/lib/wardrobe.rb +34 -0
- data/sandbox/Gemfile +5 -0
- data/sandbox/Gemfile.lock +52 -0
- data/sandbox/bench_mutable_coercion.rb +91 -0
- data/sandbox/bench_test_1.rb +50 -0
- data/sandbox/bench_test_2.rb +48 -0
- data/sandbox/bench_test_3.rb +92 -0
- data/sandbox/cnn_article_find.rb +20 -0
- data/sandbox/define_method_procs.rb +139 -0
- data/sandbox/hanami_validations.rb +40 -0
- data/sandbox/middleware_bench_test.rb +97 -0
- data/sandbox/middleware_bench_v2_test.rb +130 -0
- data/test/assertions.rb +22 -0
- data/test/test_helper.rb +37 -0
- data/test/unit/atrs_config_test.rb +33 -0
- data/test/unit/atrs_root_module_test.rb +19 -0
- data/test/unit/attribute_test.rb +44 -0
- data/test/unit/block_runner/array_test.rb +29 -0
- data/test/unit/block_setup_test.rb +41 -0
- data/test/unit/class_methods_test.rb +92 -0
- data/test/unit/create_class_from_hash_test.rb +28 -0
- data/test/unit/define_getter_test.rb +94 -0
- data/test/unit/define_setter_test.rb +0 -0
- data/test/unit/disable_coercion_test.rb +19 -0
- data/test/unit/inheritance_composition_test.rb +36 -0
- data/test/unit/method_override_test.rb +18 -0
- data/test/unit/method_polution_test.rb +48 -0
- data/test/unit/option_test.rb +17 -0
- data/test/unit/plugin_test.rb +12 -0
- data/test/unit/plugins/alias_setters_test.rb +24 -0
- data/test/unit/plugins/coercible/array_test.rb +79 -0
- data/test/unit/plugins/coercible/boolean_test.rb +68 -0
- data/test/unit/plugins/coercible/custom_class_test.rb +61 -0
- data/test/unit/plugins/coercible/date_test.rb +38 -0
- data/test/unit/plugins/coercible/date_time_test.rb +44 -0
- data/test/unit/plugins/coercible/float_test.rb +42 -0
- data/test/unit/plugins/coercible/hash_test.rb +62 -0
- data/test/unit/plugins/coercible/integer_test.rb +42 -0
- data/test/unit/plugins/coercible/nested_wardrobe_test.rb +63 -0
- data/test/unit/plugins/coercible/set_test.rb +49 -0
- data/test/unit/plugins/coercible/string_test.rb +36 -0
- data/test/unit/plugins/coercible/symbol_test.rb +30 -0
- data/test/unit/plugins/coercible/time_test.rb +43 -0
- data/test/unit/plugins/configurable_test.rb +64 -0
- data/test/unit/plugins/default_value_test.rb +78 -0
- data/test/unit/plugins/dirty_tracker_test.rb +78 -0
- data/test/unit/plugins/equality_test.rb +36 -0
- data/test/unit/plugins/html_initializer_test.rb +19 -0
- data/test/unit/plugins/immutable_test.rb +149 -0
- data/test/unit/plugins/ivy_presenter_test.rb +57 -0
- data/test/unit/plugins/json_initializer_test.rb +67 -0
- data/test/unit/plugins/nil_if_empty_test.rb +39 -0
- data/test/unit/plugins/nil_if_zero_test.rb +26 -0
- data/test/unit/plugins/presenter_test.rb +68 -0
- data/test/unit/plugins/validate/inheritance_test.rb +36 -0
- data/test/unit/plugins/validate/nested_model_test.rb +40 -0
- data/test/unit/plugins/validate/predicates/each_key_each_value_test.rb +34 -0
- data/test/unit/plugins/validate/predicates/each_test.rb +33 -0
- data/test/unit/plugins/validate/predicates/empty_test.rb +37 -0
- data/test/unit/plugins/validate/predicates/even_test.rb +33 -0
- data/test/unit/plugins/validate/predicates/excluded_from_test.rb +55 -0
- data/test/unit/plugins/validate/predicates/filled_test.rb +49 -0
- data/test/unit/plugins/validate/predicates/format_test.rb +28 -0
- data/test/unit/plugins/validate/predicates/gt_test.rb +36 -0
- data/test/unit/plugins/validate/predicates/gteq_test.rb +36 -0
- data/test/unit/plugins/validate/predicates/included_in_test.rb +55 -0
- data/test/unit/plugins/validate/predicates/lt_test.rb +36 -0
- data/test/unit/plugins/validate/predicates/lteq_test.rb +36 -0
- data/test/unit/plugins/validate/predicates/max_size_test.rb +43 -0
- data/test/unit/plugins/validate/predicates/min_size_test.rb +43 -0
- data/test/unit/plugins/validate/predicates/none_test.rb +49 -0
- data/test/unit/plugins/validate/predicates/odd_test.rb +33 -0
- data/test/unit/plugins/validate/predicates/size_range_test.rb +43 -0
- data/test/unit/plugins/validate/predicates/size_test.rb +43 -0
- data/test/unit/plugins/validate/predicates/type_test.rb +77 -0
- data/test/unit/plugins/validate/predicates_test.rb +73 -0
- data/test/unit/plugins/validate/validate_bang_test.rb +30 -0
- data/test/unit/store_test.rb +15 -0
- data/test/unit/stores_test.rb +18 -0
- data/wardrobe.gemspec +26 -0
- metadata +344 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wardrobe
|
|
4
|
+
module Plugins
|
|
5
|
+
module Validation
|
|
6
|
+
module Refinements
|
|
7
|
+
refine Symbol do
|
|
8
|
+
def empty?
|
|
9
|
+
return if self.to_s.strip == ''
|
|
10
|
+
'must be empty'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def filled?
|
|
14
|
+
return unless self.to_s.strip == ''
|
|
15
|
+
'must be filled'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def format?(regex)
|
|
19
|
+
return if regex.match(self)
|
|
20
|
+
"must match #{regex.inspect}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'refinements/_size'
|
|
4
|
+
require_relative 'refinements/array'
|
|
5
|
+
require_relative 'refinements/date'
|
|
6
|
+
require_relative 'refinements/hash'
|
|
7
|
+
require_relative 'refinements/integer'
|
|
8
|
+
require_relative 'refinements/nil_class'
|
|
9
|
+
require_relative 'refinements/numeric'
|
|
10
|
+
require_relative 'refinements/object'
|
|
11
|
+
require_relative 'refinements/set'
|
|
12
|
+
require_relative 'refinements/string'
|
|
13
|
+
require_relative 'refinements/symbol'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Wardrobe
|
|
2
|
+
module Plugins
|
|
3
|
+
module Validation
|
|
4
|
+
|
|
5
|
+
class Validation < Hash
|
|
6
|
+
def initialize(method, argument)
|
|
7
|
+
self[:method] = method
|
|
8
|
+
self[:argument] = argument
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def method
|
|
12
|
+
self[:method]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def argument
|
|
16
|
+
self[:argument]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def args
|
|
20
|
+
@args ||= begin
|
|
21
|
+
arr = [ method ]
|
|
22
|
+
arr << argument if argument
|
|
23
|
+
arr
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
SPECIAL_METHODS = Set.new([:each?, :each_key?, :each_value?])
|
|
28
|
+
|
|
29
|
+
def type
|
|
30
|
+
@type ||= begin
|
|
31
|
+
if method[/^_.+_$/] || SPECIAL_METHODS.include?(method)
|
|
32
|
+
:special
|
|
33
|
+
else
|
|
34
|
+
:value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def &(other)
|
|
40
|
+
if method == :_and_
|
|
41
|
+
argument << other
|
|
42
|
+
self
|
|
43
|
+
else
|
|
44
|
+
self.class.new(:_and_, [self, other])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def |(other)
|
|
49
|
+
if method == :_or_
|
|
50
|
+
argument<< other
|
|
51
|
+
self
|
|
52
|
+
else
|
|
53
|
+
self.class.new(:_or_, [self, other])
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def >(other)
|
|
58
|
+
self.class.new(:_then_, [self, other])
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wardrobe
|
|
4
|
+
module Plugins
|
|
5
|
+
module Validation
|
|
6
|
+
class ValidationError < StandardError
|
|
7
|
+
attr_reader :errors
|
|
8
|
+
|
|
9
|
+
def initialize(errors)
|
|
10
|
+
@errors = errors.freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
errors.to_s
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wardrobe
|
|
4
|
+
module Plugins
|
|
5
|
+
module Validation
|
|
6
|
+
class ValidationRunner
|
|
7
|
+
attr_reader :instance
|
|
8
|
+
|
|
9
|
+
def initialize(instance)
|
|
10
|
+
@instance = instance
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.validate(instance)
|
|
14
|
+
new(instance).run
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def Validate(value, atr, error_store)
|
|
18
|
+
Validator.new(value, atr, error_store).run
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
instance._attribute_store.each do |_name, atr|
|
|
23
|
+
Validate(instance.send(atr.name), atr, error_store)
|
|
24
|
+
end
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def error_store
|
|
29
|
+
@error_store ||= ErrorStore.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def errors
|
|
33
|
+
error_store.store
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wardrobe
|
|
4
|
+
module Plugins
|
|
5
|
+
module Validation
|
|
6
|
+
class Validator
|
|
7
|
+
using Refinements
|
|
8
|
+
attr_reader :value, :atr, :error_store, :validation
|
|
9
|
+
|
|
10
|
+
def initialize(value, atr, error_store, validation = nil)
|
|
11
|
+
@value = value
|
|
12
|
+
@atr = atr
|
|
13
|
+
@error_store = error_store
|
|
14
|
+
@validation = validation || atr.options[:validates]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run(report = true)
|
|
18
|
+
if validation
|
|
19
|
+
validate(validation, report)
|
|
20
|
+
elsif value.respond_to?(:_validate!)
|
|
21
|
+
if value._validation_errors.any?
|
|
22
|
+
error_store.store[atr.name] = value._validation_errors
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
Wardrobe.logger.warn("Unable to validate #{value.class} class")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def report(*errors)
|
|
32
|
+
error_store.add(atr, *errors)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate(validation, report)
|
|
36
|
+
if validation.type == :special#method[/^_.+_$/]
|
|
37
|
+
send(*validation.args, report)
|
|
38
|
+
else
|
|
39
|
+
error = value.send(*validation.args)
|
|
40
|
+
report && error ? report(error) : error
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def validate_list(validations, report)
|
|
45
|
+
validations.map do |validation|
|
|
46
|
+
validate(validation, report)
|
|
47
|
+
end.compact
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def each?(validation, report)
|
|
51
|
+
errors = {}
|
|
52
|
+
value.each_with_index do |item, index|
|
|
53
|
+
result = Validator.new(item, nil, nil, validation).run(false)
|
|
54
|
+
result = [result] unless result.is_a?(Array)
|
|
55
|
+
errors[index] = result if result.any?
|
|
56
|
+
end
|
|
57
|
+
if errors.any?
|
|
58
|
+
report(errors) if report
|
|
59
|
+
errors
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def each_key?(validation, report)
|
|
64
|
+
errors = {}
|
|
65
|
+
value.each do |key, _|
|
|
66
|
+
result = Validator.new(key, nil, nil, validation).run(false)
|
|
67
|
+
result = [result] unless result.is_a?(Array)
|
|
68
|
+
errors[key] = result.map { |error| 'key ' + error } if result.any?
|
|
69
|
+
end
|
|
70
|
+
if errors.any?
|
|
71
|
+
report(errors) if report
|
|
72
|
+
errors
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def each_value?(validation, report)
|
|
77
|
+
errors = {}
|
|
78
|
+
value.each do |key, val|
|
|
79
|
+
result = Validator.new(val, nil, nil, validation).run(false)
|
|
80
|
+
result = [result] unless result.is_a?(Array)
|
|
81
|
+
errors[key] = result.map { |error| 'value ' + error } if result.any?
|
|
82
|
+
end
|
|
83
|
+
if errors.any?
|
|
84
|
+
report(errors) if report
|
|
85
|
+
errors
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _or_(validations, report)
|
|
90
|
+
errors = validate_list(validations, false)
|
|
91
|
+
error_string = errors.join(' or ')
|
|
92
|
+
report(error_string) if validations.size == errors.size && report
|
|
93
|
+
error_string
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def _and_(validations, report)
|
|
97
|
+
errors = validate_list(validations, false)
|
|
98
|
+
report ? report(*errors) : errors
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def _then_(validations, report)
|
|
102
|
+
error = validate(validations.first, false)
|
|
103
|
+
if error
|
|
104
|
+
report(error) if report
|
|
105
|
+
error
|
|
106
|
+
else
|
|
107
|
+
validate_list(validations[1..-1], report)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def _optional_(validation, report)
|
|
112
|
+
validate(validation, report)
|
|
113
|
+
rescue NoMethodError => e
|
|
114
|
+
raise e unless value.nil?
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'validation/refinements'
|
|
4
|
+
require_relative 'validation/deep_merge'
|
|
5
|
+
require_relative 'validation/error_store'
|
|
6
|
+
require_relative 'validation/instance_methods'
|
|
7
|
+
require_relative 'validation/validation'
|
|
8
|
+
require_relative 'validation/validator'
|
|
9
|
+
require_relative 'validation/validaton_runner'
|
|
10
|
+
require_relative 'validation/validation_error'
|
|
11
|
+
require_relative 'validation/class_methods'
|
|
12
|
+
require_relative 'validation/block_handler'
|
|
13
|
+
|
|
14
|
+
# TODO:
|
|
15
|
+
# - Setting to run validations automatically
|
|
16
|
+
# - Support all Hanami/Dry validations
|
|
17
|
+
# - Support advanced predicates
|
|
18
|
+
|
|
19
|
+
module Wardrobe
|
|
20
|
+
module Plugins
|
|
21
|
+
module Validation
|
|
22
|
+
extend Wardrobe::Plugin
|
|
23
|
+
option :validates, Hash
|
|
24
|
+
option :required, Boolean, default: true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
register_plugin(:validation, Plugins::Validation)
|
|
28
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wardrobe
|
|
4
|
+
# Config class used on Wardrobe root module
|
|
5
|
+
class RootConfig
|
|
6
|
+
include Wardrobe
|
|
7
|
+
plugin :immutable
|
|
8
|
+
plugin :default
|
|
9
|
+
attribute :coerce, Boolean, default: true
|
|
10
|
+
attribute :logger, Object, default: ->() { Logger.new(STDOUT) }
|
|
11
|
+
attribute :default_plugins, Set[Symbol], default: Set.new([:coercible])
|
|
12
|
+
|
|
13
|
+
def register_default_plugin(name)
|
|
14
|
+
raise 'error' unless Wardrobe.plugins.key?(name)
|
|
15
|
+
default_plugins.add(name)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def coerce=(value)
|
|
19
|
+
if super == false
|
|
20
|
+
default_plugins.delete(:coercible)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def build(**args)
|
|
25
|
+
args.each do |k, v|
|
|
26
|
+
send("#{k}=", v)
|
|
27
|
+
end
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wardrobe
|
|
4
|
+
class Store
|
|
5
|
+
include Enumerable
|
|
6
|
+
attr_reader :store, :parent
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@store = {}
|
|
10
|
+
freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def values
|
|
14
|
+
store.values
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def [](name)
|
|
18
|
+
store[name]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def each(&blk)
|
|
22
|
+
store.each(&blk)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def freeze
|
|
26
|
+
store.freeze
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def method_missing(name, *args, **key_args, &block)
|
|
31
|
+
if (atr = store[name])
|
|
32
|
+
atr
|
|
33
|
+
else
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def merge(other, _calling_object, _config)
|
|
39
|
+
mutate do
|
|
40
|
+
@store = store.merge(other.store)
|
|
41
|
+
# I guess we have to loop through each item and do a custom merge...
|
|
42
|
+
# maybe not use Hash#merge?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def del(name)
|
|
47
|
+
mutate do
|
|
48
|
+
store.delete(name) { |key| raise "#{key} not found" }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def mutate(&blk)
|
|
55
|
+
dup.instance_exec do
|
|
56
|
+
@store = store.dup
|
|
57
|
+
instance_exec(&blk)
|
|
58
|
+
freeze
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wardrobe
|
|
4
|
+
class Stores
|
|
5
|
+
def self.registered_stores
|
|
6
|
+
@registered_stores ||= {}.freeze
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.register_store(name, klass)
|
|
10
|
+
@registered_stores = registered_stores.merge(name => klass).freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
register_store(:attribute_store, AttributeStore)
|
|
14
|
+
register_store(:plugin_store, PluginStore)
|
|
15
|
+
register_store(:option_store, OptionStore)
|
|
16
|
+
|
|
17
|
+
attr_reader :stores
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@stores = {}.freeze
|
|
21
|
+
self.class.registered_stores.each do |key, value|
|
|
22
|
+
add_store(key, value, initializer: true)
|
|
23
|
+
end
|
|
24
|
+
freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update(&blk)
|
|
28
|
+
if frozen?
|
|
29
|
+
dup.update(&blk)
|
|
30
|
+
else
|
|
31
|
+
instance_exec(&blk)
|
|
32
|
+
freeze
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add_store(name, klass, initializer: false)
|
|
37
|
+
if frozen?
|
|
38
|
+
dup.add_store(name, klass)
|
|
39
|
+
else
|
|
40
|
+
@stores = stores.merge(name => klass)
|
|
41
|
+
instance_variable_set("@#{name}", klass.new)
|
|
42
|
+
define_singleton_method(name) { instance_variable_get("@#{name}") }
|
|
43
|
+
freeze unless initializer
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def merge(other, calling_object)
|
|
48
|
+
if frozen?
|
|
49
|
+
dup.merge(other, calling_object)
|
|
50
|
+
else
|
|
51
|
+
(other.stores.keys - stores.keys).each do |name|
|
|
52
|
+
add_store(name, other.stores[name], initializer: true)
|
|
53
|
+
end
|
|
54
|
+
stores.each do |name, klass|
|
|
55
|
+
instance = respond_to?(name) ? send(name) : klass.new
|
|
56
|
+
instance_variable_set(
|
|
57
|
+
"@#{name}", instance.merge(other.send(name), calling_object, self)
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
freeze
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def dup
|
|
65
|
+
duplicate = super
|
|
66
|
+
duplicate.stores.each do |name, _klass|
|
|
67
|
+
duplicate.define_singleton_method(name) do
|
|
68
|
+
instance_variable_get("@#{name}")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
duplicate
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def enable_plugin(name, **args)
|
|
75
|
+
if frozen?
|
|
76
|
+
dup.enable_plugin(name, **args)
|
|
77
|
+
else
|
|
78
|
+
@plugin_store = plugin_store.add(name, **args)
|
|
79
|
+
plugin_store[name][:klass].options.each do |option|
|
|
80
|
+
@option_store = option_store.add(option.name, option)
|
|
81
|
+
end
|
|
82
|
+
freeze
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def add_attribute(name, klass, defining_object, **merged_args, &blk)
|
|
87
|
+
if frozen?
|
|
88
|
+
dup.add_attribute(name, klass, defining_object, **merged_args, &blk)
|
|
89
|
+
else
|
|
90
|
+
@attribute_store = attribute_store.add(
|
|
91
|
+
name, klass, defining_object, self, **merged_args, &blk
|
|
92
|
+
)
|
|
93
|
+
freeze
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def remove_attribute(name)
|
|
98
|
+
if frozen?
|
|
99
|
+
dup.remove_attribute(name)
|
|
100
|
+
else
|
|
101
|
+
@attribute_store = attribute_store.del(name)
|
|
102
|
+
freeze
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/wardrobe.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'wardrobe/version'
|
|
4
|
+
require 'wardrobe/boolean'
|
|
5
|
+
require 'wardrobe/store'
|
|
6
|
+
require 'wardrobe/getter_setter'
|
|
7
|
+
require 'wardrobe/attribute'
|
|
8
|
+
require 'wardrobe/attribute_store'
|
|
9
|
+
require 'wardrobe/option'
|
|
10
|
+
require 'wardrobe/option_store'
|
|
11
|
+
require 'wardrobe/plugin'
|
|
12
|
+
require 'wardrobe/plugin_store'
|
|
13
|
+
require 'wardrobe/stores'
|
|
14
|
+
require 'wardrobe/block_setup'
|
|
15
|
+
require 'wardrobe/class_methods'
|
|
16
|
+
require 'wardrobe/instance_methods'
|
|
17
|
+
require 'wardrobe/module_methods'
|
|
18
|
+
|
|
19
|
+
# Top level Wardrobe module
|
|
20
|
+
module Wardrobe
|
|
21
|
+
extend ModuleMethods
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require 'wardrobe/root_config'
|
|
25
|
+
|
|
26
|
+
# rubocop:disable Style/MethodName
|
|
27
|
+
def Wardrobe(**options)
|
|
28
|
+
mod = Module.new do
|
|
29
|
+
extend Wardrobe::ModuleMethods
|
|
30
|
+
end
|
|
31
|
+
mod.configure { |config| config.build(options) }
|
|
32
|
+
mod
|
|
33
|
+
end
|
|
34
|
+
# rubocop:enable Style/MethodName
|
data/sandbox/Gemfile
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ../
|
|
3
|
+
specs:
|
|
4
|
+
atrs (0.0.1)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
axiom-types (0.1.1)
|
|
10
|
+
descendants_tracker (~> 0.0.4)
|
|
11
|
+
ice_nine (~> 0.11.0)
|
|
12
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
13
|
+
benchmark-ips (2.7.2)
|
|
14
|
+
coercible (1.0.0)
|
|
15
|
+
descendants_tracker (~> 0.0.1)
|
|
16
|
+
derailed_benchmarks (1.3.1)
|
|
17
|
+
benchmark-ips (~> 2)
|
|
18
|
+
get_process_mem (~> 0)
|
|
19
|
+
heapy (~> 0)
|
|
20
|
+
memory_profiler (~> 0)
|
|
21
|
+
rack (>= 1)
|
|
22
|
+
rake (> 10, < 12)
|
|
23
|
+
thor (~> 0.19)
|
|
24
|
+
descendants_tracker (0.0.4)
|
|
25
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
26
|
+
equalizer (0.0.11)
|
|
27
|
+
get_process_mem (0.2.1)
|
|
28
|
+
heapy (0.1.2)
|
|
29
|
+
ice_nine (0.11.2)
|
|
30
|
+
memory_profiler (0.9.7)
|
|
31
|
+
rack (2.0.1)
|
|
32
|
+
rake (11.3.0)
|
|
33
|
+
stackprof (0.2.10)
|
|
34
|
+
thor (0.19.1)
|
|
35
|
+
thread_safe (0.3.5)
|
|
36
|
+
virtus (1.0.5)
|
|
37
|
+
axiom-types (~> 0.1)
|
|
38
|
+
coercible (~> 1.0)
|
|
39
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
|
40
|
+
equalizer (~> 0.0, >= 0.0.9)
|
|
41
|
+
|
|
42
|
+
PLATFORMS
|
|
43
|
+
ruby
|
|
44
|
+
|
|
45
|
+
DEPENDENCIES
|
|
46
|
+
atrs!
|
|
47
|
+
derailed_benchmarks
|
|
48
|
+
stackprof
|
|
49
|
+
virtus
|
|
50
|
+
|
|
51
|
+
BUNDLED WITH
|
|
52
|
+
1.13.6
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
|
2
|
+
class AlmostArray < Array
|
|
3
|
+
|
|
4
|
+
end
|
|
5
|
+
module Wardrobe
|
|
6
|
+
module Coercions
|
|
7
|
+
refine AlmostArray.singleton_class do
|
|
8
|
+
def coerce(v, atr)
|
|
9
|
+
case v
|
|
10
|
+
when AlmostArray, Array then AlmostArray.new(v)
|
|
11
|
+
when Set then AlmostArray.new(v.to_a)
|
|
12
|
+
when NilClass then []
|
|
13
|
+
else
|
|
14
|
+
raise UnsupportedError
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
refine AlmostArray do
|
|
20
|
+
class WrongNumberOfItemsError < StandardError; end
|
|
21
|
+
def coerce(v, atr)
|
|
22
|
+
result = case v
|
|
23
|
+
when Array, AlmostArray
|
|
24
|
+
|
|
25
|
+
# This check shold be moved to attribute creation time
|
|
26
|
+
raise StandardError, "`Array#{map(&:name)}' contains two many classes. No more than one is allowed." if count != 1
|
|
27
|
+
v.map! { |item| first.coerce(item, nil) }
|
|
28
|
+
when NilClass then AlmostArray.new
|
|
29
|
+
else
|
|
30
|
+
raise UnsupportedError
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
require 'wardrobe'
|
|
38
|
+
require 'benchmark/ips'
|
|
39
|
+
require 'pry'
|
|
40
|
+
require 'pry-byebug'
|
|
41
|
+
require 'ostruct'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Embedded
|
|
51
|
+
include Wardrobe
|
|
52
|
+
attribute :name, String
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class One
|
|
56
|
+
include Wardrobe
|
|
57
|
+
attribute :array, Array[Embedded]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class Two
|
|
61
|
+
include Wardrobe
|
|
62
|
+
attribute :array, AlmostArray[Embedded]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
one = One.new
|
|
66
|
+
two = Two.new
|
|
67
|
+
# one.array.unshift({name: 'asf'})
|
|
68
|
+
# two.array.unshift({name: 'asf'})
|
|
69
|
+
|
|
70
|
+
# binding.pry
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Benchmark.ips do |x|
|
|
74
|
+
x.report('With Array Coercion') {
|
|
75
|
+
one.array << { name: 'abcd' }
|
|
76
|
+
}
|
|
77
|
+
x.report('Without Array Coercion') {
|
|
78
|
+
two.array << { name: 'abcd' }
|
|
79
|
+
}
|
|
80
|
+
# x.report('Modules') {
|
|
81
|
+
# modules.inject('') do |val, item|
|
|
82
|
+
# item.call(val, instance)
|
|
83
|
+
# end
|
|
84
|
+
# }
|
|
85
|
+
# x.report('Classes') {
|
|
86
|
+
# classes.inject('') do |val, item|
|
|
87
|
+
# item.new.call(val, instance)
|
|
88
|
+
# end
|
|
89
|
+
# }
|
|
90
|
+
x.compare!
|
|
91
|
+
end
|