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
|