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.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +10 -0
  3. data/.gitignore +19 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +11 -0
  6. data/CHANGELOG.md +5 -0
  7. data/Gemfile +18 -0
  8. data/Guardfile +42 -0
  9. data/LICENSE +19 -0
  10. data/README.md +227 -0
  11. data/Rakefile +20 -0
  12. data/examples/new_york_times_article.rb +55 -0
  13. data/lib/wardrobe/attribute.rb +76 -0
  14. data/lib/wardrobe/attribute_store.rb +16 -0
  15. data/lib/wardrobe/block_setup.rb +124 -0
  16. data/lib/wardrobe/boolean.rb +6 -0
  17. data/lib/wardrobe/class_methods.rb +127 -0
  18. data/lib/wardrobe/getter_setter.rb +48 -0
  19. data/lib/wardrobe/instance_methods.rb +56 -0
  20. data/lib/wardrobe/module_methods.rb +51 -0
  21. data/lib/wardrobe/option.rb +30 -0
  22. data/lib/wardrobe/option_store.rb +22 -0
  23. data/lib/wardrobe/plugin.rb +39 -0
  24. data/lib/wardrobe/plugin_store.rb +28 -0
  25. data/lib/wardrobe/plugins/alias_setters.rb +35 -0
  26. data/lib/wardrobe/plugins/coercible/refinements/array.rb +63 -0
  27. data/lib/wardrobe/plugins/coercible/refinements/boolean.rb +36 -0
  28. data/lib/wardrobe/plugins/coercible/refinements/date.rb +31 -0
  29. data/lib/wardrobe/plugins/coercible/refinements/date_time.rb +29 -0
  30. data/lib/wardrobe/plugins/coercible/refinements/float.rb +23 -0
  31. data/lib/wardrobe/plugins/coercible/refinements/hash.rb +65 -0
  32. data/lib/wardrobe/plugins/coercible/refinements/integer.rb +23 -0
  33. data/lib/wardrobe/plugins/coercible/refinements/object.rb +15 -0
  34. data/lib/wardrobe/plugins/coercible/refinements/set.rb +33 -0
  35. data/lib/wardrobe/plugins/coercible/refinements/string.rb +21 -0
  36. data/lib/wardrobe/plugins/coercible/refinements/symbol.rb +21 -0
  37. data/lib/wardrobe/plugins/coercible/refinements/time.rb +30 -0
  38. data/lib/wardrobe/plugins/coercible/refinements/unsupported_error.rb +11 -0
  39. data/lib/wardrobe/plugins/coercible.rb +52 -0
  40. data/lib/wardrobe/plugins/configurable/configurable_store.rb +26 -0
  41. data/lib/wardrobe/plugins/configurable.rb +40 -0
  42. data/lib/wardrobe/plugins/default.rb +33 -0
  43. data/lib/wardrobe/plugins/dirty_tracker.rb +66 -0
  44. data/lib/wardrobe/plugins/equality.rb +19 -0
  45. data/lib/wardrobe/plugins/html_initializer.rb +38 -0
  46. data/lib/wardrobe/plugins/immutable.rb +164 -0
  47. data/lib/wardrobe/plugins/ivy_presenter.rb +42 -0
  48. data/lib/wardrobe/plugins/json_initializer.rb +41 -0
  49. data/lib/wardrobe/plugins/nil_if_empty.rb +27 -0
  50. data/lib/wardrobe/plugins/nil_if_zero.rb +19 -0
  51. data/lib/wardrobe/plugins/optional_getter.rb +21 -0
  52. data/lib/wardrobe/plugins/optional_setter.rb +24 -0
  53. data/lib/wardrobe/plugins/presenter/refinements/array.rb +15 -0
  54. data/lib/wardrobe/plugins/presenter/refinements/hash.rb +15 -0
  55. data/lib/wardrobe/plugins/presenter/refinements/object.rb +15 -0
  56. data/lib/wardrobe/plugins/presenter/refinements/time.rb +19 -0
  57. data/lib/wardrobe/plugins/presenter/refinements.rb +6 -0
  58. data/lib/wardrobe/plugins/presenter.rb +25 -0
  59. data/lib/wardrobe/plugins/validation/block_handler.rb +83 -0
  60. data/lib/wardrobe/plugins/validation/class_methods.rb +13 -0
  61. data/lib/wardrobe/plugins/validation/deep_merge.rb +27 -0
  62. data/lib/wardrobe/plugins/validation/error_store.rb +27 -0
  63. data/lib/wardrobe/plugins/validation/instance_methods.rb +39 -0
  64. data/lib/wardrobe/plugins/validation/refinements/_size.rb +30 -0
  65. data/lib/wardrobe/plugins/validation/refinements/array.rb +26 -0
  66. data/lib/wardrobe/plugins/validation/refinements/date.rb +15 -0
  67. data/lib/wardrobe/plugins/validation/refinements/hash.rb +27 -0
  68. data/lib/wardrobe/plugins/validation/refinements/integer.rb +21 -0
  69. data/lib/wardrobe/plugins/validation/refinements/nil_class.rb +19 -0
  70. data/lib/wardrobe/plugins/validation/refinements/numeric.rb +31 -0
  71. data/lib/wardrobe/plugins/validation/refinements/object.rb +35 -0
  72. data/lib/wardrobe/plugins/validation/refinements/set.rb +25 -0
  73. data/lib/wardrobe/plugins/validation/refinements/string.rb +55 -0
  74. data/lib/wardrobe/plugins/validation/refinements/symbol.rb +26 -0
  75. data/lib/wardrobe/plugins/validation/refinements.rb +13 -0
  76. data/lib/wardrobe/plugins/validation/validation.rb +63 -0
  77. data/lib/wardrobe/plugins/validation/validation_error.rb +19 -0
  78. data/lib/wardrobe/plugins/validation/validaton_runner.rb +38 -0
  79. data/lib/wardrobe/plugins/validation/validator.rb +119 -0
  80. data/lib/wardrobe/plugins/validation.rb +28 -0
  81. data/lib/wardrobe/root_config.rb +31 -0
  82. data/lib/wardrobe/store.rb +62 -0
  83. data/lib/wardrobe/stores.rb +106 -0
  84. data/lib/wardrobe/version.rb +5 -0
  85. data/lib/wardrobe.rb +34 -0
  86. data/sandbox/Gemfile +5 -0
  87. data/sandbox/Gemfile.lock +52 -0
  88. data/sandbox/bench_mutable_coercion.rb +91 -0
  89. data/sandbox/bench_test_1.rb +50 -0
  90. data/sandbox/bench_test_2.rb +48 -0
  91. data/sandbox/bench_test_3.rb +92 -0
  92. data/sandbox/cnn_article_find.rb +20 -0
  93. data/sandbox/define_method_procs.rb +139 -0
  94. data/sandbox/hanami_validations.rb +40 -0
  95. data/sandbox/middleware_bench_test.rb +97 -0
  96. data/sandbox/middleware_bench_v2_test.rb +130 -0
  97. data/test/assertions.rb +22 -0
  98. data/test/test_helper.rb +37 -0
  99. data/test/unit/atrs_config_test.rb +33 -0
  100. data/test/unit/atrs_root_module_test.rb +19 -0
  101. data/test/unit/attribute_test.rb +44 -0
  102. data/test/unit/block_runner/array_test.rb +29 -0
  103. data/test/unit/block_setup_test.rb +41 -0
  104. data/test/unit/class_methods_test.rb +92 -0
  105. data/test/unit/create_class_from_hash_test.rb +28 -0
  106. data/test/unit/define_getter_test.rb +94 -0
  107. data/test/unit/define_setter_test.rb +0 -0
  108. data/test/unit/disable_coercion_test.rb +19 -0
  109. data/test/unit/inheritance_composition_test.rb +36 -0
  110. data/test/unit/method_override_test.rb +18 -0
  111. data/test/unit/method_polution_test.rb +48 -0
  112. data/test/unit/option_test.rb +17 -0
  113. data/test/unit/plugin_test.rb +12 -0
  114. data/test/unit/plugins/alias_setters_test.rb +24 -0
  115. data/test/unit/plugins/coercible/array_test.rb +79 -0
  116. data/test/unit/plugins/coercible/boolean_test.rb +68 -0
  117. data/test/unit/plugins/coercible/custom_class_test.rb +61 -0
  118. data/test/unit/plugins/coercible/date_test.rb +38 -0
  119. data/test/unit/plugins/coercible/date_time_test.rb +44 -0
  120. data/test/unit/plugins/coercible/float_test.rb +42 -0
  121. data/test/unit/plugins/coercible/hash_test.rb +62 -0
  122. data/test/unit/plugins/coercible/integer_test.rb +42 -0
  123. data/test/unit/plugins/coercible/nested_wardrobe_test.rb +63 -0
  124. data/test/unit/plugins/coercible/set_test.rb +49 -0
  125. data/test/unit/plugins/coercible/string_test.rb +36 -0
  126. data/test/unit/plugins/coercible/symbol_test.rb +30 -0
  127. data/test/unit/plugins/coercible/time_test.rb +43 -0
  128. data/test/unit/plugins/configurable_test.rb +64 -0
  129. data/test/unit/plugins/default_value_test.rb +78 -0
  130. data/test/unit/plugins/dirty_tracker_test.rb +78 -0
  131. data/test/unit/plugins/equality_test.rb +36 -0
  132. data/test/unit/plugins/html_initializer_test.rb +19 -0
  133. data/test/unit/plugins/immutable_test.rb +149 -0
  134. data/test/unit/plugins/ivy_presenter_test.rb +57 -0
  135. data/test/unit/plugins/json_initializer_test.rb +67 -0
  136. data/test/unit/plugins/nil_if_empty_test.rb +39 -0
  137. data/test/unit/plugins/nil_if_zero_test.rb +26 -0
  138. data/test/unit/plugins/presenter_test.rb +68 -0
  139. data/test/unit/plugins/validate/inheritance_test.rb +36 -0
  140. data/test/unit/plugins/validate/nested_model_test.rb +40 -0
  141. data/test/unit/plugins/validate/predicates/each_key_each_value_test.rb +34 -0
  142. data/test/unit/plugins/validate/predicates/each_test.rb +33 -0
  143. data/test/unit/plugins/validate/predicates/empty_test.rb +37 -0
  144. data/test/unit/plugins/validate/predicates/even_test.rb +33 -0
  145. data/test/unit/plugins/validate/predicates/excluded_from_test.rb +55 -0
  146. data/test/unit/plugins/validate/predicates/filled_test.rb +49 -0
  147. data/test/unit/plugins/validate/predicates/format_test.rb +28 -0
  148. data/test/unit/plugins/validate/predicates/gt_test.rb +36 -0
  149. data/test/unit/plugins/validate/predicates/gteq_test.rb +36 -0
  150. data/test/unit/plugins/validate/predicates/included_in_test.rb +55 -0
  151. data/test/unit/plugins/validate/predicates/lt_test.rb +36 -0
  152. data/test/unit/plugins/validate/predicates/lteq_test.rb +36 -0
  153. data/test/unit/plugins/validate/predicates/max_size_test.rb +43 -0
  154. data/test/unit/plugins/validate/predicates/min_size_test.rb +43 -0
  155. data/test/unit/plugins/validate/predicates/none_test.rb +49 -0
  156. data/test/unit/plugins/validate/predicates/odd_test.rb +33 -0
  157. data/test/unit/plugins/validate/predicates/size_range_test.rb +43 -0
  158. data/test/unit/plugins/validate/predicates/size_test.rb +43 -0
  159. data/test/unit/plugins/validate/predicates/type_test.rb +77 -0
  160. data/test/unit/plugins/validate/predicates_test.rb +73 -0
  161. data/test/unit/plugins/validate/validate_bang_test.rb +30 -0
  162. data/test/unit/store_test.rb +15 -0
  163. data/test/unit/stores_test.rb +18 -0
  164. data/wardrobe.gemspec +26 -0
  165. 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ VERSION = '0.1.0'
5
+ 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,5 @@
1
+ source 'https://rubygems.org'
2
+ gem 'virtus'
3
+ gem 'wardrobe', path: '../'
4
+ gem 'derailed_benchmarks', group: :development
5
+ gem 'stackprof', group: :development
@@ -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