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,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ refine Integer.singleton_class do
8
+ def coerce(v, _atr)
9
+ case v
10
+ when self then v
11
+ when String, Float then v.to_i
12
+ when Time then v.to_i
13
+ when Date then v.to_time.to_i
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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ refine Object.singleton_class do
8
+ def coerce(v, _atr)
9
+ new(v)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ module Wardrobe
5
+ module Plugins
6
+ module Coercible
7
+ module Refinements
8
+ refine Set.singleton_class do
9
+ def coerce(v, _atr)
10
+ case v
11
+ when self then v
12
+ when Array then v.to_set
13
+ when NilClass then new
14
+ else
15
+ raise UnsupportedError
16
+ end
17
+ end
18
+ end
19
+ refine Set do
20
+ def coerce(v, _atr)
21
+ case v
22
+ when NilClass then self.class.new
23
+ when Array, Set
24
+ v.to_set.map! { |i| first.coerce(i, nil) }
25
+ else
26
+ raise UnsupportedError
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ refine String.singleton_class do
8
+ def coerce(v, _atr)
9
+ case v
10
+ when self then v
11
+ when Integer, Float, Symbol then v.to_s
12
+ when NilClass then nil
13
+ else
14
+ raise UnsupportedError
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ refine Symbol.singleton_class do
8
+ def coerce(v, _atr)
9
+ case v
10
+ when self then v
11
+ when String then v.to_sym
12
+ when NilClass then nil
13
+ else
14
+ raise UnsupportedError
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/CyclomaticComplexity
4
+
5
+ require 'time'
6
+
7
+ module Wardrobe
8
+ module Plugins
9
+ module Coercible
10
+ module Refinements
11
+ refine Time.singleton_class do
12
+ def coerce(v, _atr)
13
+ case v
14
+ when self then v
15
+ when String then Time.parse(v)
16
+ when Integer then Time.at(v)
17
+ when Float then Time.at(v)
18
+ when Date then v.to_time
19
+ when NilClass then nil
20
+ else
21
+ raise UnsupportedError
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ # rubocop:enable Metrics/CyclomaticComplexity
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Coercible
6
+ module Refinements
7
+ class UnsupportedError < StandardError; end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ require 'wardrobe/plugins/coercible/refinements/unsupported_error'
3
+ require 'wardrobe/plugins/coercible/refinements/array'
4
+ require 'wardrobe/plugins/coercible/refinements/set'
5
+ require 'wardrobe/plugins/coercible/refinements/date'
6
+ require 'wardrobe/plugins/coercible/refinements/date_time'
7
+ require 'wardrobe/plugins/coercible/refinements/float'
8
+ require 'wardrobe/plugins/coercible/refinements/hash'
9
+ require 'wardrobe/plugins/coercible/refinements/integer'
10
+ require 'wardrobe/plugins/coercible/refinements/object'
11
+ require 'wardrobe/plugins/coercible/refinements/string'
12
+ require 'wardrobe/plugins/coercible/refinements/boolean'
13
+ require 'wardrobe/plugins/coercible/refinements/symbol'
14
+ require 'wardrobe/plugins/coercible/refinements/time'
15
+
16
+ module Wardrobe
17
+ class Attribute
18
+ using Plugins::Coercible::Refinements
19
+
20
+ def coerce(val)
21
+ klass.coerce(val, self)
22
+ rescue Plugins::Coercible::Refinements::UnsupportedError => e
23
+ raise e.class,
24
+ "Can't coerce #{val.class} `#{val}` into #{klass}"
25
+ end
26
+ end
27
+
28
+ register_setter(
29
+ name: :coercer,
30
+ priority: -100,
31
+ use_if: ->(_atr) { true },
32
+ setter: lambda do |value, atr, _instance|
33
+ atr.coerce(value)
34
+ end
35
+ )
36
+
37
+ module Plugins
38
+ module Coercible
39
+ extend Wardrobe::Plugin
40
+ option :coerce, Boolean, default: true, setter: :coercer
41
+
42
+ module ClassMethods
43
+ def coerce(val, _atr)
44
+ return new if val.nil?
45
+ return new(**val) if val.is_a?(Hash)
46
+ new(val)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ register_plugin(:coercible, Plugins::Coercible)
52
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Configurable
6
+ class ConfigurableStore < Store
7
+ using ImmutableInstanceMethods
8
+ def register(name, klass)
9
+ mutate do
10
+ store.merge!(name => klass.new.freeze)
11
+ end
12
+ end
13
+
14
+ def update(name, &blk)
15
+ if frozen?
16
+ dup.update(name, &blk)
17
+ else
18
+ duplicate = @store[name].mutate(&blk)
19
+ @store = @store.merge(name => duplicate)
20
+ freeze
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configurable/configurable_store'
4
+
5
+ module Wardrobe
6
+ module Plugins
7
+ module Configurable
8
+ extend Wardrobe::Plugin
9
+
10
+ module ClassMethods
11
+ extend Forwardable
12
+ def_delegators :@wardrobe_stores, :configurable_store
13
+
14
+ def self.extended(base)
15
+ super
16
+ base.wardrobe_stores do
17
+ add_store(:configurable_store, ConfigurableStore)
18
+ end
19
+ end
20
+
21
+ def configurable(name, blk_name, klass)
22
+ wardrobe_stores do
23
+ @configurable_store = configurable_store.register(name, klass)
24
+ end
25
+
26
+ define_singleton_method(name) do
27
+ wardrobe_stores.configurable_store[name]
28
+ end
29
+
30
+ define_singleton_method(blk_name) do |&blk|
31
+ wardrobe_stores do
32
+ @configurable_store = configurable_store.update(name, &blk)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ register_plugin(:configurable, Plugins::Configurable)
40
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module Default
6
+ extend Wardrobe::Plugin
7
+
8
+ Wardrobe.register_setter(
9
+ name: :default_setter,
10
+ priority: 5,
11
+ use_if: ->(atr) { !atr.options[:default].nil? },
12
+ setter: lambda do |value, atr, instance|
13
+ if !value.nil? && ![{},[]].include?(value)
14
+ value
15
+ else
16
+ default = atr.options[:default]
17
+ case default
18
+ when Symbol
19
+ default.match?(/default$/) ? instance.send(default) : default
20
+ when Proc
21
+ default.arity.zero? ? default.call : default.call(instance)
22
+ else
23
+ default
24
+ end
25
+ end
26
+ end
27
+ )
28
+
29
+ option :default, Object, setter: :default_setter
30
+ end
31
+ end
32
+ register_plugin(:default, Plugins::Default)
33
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module DirtyTracker
6
+ extend Wardrobe::Plugin
7
+
8
+ Wardrobe.register_setter(
9
+ name: :dirty_tracker,
10
+ priority: 25,
11
+ use_if: ->(atr) { atr.options[:track] },
12
+ setter: lambda do |value, atr, instance|
13
+ return value if instance._initializing?
14
+ instance._dirty! if value != instance._get_attribute_value(atr)
15
+ value
16
+ end
17
+ )
18
+
19
+ Wardrobe.register_getter(
20
+ name: :dirty_tracker,
21
+ priority: 5,
22
+ use_if: ->(atr) { atr.options[:track] },
23
+ getter: lambda do |value, atr, instance|
24
+ instance._register_get(atr, value)
25
+ value
26
+ end
27
+ )
28
+
29
+ option :track, Boolean, default: true, setter: :dirty_tracker,
30
+ getter: :dirty_tracker#setter(10,&setter_proc)
31
+
32
+ module InstanceMethods
33
+ def _changed?
34
+ _fetched_attributes.delete_if do |atr, val|
35
+ if val == :wardrobe_instance
36
+ _dirty! if _get_attribute_value(atr)._changed?
37
+ else
38
+ _dirty! if _get_attribute_value(atr).hash != val
39
+ end
40
+ true
41
+ end
42
+ @changed ||= false
43
+ end
44
+
45
+ def _dirty!
46
+ @changed = true
47
+ end
48
+
49
+ def _register_get(atr, value)
50
+ _fetched_attributes[atr] ||= if value.respond_to?(:_changed?)
51
+ :wardrobe_instance
52
+ else
53
+ value.hash
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def _fetched_attributes
60
+ @_fetched_attributes ||= {}
61
+ end
62
+ end
63
+ end
64
+ end
65
+ register_plugin(:dirty_tracker, Plugins::DirtyTracker)
66
+ end
@@ -0,0 +1,19 @@
1
+ module Wardrobe
2
+ module Plugins
3
+ module Equality
4
+ extend Wardrobe::Plugin
5
+
6
+ option :include_in_equality, Boolean, default: true
7
+
8
+ module InstanceMethods
9
+ def ==(other)
10
+ _attribute_store.all? do |_name, atr|
11
+ return true unless atr.options[:include_in_equality]
12
+ send(atr.name) == other.send(atr.name)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ register_plugin(:equality, Plugins::Equality)
19
+ end
@@ -0,0 +1,38 @@
1
+ module Wardrobe
2
+ module Plugins
3
+ module HtmlInitializer
4
+ extend Wardrobe::Plugin
5
+
6
+ option :html_selector, Proc
7
+
8
+ def self.parse(html)
9
+ parser.parse(html)
10
+ end
11
+
12
+ def self.parser
13
+ @parser ||= begin
14
+ unless defined? Nokogiri
15
+ require 'nokogiri'
16
+ end
17
+ Nokogiri::HTML
18
+ end
19
+ end
20
+
21
+ module InstanceMethods
22
+ def initialize(html = nil, **args)
23
+ if html
24
+ define_singleton_method(:_attribute_init) do |atr, doc, _name|
25
+ value = atr.options[:html_selector].call(doc, atr, self)
26
+ send(atr.setter_name, value)
27
+ end
28
+ html = HtmlInitializer.parse(html) if html.is_a?(String)
29
+ _wardrobe_init(html)
30
+ else
31
+ super(**args)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ register_plugin(:html_initializer, Plugins::HtmlInitializer)
38
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+
4
+ module Wardrobe
5
+ module Plugins
6
+ module ImmutableInstanceMethods
7
+ IMMUTABLE_CORE_CLASSES = Set.new(
8
+ [NilClass, TrueClass, FalseClass, Integer, Symbol]
9
+ )
10
+
11
+ refine Object do
12
+ def _mutating?
13
+ instance_variable_defined?(:@_mutating) && @_mutating
14
+ end
15
+
16
+ def mutate(**args, &blk)
17
+ dup.instance_exec do
18
+ instance_variable_set(:@_mutating, true)
19
+ blk.call(self) if block_given?
20
+ args.each do |name, _value|
21
+ if (atr = _attribute_store[name])
22
+ _attribute_init(atr, args, name)
23
+ end
24
+ end
25
+ remove_instance_variable(:@_mutating)
26
+ deep_freeze
27
+ self
28
+ end
29
+ end
30
+
31
+ def mutate!
32
+ return self if IMMUTABLE_CORE_CLASSES.include?(self.class)
33
+ dup.instance_exec do
34
+ instance_variable_set(:@_mutating, true)
35
+ self
36
+ end
37
+ end
38
+ end
39
+
40
+ refine Hash do
41
+ def deep_freeze
42
+ each { |_k, v| v.deep_freeze }
43
+ freeze
44
+ end
45
+ end
46
+ refine String do
47
+ def deep_freeze
48
+ freeze
49
+ end
50
+ end
51
+ refine Logger do
52
+ def deep_freeze
53
+ freeze
54
+ end
55
+ end
56
+ refine Set do
57
+ def deep_freeze
58
+ # Refinements does not work here when using &:deep_freeze like in array
59
+ each { |item| item.deep_freeze }
60
+ remove_instance_variable(:@_mutating) if instance_variable_defined?(:@_mutating)
61
+ freeze
62
+ end
63
+ end
64
+ refine Array do
65
+ def deep_freeze
66
+ each(&:deep_freeze)
67
+ remove_instance_variable(:@_mutating) if instance_variable_defined?(:@_mutating)
68
+ freeze
69
+ end
70
+
71
+ def mutate!
72
+ dup.instance_exec do
73
+ instance_variable_set(:@_mutating, true)
74
+ map!(&:mutate!)
75
+ self
76
+ end
77
+ end
78
+ end
79
+ IMMUTABLE_CORE_CLASSES.each do |klass|
80
+ refine klass do
81
+ def deep_freeze
82
+ freeze
83
+ end
84
+ end
85
+ end
86
+ refine Wardrobe::InstanceMethods do
87
+ def mutate!
88
+ dup.instance_exec do
89
+ instance_variable_set(:@_mutating, true)
90
+ self
91
+ end
92
+ end
93
+
94
+ def deep_freeze
95
+ _attribute_store.each do |_name, atr|
96
+ instance_variable_get(atr.ivar_name).deep_freeze
97
+ end
98
+ remove_instance_variable(:@_mutating) if instance_variable_defined?(:@_mutating)
99
+ freeze
100
+ end
101
+ end
102
+ end
103
+
104
+ module Immutable
105
+ extend Wardrobe::Plugin
106
+
107
+ # This plugin will disable all setters. A "set(key, value)" will be enabled. Should we also use hash storage?
108
+ # This should also depend on the optonal_setter plugin and set it to default on
109
+
110
+ Wardrobe.register_setter(
111
+ name: :disable_setter_for_immutable_plugin,
112
+ priority: -101,
113
+ use_if: ->(_atr) { true },
114
+ setter: lambda do |value, atr, instance|
115
+ return value if instance._initializing? || !instance.frozen?
116
+ raise NoMethodError, <<~eos
117
+ undefined method `#{atr.name}=' for #{instance}.
118
+ The instance is immutable. Use `#mutate(key, value)' or `#mutate { |obj| obj.#{atr.name} = #{value.inspect}}'
119
+ eos
120
+ end
121
+ )
122
+
123
+ Wardrobe.register_getter(
124
+ name: :dup_when_using_set_block,
125
+ priority: 100,
126
+ use_if: ->(_atr) { true },
127
+ getter: lambda do |value, atr, instance|
128
+ using ImmutableInstanceMethods
129
+ if instance._initializing?
130
+ value
131
+ elsif instance._mutating?
132
+ instance._set_attribute_value(atr, value.mutate!)
133
+ # instance.instance_variable_set(atr.ivar_name, value.mutate!)
134
+ else
135
+ value
136
+ end
137
+ end
138
+ )
139
+
140
+ option :immutable, Boolean, default: true,
141
+ setter: :disable_setter_for_immutable_plugin,
142
+ getter: :dup_when_using_set_block
143
+
144
+ module InstanceMethods
145
+ using ImmutableInstanceMethods
146
+ def initialize(**hash)
147
+ super
148
+ _data.deep_freeze if instance_variable_defined?(:@_data)
149
+ freeze
150
+ end
151
+
152
+ def mutate(**args, &blk)
153
+ super
154
+ end
155
+
156
+ def _attribute_init(atr, hash, name)
157
+ super
158
+ send(atr.name).deep_freeze
159
+ end
160
+ end
161
+ end
162
+ end
163
+ register_plugin(:immutable, Plugins::Immutable)
164
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'presenter/refinements'
4
+ module Wardrobe
5
+ module Plugins
6
+ module IvyPresenter
7
+ extend Wardrobe::Plugin
8
+
9
+ option :preset, Set
10
+ option :source, Boolean
11
+
12
+ module InstanceMethods
13
+ def _ivy_present(**args)
14
+ attributes_hash = IvyPresenter.build_attributes_hash(_attribute_store, **args)
15
+ _present(attributes: attributes_hash)
16
+ end
17
+
18
+
19
+ # using Refinements
20
+ # def _present(wardrobe: nil)
21
+ # result = {}
22
+ # _attribute_store.store.each do |key, atr|
23
+ # if wardrobe.nil? || (wardrobe && wardrobe.key?(key))
24
+ # result[key] = send(atr.name)._present(wardrobe: (wardrobe[key] if wardrobe))
25
+ # end
26
+ # end
27
+ # result
28
+ # end
29
+ end
30
+
31
+ def self.build_attributes_hash(set, preset: nil, source: nil)
32
+ result = {}
33
+ set.each do |key, atr|
34
+ next unless atr.options[:preset]&.include?(preset) || atr.options[:source] == source
35
+ result[key] = nil
36
+ end
37
+ result
38
+ end
39
+ end
40
+ end
41
+ register_plugin(:ivy_presenter, Plugins::IvyPresenter)
42
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wardrobe
4
+ module Plugins
5
+ module JsonInitializer
6
+ extend Wardrobe::Plugin
7
+
8
+ def self.parse(json)
9
+ raise InvalidJsonError unless json.match(/\A[\[{].+[\]}]\z/m)
10
+ case parser.to_s
11
+ when 'JSON'
12
+ parser.parse(json, symbolize_names: true)
13
+ when 'MultiJson'
14
+ parser.load(json, symbolize_keys: true)
15
+ end
16
+ end
17
+
18
+ def self.parser
19
+ @parser ||= select_parser
20
+ end
21
+
22
+ def self.select_parser
23
+ return MultiJson if defined? MultiJson
24
+ return JSON if defined? JSON
25
+ require 'json'
26
+ JSON
27
+ end
28
+
29
+ module InstanceMethods
30
+ def initialize(json_string = nil, **args)
31
+ if json_string
32
+ super(**JsonInitializer.parse(json_string))
33
+ else
34
+ super(**args)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ register_plugin(:json_initializer, Plugins::JsonInitializer)
41
+ end