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,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Wardrobe
6
+ module ClassMethods
7
+ extend Forwardable
8
+ def_delegators :@wardrobe_stores, :attribute_store, :plugin_store, :option_store
9
+
10
+ def self.extended(base)
11
+ wardrobe_methods = base.instance_variable_set(:@wardrobe_methods, Module.new)
12
+ base.include(wardrobe_methods)
13
+ base.instance_variable_set(:@wardrobe_stores, Stores.new)
14
+ end
15
+
16
+ # This is called when included in another module/class
17
+ def included(base)
18
+ base.include(Wardrobe) unless base.respond_to? :wardrobe_stores
19
+ base.merge(wardrobe_stores)
20
+ end
21
+
22
+ def inherited(child)
23
+ wardrobe_methods = child.instance_variable_set(:@wardrobe_methods, Module.new)
24
+ child.include(wardrobe_methods)
25
+ child.instance_variable_set(:@wardrobe_stores, Stores.new)
26
+ child.merge(wardrobe_stores)
27
+ child.root_config = root_config
28
+ end
29
+
30
+ def wardrobe_stores(&blk)
31
+ if block_given?
32
+ @wardrobe_stores = wardrobe_stores.update(&blk)
33
+ else
34
+ @wardrobe_stores
35
+ end
36
+ end
37
+
38
+ def root_config=(input)
39
+ @root_config = input
40
+ end
41
+
42
+ def root_config
43
+ @root_config if instance_variable_defined?(:@root_config)
44
+ end
45
+
46
+ def default_getters
47
+ [
48
+ Wardrobe.getters[:getter]
49
+ ]
50
+ end
51
+
52
+ def default_setters
53
+ [
54
+ Wardrobe.setters[:setter]
55
+ ]
56
+ end
57
+
58
+ def merge(config)
59
+ @wardrobe_stores = wardrobe_stores.merge(config, self)
60
+ end
61
+
62
+ def define_getter(atr)
63
+ @wardrobe_methods.instance_exec do
64
+ define_method(atr.name) do
65
+ atr.getters.inject(nil) do |val, getter|
66
+ getter.block.call(val, atr, self)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def define_setter(atr)
73
+ @wardrobe_methods.instance_exec do
74
+ define_method(atr.setter_name) do |input|
75
+ atr.setters.inject(input) do |val, setter|
76
+ setter.block.call(val, atr, self)
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ def attribute(name, klass, *args, &blk)
84
+ merged_args = option_store.defaults.merge(args.inject({}) { |input, val| input.merge! val })
85
+ @wardrobe_stores = wardrobe_stores.add_attribute(
86
+ name, klass, self, **merged_args, &blk
87
+ )
88
+ define_getter(attribute_store[name])
89
+ define_setter(attribute_store[name])
90
+ end
91
+
92
+ def attributes(**kargs, &blk)
93
+ BlockSetup.new(self).run(**kargs, &blk)
94
+ end
95
+
96
+ def remove_attributes(*wardrobe)
97
+ wardrobe.each do |name|
98
+ @wardrobe_stores = wardrobe_stores.remove_attribute(name)
99
+ end
100
+ end
101
+
102
+ alias remove_attribute remove_attributes
103
+
104
+ def plugin(name, **args)
105
+ @wardrobe_stores = wardrobe_stores.enable_plugin(name, **args)
106
+ plugin = plugin_store[name][:klass]
107
+
108
+ if plugin.const_defined?(:ClassMethods)
109
+ extend(plugin.const_get(:ClassMethods))
110
+ end
111
+
112
+ if plugin.const_defined?(:InstanceMethods)
113
+ include(plugin.const_get(:InstanceMethods))
114
+ end
115
+
116
+ # Currently these are not needed
117
+ #
118
+ # if plugin.const_defined?(:AttributeClassMethods)
119
+ # Attribute.extend(plugin.const_get(:AttributeClassMethods))
120
+ # end
121
+ #
122
+ # if plugin.const_defined?(:AttributeInstanceMethods)
123
+ # Attribute.include(plugin.const_get(:AttributeInstanceMethods))
124
+ # end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,48 @@
1
+ module Wardrobe
2
+ class << self
3
+ attr_reader :setters, :getters
4
+ end
5
+ @setters = {}
6
+ @getters = {}
7
+
8
+ class SetterGetter
9
+ attr_reader :name, :priority, :block, :use_if
10
+
11
+ def initialize(name, priority, block, use_if)
12
+ @name = name
13
+ @priority = priority
14
+ @block = block
15
+ @use_if = use_if
16
+ end
17
+
18
+ def <=>(other)
19
+ priority <=> other.priority
20
+ end
21
+ end
22
+
23
+ def self.register_setter(name:, priority:, setter:, use_if: ->(_atr) { true })
24
+ raise 'Name taken' if setters[name]
25
+ setters[name] = SetterGetter.new(name, priority, setter, use_if)
26
+ end
27
+
28
+ def self.register_getter(name:, priority:, getter:, use_if: ->(_atr) { true })
29
+ raise 'Name taken' if getters[name]
30
+ getters[name] = SetterGetter.new(name, priority, getter, use_if)
31
+ end
32
+
33
+ Wardrobe.register_getter(
34
+ name: :getter,
35
+ priority: 0,
36
+ getter: lambda do |_value, atr, instance|
37
+ instance.instance_variable_get(atr.ivar_name)
38
+ end
39
+ )
40
+
41
+ Wardrobe.register_setter(
42
+ name: :setter,
43
+ priority: 100,
44
+ setter: lambda do |value, atr, instance|
45
+ instance.instance_variable_set(atr.ivar_name, value)
46
+ end
47
+ )
48
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module InstanceMethods
5
+ def initialize(**hash)
6
+ # raise "POC: More than one arg not supported..." if args.length > 1
7
+ # kargs = args.first.merge(kargs) if args.any?
8
+ _initialize { _wardrobe_init(hash) }
9
+ end
10
+
11
+ def _initializing?
12
+ instance_variable_defined?(:@_initializing) && @_initializing
13
+ end
14
+
15
+ def _attribute_store
16
+ _attribute_store_singleton || self.class.attribute_store
17
+ end
18
+
19
+ def _add_attribute(name, klass, **args, &blk)
20
+ @_wardrobe_set_singleton = _attribute_store.add(
21
+ name, klass, self.class, self.class.wardrobe_stores, **args, &blk
22
+ )
23
+ end
24
+
25
+ def _set_attribute_value(atr, value)
26
+ instance_variable_set(atr.ivar_name, value)
27
+ end
28
+
29
+ def _get_attribute_value(atr)
30
+ instance_variable_get(atr.ivar_name)
31
+ end
32
+
33
+ private
34
+
35
+ def _initialize
36
+ instance_variable_set(:@_initializing, true)
37
+ yield
38
+ remove_instance_variable(:@_initializing)
39
+ end
40
+
41
+ def _attribute_store_singleton
42
+ @_wardrobe_set_singleton if instance_variable_defined?('@_wardrobe_set_singleton')
43
+ end
44
+
45
+ def _wardrobe_init(data)
46
+ # Should we also loop over the hash and report on missing or additional attributes?
47
+ _attribute_store.each do |name, atr|
48
+ _attribute_init(atr, data, name)
49
+ end
50
+ end
51
+
52
+ def _attribute_init(atr, hash, name)
53
+ send(atr.setter_name, hash[name])
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Wardrobe
6
+ # This module is extended in Wardrobe and extended to anonymous modules created by
7
+ # Wardrobe() method.
8
+ module ModuleMethods
9
+ def included(base)
10
+ base.extend(ClassMethods)
11
+ unless base.to_s == 'Wardrobe::RootConfig'
12
+ base.root_config = config
13
+ config.default_plugins.each do |plugin|
14
+ # TODO: Support k_args
15
+ base.plugin plugin
16
+ end
17
+ end
18
+ base.include(InstanceMethods)
19
+ end
20
+
21
+ def create_class(plugins: [], attributes: [])
22
+ Class.new.class_exec do
23
+ include Wardrobe
24
+ plugin(*plugins)
25
+ attributes.each do |atr|
26
+ attribute(atr[:name], const_get(atr[:class]), atr.fetch(:options, {}))
27
+ end
28
+ self
29
+ end
30
+ end
31
+
32
+ def config
33
+ @config ||= begin
34
+ callee = caller_locations.first
35
+ if callee.label == 'config' && callee.lineno == (__LINE__ + 3)
36
+ RootConfig.new
37
+ else
38
+ Wardrobe.config
39
+ end
40
+ end
41
+ end
42
+
43
+ def configure(&blk)
44
+ @config = config.mutate(&blk)
45
+ end
46
+
47
+ def logger
48
+ config.logger
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ class Option
5
+ attr_reader :name, :klass, :plugin, :default, :options, :setter, :getter
6
+
7
+ def initialize(name, klass, plugin, **kargs)
8
+ @name = name
9
+ @klass = klass
10
+ @plugin = plugin
11
+ @default = kargs.fetch(:default, nil)
12
+ @getter = Wardrobe.getters[kargs.fetch(:getter, nil)]
13
+ @setter = Wardrobe.setters[kargs.fetch(:setter, nil)]
14
+ @options = kargs
15
+ freeze
16
+ end
17
+
18
+ def klass_name
19
+ klass.to_s[/([^:]+)$/,1].downcase
20
+ end
21
+
22
+ def use_getter_for_atr?(atr)
23
+ getter&.use_if&.call(atr)
24
+ end
25
+
26
+ def use_setter_for_atr?(atr)
27
+ setter&.use_if&.call(atr)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ class OptionStore < Store
5
+ attr_reader :store, :defaults
6
+
7
+ def freeze
8
+ @defaults = select_defaults
9
+ super
10
+ end
11
+
12
+ def add(name, instance)
13
+ mutate { store[name] = instance }
14
+ end
15
+
16
+ private
17
+
18
+ def select_defaults
19
+ store.reject { |_k, v| v.default.nil? }.transform_values(&:default)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ class PluginNameTakenError < StandardError; end
5
+ class PluginOptionKeywordTakenError < StandardError; end
6
+
7
+ class << self
8
+ attr_reader :plugins, :options
9
+ end
10
+ @plugins = {}
11
+ @options = {}
12
+
13
+ def self.register_plugin(name, mod)
14
+ raise PluginNameTakenError, "Plugin #{name} already in use" if plugins[name]
15
+ plugins[name] = mod
16
+ end
17
+
18
+ module Plugin
19
+ def options
20
+ @options ||= []
21
+ end
22
+
23
+ def option(name, klass, **kargs, &blk)
24
+ raise PluginOptionKeywordTaken if Wardrobe.options[name]
25
+ option_instance = Option.new(name, klass, self, **kargs, &blk)
26
+ Wardrobe.options[name] = option_instance
27
+ BlockSetup.register_option(option_instance)
28
+ options << option_instance
29
+ #
30
+ # # TODO: Refactor this to apply only if plugin is in use
31
+ #
32
+ # raise PluginOptionKeywordTaken if Wardrobe.options[:name]
33
+ # # These needs to go somewhere else
34
+ # @option_name = name
35
+ # @option_klass = klass
36
+ # @option_default = default
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ class NoPluginRegisteredError < StandardError; end
5
+
6
+ class PluginStore < Store
7
+ attr_reader :store
8
+ def add(name, **args)
9
+ begin
10
+ plugin = Wardrobe.plugins.fetch(name)
11
+ rescue KeyError
12
+ begin
13
+ if require "wardrobe/plugins/#{name}"
14
+ retry
15
+ end
16
+ rescue LoadError
17
+ raise NoPluginRegisteredError, name
18
+ end
19
+ end
20
+ mutate do
21
+ store[name] = {
22
+ klass: plugin,
23
+ options: args
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module AliasSetters
6
+ extend Wardrobe::Plugin
7
+
8
+ option :alias_setter, Array
9
+
10
+ module ClassMethods
11
+ def define_setter(atr)
12
+ super
13
+ atr.options[:alias_setter]&.each do |alias_setter|
14
+ alias_method "#{alias_setter}=", atr.setter_name
15
+ end
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ private
21
+
22
+ def _attribute_init(atr, hash, name)
23
+ return super unless atr.options[:alias_setter]
24
+ return super if atr.options[:alias_setter].empty?
25
+ keys_in_hash = atr.options[:alias_setter].select { |key| hash.key?(key) }
26
+ return super unless keys_in_hash.any?
27
+ keys_in_hash.reverse.each do |key|
28
+ send("#{name}=", atr.coerce(hash[key]))
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ register_plugin(:alias_setters, Plugins::AliasSetters)
35
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ refine Array.singleton_class do
8
+ def coerce(v, _atr)
9
+ case v
10
+ when self then v
11
+ when Set then v.to_a
12
+ when NilClass then []
13
+ else
14
+ raise UnsupportedError
15
+ end
16
+ end
17
+ end
18
+
19
+
20
+
21
+ refine Array do
22
+ class WrongNumberOfItemsError < StandardError; end
23
+
24
+ module ArrayInstanceCoercer
25
+ def _wardrobe_init(atr, coercer: nil)
26
+ @_wardrobe_atr = atr
27
+ @_wardrobe_coercer = coercer
28
+ self
29
+ end
30
+
31
+ def _coerce(item)
32
+ @_wardrobe_coercer.coerce(item, @_wardrobe_atr)
33
+ end
34
+
35
+ def <<(item)
36
+ super(_coerce(item))
37
+ end
38
+
39
+ def push(*items)
40
+ super(*items.map { |i| _coerce(i) })
41
+ end
42
+
43
+ def unshift(*items)
44
+ super(*items.map { |i| _coerce(i) })
45
+ end
46
+ end
47
+
48
+ def coerce(v, atr)
49
+ res = case v
50
+ when Array
51
+ v.map! { |item| first.coerce(item, nil) }
52
+ when NilClass then []
53
+ else
54
+ raise UnsupportedError
55
+ end
56
+ res.singleton_class.include(ArrayInstanceCoercer)
57
+ res._wardrobe_init(atr, coercer: first)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ TRUE_STRINGS = Set.new(['1', 'yes', 'true']).freeze
8
+ FALSE_STRINGS = Set.new(['0', 'no', 'false']).freeze
9
+ refine Wardrobe::Boolean.singleton_class do
10
+ def coerce(v, _atr)
11
+ case v
12
+ when TrueClass, FalseClass then v
13
+ when Integer then integer(v)
14
+ when String then string(v)
15
+ when NilClass then nil
16
+ else
17
+ raise UnsupportedError
18
+ end
19
+ end
20
+
21
+ def integer(v)
22
+ return false if v == 0
23
+ return true if v == 1
24
+ raise UnsupportedError
25
+ end
26
+
27
+ def string(v)
28
+ return false if FALSE_STRINGS.include?(v)
29
+ return true if TRUE_STRINGS.include?(v)
30
+ raise UnsupportedError
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/CyclomaticComplexity
4
+
5
+ require 'date'
6
+ module Wardrobe
7
+ module Plugins
8
+ module Coercible
9
+ module Refinements
10
+ refine Date.singleton_class do
11
+ def coerce(v, _atr)
12
+ case v
13
+ # DateTime has to be first. `DateTime.new === Date.new # => true`
14
+ when DateTime then v.to_date
15
+ when self then v
16
+ when String then Date.parse(v)
17
+ when Time then v.to_date
18
+ when Integer then Time.at(v).to_date
19
+ when Float then Time.at(v).to_date
20
+ when NilClass then nil
21
+ else
22
+ raise UnsupportedError
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ # rubocop:enable Metrics/CyclomaticComplexity
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/CyclomaticComplexity
4
+
5
+ module Wardrobe
6
+ module Plugins
7
+ module Coercible
8
+ module Refinements
9
+ refine DateTime.singleton_class do
10
+ def coerce(v, _atr)
11
+ case v
12
+ when self then v
13
+ when String then DateTime.parse(v)
14
+ when Date then v.to_datetime
15
+ when Time then v.to_datetime
16
+ when Integer then Time.at(v).to_datetime
17
+ when Float then Time.at(v).to_datetime
18
+ when NilClass then nil
19
+ else
20
+ raise UnsupportedError
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # rubocop:enable Metrics/CyclomaticComplexity
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ refine Float.singleton_class do
8
+ def coerce(v, _atr)
9
+ case v
10
+ when self then v
11
+ when String, Integer then v.to_f
12
+ when Time then v.to_f
13
+ when Date then v.to_time.to_f
14
+ when NilClass then nil
15
+ else
16
+ raise UnsupportedError
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ refine Hash.singleton_class do
8
+ def coerce(v, _atr)
9
+ case v
10
+ when self then v
11
+ when Array then self[*v]
12
+ when NilClass then {}
13
+ end
14
+ end
15
+ end
16
+
17
+ refine Hash do
18
+ def coerce(v, atr)
19
+ case v
20
+ when Hash
21
+ coerce_hash(v, atr)
22
+ when Array
23
+ coerce_hash(self.class[*v], atr)
24
+ when NilClass then {}
25
+ else
26
+ raise UnsupportedError
27
+ end
28
+ end
29
+
30
+ HASH_SINGLETON_MODIFIER = lambda { |coercer, atr_object|
31
+ @_coercer = coercer
32
+ @_atr = atr_object
33
+
34
+ def _coercer
35
+ self.singleton_class.instance_variable_get(:@_coercer)
36
+ end
37
+
38
+ def self._atr; @_atr; end
39
+
40
+ def _coerce(key, value)
41
+ return _coercer[0].coerce(key, nil),
42
+ _coercer[1].coerce(value, nil)
43
+ end
44
+
45
+ def []=(key, value)
46
+ super(*_coerce(key,value))
47
+ end
48
+ }
49
+
50
+ private
51
+
52
+ def coerce_hash(h, atr)
53
+ hash = h.map do |key, value|
54
+ #TODO: Since we pass around the atr object, how should this work here? Just pass nil?
55
+ [self.first[0].coerce(key, nil), self.first[1].coerce(value, nil)]
56
+ end.to_h
57
+
58
+ hash.singleton_class.class_exec(first,atr, &HASH_SINGLETON_MODIFIER)
59
+ hash
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end