serega 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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/VERSION +1 -0
  3. data/lib/serega/attribute.rb +130 -0
  4. data/lib/serega/config.rb +48 -0
  5. data/lib/serega/convert.rb +45 -0
  6. data/lib/serega/convert_item.rb +37 -0
  7. data/lib/serega/helpers/serializer_class_helper.rb +11 -0
  8. data/lib/serega/map.rb +49 -0
  9. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +61 -0
  10. data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +95 -0
  11. data/lib/serega/plugins/context_metadata/context_metadata.rb +74 -0
  12. data/lib/serega/plugins/formatters/formatters.rb +49 -0
  13. data/lib/serega/plugins/hide_nil/hide_nil.rb +80 -0
  14. data/lib/serega/plugins/metadata/meta_attribute.rb +74 -0
  15. data/lib/serega/plugins/metadata/metadata.rb +123 -0
  16. data/lib/serega/plugins/metadata/validations/check_block.rb +45 -0
  17. data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +31 -0
  18. data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +31 -0
  19. data/lib/serega/plugins/metadata/validations/check_opts.rb +41 -0
  20. data/lib/serega/plugins/metadata/validations/check_path.rb +53 -0
  21. data/lib/serega/plugins/preloads/lib/enum_deep_freeze.rb +17 -0
  22. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +53 -0
  23. data/lib/serega/plugins/preloads/lib/main_preload_path.rb +40 -0
  24. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +60 -0
  25. data/lib/serega/plugins/preloads/preloads.rb +100 -0
  26. data/lib/serega/plugins/preloads/validations/check_opt_preload_path.rb +38 -0
  27. data/lib/serega/plugins/presenter/presenter.rb +111 -0
  28. data/lib/serega/plugins/root/root.rb +65 -0
  29. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +64 -0
  30. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +32 -0
  31. data/lib/serega/plugins/validate_modifiers/validate.rb +51 -0
  32. data/lib/serega/plugins/validate_modifiers/validate_modifiers.rb +44 -0
  33. data/lib/serega/plugins.rb +51 -0
  34. data/lib/serega/utils/as_json.rb +35 -0
  35. data/lib/serega/utils/enum_deep_dup.rb +43 -0
  36. data/lib/serega/utils/to_hash.rb +52 -0
  37. data/lib/serega/utils/to_json.rb +22 -0
  38. data/lib/serega/validations/attribute/check_block.rb +81 -0
  39. data/lib/serega/validations/attribute/check_name.rb +45 -0
  40. data/lib/serega/validations/attribute/check_opt_hide.rb +20 -0
  41. data/lib/serega/validations/attribute/check_opt_many.rb +20 -0
  42. data/lib/serega/validations/attribute/check_opt_method.rb +25 -0
  43. data/lib/serega/validations/attribute/check_opt_serializer.rb +36 -0
  44. data/lib/serega/validations/attribute/check_opts.rb +36 -0
  45. data/lib/serega/validations/check_allowed_keys.rb +13 -0
  46. data/lib/serega/validations/check_opt_is_bool.rb +14 -0
  47. data/lib/serega/validations/check_opt_is_hash.rb +14 -0
  48. data/lib/serega/version.rb +5 -0
  49. data/lib/serega.rb +265 -0
  50. metadata +94 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ac327fb1d98a08b76fcdfdc68166265bea134f298892f0a2f9b1a25c7d1e6063
4
+ data.tar.gz: 9843b54690c3ab5e4c4ad0c651e5e7629915eb502a0913520cec9153e6ebfa9f
5
+ SHA512:
6
+ metadata.gz: 7260b7ff2a939f93510565e50a19e6ad3c4fe6899c8ddb927938714bf422d0faedab7e62c638f18e98af13b6f9403cd30d4d72088dd6209f083ad4338f4d64da
7
+ data.tar.gz: b54726235c873c143fc3458837f1ecce05ffa26229d37e6b7420b304b8f1bff3bdde9e4a73fcd91c0e19664918cd2080d708e105398d4d30f15e6b440a7d737f
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ #
5
+ # Stores Attribute data
6
+ #
7
+ class Attribute
8
+ #
9
+ # Stores Attribute instance methods
10
+ #
11
+ module AttributeInstanceMethods
12
+ # @return [Symbol] Attribute name
13
+ attr_reader :name
14
+
15
+ # @return [Hash] Attribute options
16
+ attr_reader :opts
17
+
18
+ # @return [Proc] Attribute originally added block
19
+ attr_reader :block
20
+
21
+ #
22
+ # Initializes new attribute
23
+ #
24
+ # @param name [Symbol, String] Name of attribute
25
+ #
26
+ # @param opts [Hash] Attribute options
27
+ # @option opts [Symbol] :key Object instance method name to get attribute value
28
+ # @option opts [Boolean] :exposed Configures if we should serialize this attribute by default.
29
+ # (by default is true for regular attributes and false for relationships)
30
+ # @option opts [Boolean] :many Specifies has_many relationship. By default is detected via object.is_a?(Enumerable)
31
+ # @option opts [Serega, Proc] :serializer Relationship serializer class. Use `proc { MySerializer }` if serializers have cross references.
32
+ #
33
+ # @param block [Proc] Proc that receives object and context and finds attribute value
34
+ #
35
+ def initialize(name:, opts: {}, block: nil)
36
+ check(name, opts, block)
37
+
38
+ @name = name.to_sym
39
+ @opts = Utils::EnumDeepDup.call(opts)
40
+ @block = opts[:value] || block
41
+ end
42
+
43
+ # @return [Symbol] Object method name to will be used to get attribute value unless block provided
44
+ def key
45
+ @key ||= opts.key?(:key) ? opts[:key].to_sym : name
46
+ end
47
+
48
+ # @return [Boolean, nil] Attribute initial :hide option value
49
+ def hide
50
+ opts[:hide]
51
+ end
52
+
53
+ # @return [Boolean, nil] Attribute initialization :many option
54
+ def many
55
+ opts[:many]
56
+ end
57
+
58
+ # @return [Boolean] Checks if attribute is relationship (if :serializer option exists)
59
+ def relation?
60
+ !opts[:serializer].nil?
61
+ end
62
+
63
+ # @return [Serega, nil] Attribute serializer if exists
64
+ def serializer
65
+ return @serializer if instance_variable_defined?(:@serializer)
66
+
67
+ serializer = opts[:serializer]
68
+ @serializer =
69
+ case serializer
70
+ when String then Object.const_get(serializer, false)
71
+ when Proc then serializer.call
72
+ else serializer
73
+ end
74
+ end
75
+
76
+ # @return [Proc] Proc to find attribute value
77
+ def value_block
78
+ return @value_block if instance_variable_defined?(:@value_block)
79
+
80
+ @value_block = block || keyword_block
81
+ end
82
+
83
+ #
84
+ # Finds attribute value
85
+ #
86
+ # @param object [Object] Serialized object
87
+ # @param context [Hash, nil] Serialization context
88
+ #
89
+ # @return [Object] Serialized attribute value
90
+ #
91
+ def value(object, context)
92
+ value_block.call(object, context)
93
+ end
94
+
95
+ #
96
+ # Checks if attribute must be added to serialized response
97
+ #
98
+ # @param except [Hash] manually hidden attributes
99
+ # @param only [Hash] manually enforced exposed attributes, other attributes are enforced to be hidden
100
+ # @param with [Hash] manually enforced exposed attributes
101
+ #
102
+ # @return [Boolean]
103
+ #
104
+ def visible?(except:, only:, with:)
105
+ return false if except.member?(name) && except[name].empty?
106
+ return true if only.member?(name)
107
+ return true if with.member?(name)
108
+ return false unless only.empty?
109
+
110
+ !hide
111
+ end
112
+
113
+ private
114
+
115
+ def keyword_block
116
+ key_method_name = key
117
+ proc { |object| object.public_send(key_method_name) }
118
+ end
119
+
120
+ def check(name, opts, block)
121
+ CheckName.call(name)
122
+ CheckOpts.call(opts, self.class.serializer_class.config[:attribute_keys])
123
+ CheckBlock.call(opts, block)
124
+ end
125
+ end
126
+
127
+ extend Serega::Helpers::SerializerClassHelper
128
+ include AttributeInstanceMethods
129
+ end
130
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ class Serega
6
+ #
7
+ # Core class that stores serializer configuration
8
+ #
9
+ class Config
10
+ module ConfigInstanceMethods
11
+ extend Forwardable
12
+
13
+ # @return [Hash] Current config data
14
+ attr_reader :opts
15
+
16
+ #
17
+ # Initializes new config instance and deeply duplicates all provided options to
18
+ # remove possibility of accidental overwriting of parent/nested configs.
19
+ #
20
+ # @param opts [Hash] Initial config options
21
+ #
22
+ def initialize(opts = {})
23
+ @opts = Utils::EnumDeepDup.call(opts)
24
+ end
25
+
26
+ #
27
+ # @!method [](name)
28
+ # Get config option, delegates to opts#[]
29
+ # @param name [Symbol] option name
30
+ # @return [Object]
31
+ #
32
+ # @!method []=(name)
33
+ # Set config option, delegates to opts#[]=
34
+ # @param name [Symbol] option name
35
+ # @return [Object]
36
+ #
37
+ # @!method fetch(name)
38
+ # Fetch config option, delegates to opts#fetch
39
+ # @param name [Symbol] option name
40
+ # @return [Object]
41
+ #
42
+ def_delegators :opts, :[], :[]=, :fetch, :keys, :has_key?
43
+ end
44
+
45
+ include ConfigInstanceMethods
46
+ extend Serega::Helpers::SerializerClassHelper
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ class Convert
5
+ module ConvertClassMethods
6
+ def call(object, **opts)
7
+ new(object, **opts).to_h
8
+ end
9
+ end
10
+
11
+ module ConvertInstanceMethods
12
+ attr_reader :object, :opts
13
+
14
+ def initialize(object, **opts)
15
+ @object = object
16
+ @opts = opts
17
+ end
18
+
19
+ def to_h
20
+ many? ? many(object) : one(object) || {}
21
+ end
22
+
23
+ private
24
+
25
+ def many(objects)
26
+ objects.map { |obj| one(obj) }
27
+ end
28
+
29
+ def one(object)
30
+ self.class.serializer_class::ConvertItem.call(object, opts[:context], opts[:map])
31
+ end
32
+
33
+ def many?
34
+ many = opts[:many]
35
+ return many unless many.nil?
36
+
37
+ object.is_a?(Enumerable)
38
+ end
39
+ end
40
+
41
+ extend Serega::Helpers::SerializerClassHelper
42
+ extend ConvertClassMethods
43
+ include ConvertInstanceMethods
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ class ConvertItem
5
+ module ConvertItemClassMethods
6
+ def call(object, context, map)
7
+ return unless object
8
+
9
+ map.each_with_object({}) do |(attribute, nested_attributes), hash|
10
+ value = attribute.value(object, context)
11
+ attach_value(value, hash, attribute, nested_attributes, context)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def attach_value(value, hash, attribute, nested_attributes, context)
18
+ hash[attribute.name] =
19
+ if nested_attributes.empty?
20
+ attribute.relation? ? FROZEN_EMPTY_HASH : value
21
+ elsif many?(attribute, value)
22
+ value.map { |val| call(val, context, nested_attributes) }
23
+ else
24
+ call(value, context, nested_attributes)
25
+ end
26
+ end
27
+
28
+ def many?(attribute, object)
29
+ is_many = attribute.many
30
+ is_many.nil? ? object.is_a?(Enumerable) : is_many
31
+ end
32
+ end
33
+
34
+ extend Serega::Helpers::SerializerClassHelper
35
+ extend ConvertItemClassMethods
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Helpers
5
+ # Stores link to current serializer class
6
+ module SerializerClassHelper
7
+ # @return [Class<Serega>] Serializer class that current class is namespaced under.
8
+ attr_accessor :serializer_class
9
+ end
10
+ end
11
+ end
data/lib/serega/map.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ class Map
5
+ module ClassMethods
6
+ def call(opts)
7
+ @cache ||= {}
8
+ cache_key = opts.to_s
9
+
10
+ @cache[cache_key] ||= begin
11
+ modifiers = {
12
+ only: opts&.[](:only) || FROZEN_EMPTY_HASH,
13
+ except: opts&.[](:except) || FROZEN_EMPTY_HASH,
14
+ with: opts&.[](:with) || FROZEN_EMPTY_HASH
15
+ }
16
+
17
+ construct_map(serializer_class, **modifiers).tap do
18
+ @cache.shift if @cache.length >= serializer_class.config[:max_cached_map_per_serializer_count]
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def construct_map(serializer_class, only:, except:, with:)
26
+ serializer_class.attributes.each_with_object([]) do |(name, attribute), map|
27
+ next unless attribute.visible?(only: only, except: except, with: with)
28
+
29
+ nested_map =
30
+ if attribute.relation?
31
+ construct_map(
32
+ attribute.serializer,
33
+ only: only[name] || FROZEN_EMPTY_HASH,
34
+ with: with[name] || FROZEN_EMPTY_HASH,
35
+ except: except[name] || FROZEN_EMPTY_HASH
36
+ )
37
+ else
38
+ FROZEN_EMPTY_ARRAY
39
+ end
40
+
41
+ map << [attribute, nested_map]
42
+ end
43
+ end
44
+ end
45
+
46
+ extend ClassMethods
47
+ extend Serega::Helpers::SerializerClassHelper
48
+ end
49
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ #
6
+ # Plugin that checks used plugins and loads correct Preloader for selected response type
7
+ # @see Serega::Plugins::JsonApiActiverecordPreloader
8
+ # @see Serega::Plugins::SimpleApiActiverecordPreloader
9
+ #
10
+ module ActiverecordPreloads
11
+ # @return [Symbol] plugin name
12
+ def self.plugin_name
13
+ :activerecord_preloads
14
+ end
15
+
16
+ def self.before_load_plugin(serializer_class, **opts)
17
+ serializer_class.plugin(:preloads, **opts) unless serializer_class.plugin_used?(:preloads)
18
+ end
19
+
20
+ #
21
+ # Loads plugin code and additional plugins
22
+ #
23
+ # @param serializer_class [Class<Serega>] Current serializer class
24
+ # @param opts [Hash] loaded plugins opts
25
+ #
26
+ # @return [void]
27
+ #
28
+ def self.load_plugin(serializer_class, **opts)
29
+ require_relative "./lib/preloader"
30
+
31
+ serializer_class.include(InstanceMethods)
32
+ end
33
+
34
+ # Overrides Serega classes instance methods
35
+ module InstanceMethods
36
+ #
37
+ # Override original #to_h method
38
+ # @see Serega#to_h
39
+ #
40
+ def to_h(object, **opts)
41
+ object = add_preloads(object)
42
+ super
43
+ end
44
+
45
+ private
46
+
47
+ def add_preloads(obj)
48
+ return obj if obj.nil? || (obj.is_a?(Array) && obj.empty?)
49
+
50
+ # preloads() method comes from :preloads plugin
51
+ preloads = preloads()
52
+ return obj if preloads.empty?
53
+
54
+ Preloader.preload(obj, preloads)
55
+ end
56
+ end
57
+ end
58
+
59
+ register_plugin(ActiverecordPreloads.plugin_name, ActiverecordPreloads)
60
+ end
61
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module ActiverecordPreloads
6
+ class Preloader
7
+ module ClassMethods
8
+ def preload(object, preloads)
9
+ preload_handler = handlers.find { |handler| handler.fit?(object) }
10
+ raise Error, "Can't preload #{preloads.inspect} to #{object.inspect}" unless preload_handler
11
+
12
+ preload_handler.preload(object, preloads)
13
+ end
14
+
15
+ def handlers
16
+ @handlers ||= [ActiverecordRelation, ActiverecordObject, ActiverecordArray].freeze
17
+ end
18
+ end
19
+
20
+ extend ClassMethods
21
+ end
22
+
23
+ class Loader
24
+ # :nocov: We can check only one version of activerecord
25
+ def self.call(records, associations)
26
+ if ActiveRecord::VERSION::MAJOR >= 7
27
+ ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
28
+ else
29
+ ActiveRecord::Associations::Preloader.new.preload(records, associations)
30
+ end
31
+ end
32
+ # :nocov:
33
+ end
34
+
35
+ class ActiverecordObject
36
+ module ClassMethods
37
+ def fit?(object)
38
+ object.is_a?(ActiveRecord::Base)
39
+ end
40
+
41
+ def preload(object, preloads)
42
+ Loader.call([object], preloads)
43
+ object
44
+ end
45
+ end
46
+
47
+ extend ClassMethods
48
+ end
49
+
50
+ class ActiverecordRelation
51
+ module ClassMethods
52
+ def fit?(objects)
53
+ objects.is_a?(ActiveRecord::Relation)
54
+ end
55
+
56
+ def preload(objects, preloads)
57
+ if objects.loaded?
58
+ array_objects = objects.to_a
59
+ Loader.call(array_objects, preloads)
60
+ objects
61
+ else
62
+ objects.preload(preloads).load
63
+ end
64
+ end
65
+ end
66
+
67
+ extend ClassMethods
68
+ end
69
+
70
+ class ActiverecordArray
71
+ module ClassMethods
72
+ def fit?(objects)
73
+ objects.is_a?(Array) &&
74
+ ActiverecordObject.fit?(objects.first) &&
75
+ same_kind?(objects)
76
+ end
77
+
78
+ def preload(objects, preloads)
79
+ Loader.call(objects, preloads)
80
+ objects
81
+ end
82
+
83
+ private
84
+
85
+ def same_kind?(objects)
86
+ first_object_class = objects.first.class
87
+ objects.all? { |object| object.instance_of?(first_object_class) }
88
+ end
89
+ end
90
+
91
+ extend ClassMethods
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module ContextMetadata
6
+ DEFAULT_CONTEXT_METADATA_KEY = :meta
7
+
8
+ def self.plugin_name
9
+ :context_metadata
10
+ end
11
+
12
+ def self.before_load_plugin(serializer_class, **opts)
13
+ serializer_class.plugin(:root, **opts) unless serializer_class.plugin_used?(:root)
14
+ end
15
+
16
+ def self.load_plugin(serializer_class, **_opts)
17
+ serializer_class.include(InstanceMethods)
18
+ serializer_class::Convert.include(ConvertInstanceMethods)
19
+ end
20
+
21
+ def self.after_load_plugin(serializer_class, **opts)
22
+ config = serializer_class.config
23
+ meta_key = opts[:context_metadata_key] || DEFAULT_CONTEXT_METADATA_KEY
24
+ config[plugin_name] = {key: meta_key}
25
+ config[:serialize_keys] << meta_key
26
+ end
27
+
28
+ module InstanceMethods
29
+ def to_h(object, **opts)
30
+ meta_key = self.class.config[:context_metadata][:key]
31
+ meta = opts[meta_key]
32
+
33
+ if meta && !meta.is_a?(Hash)
34
+ raise Serega::Error, "Option :#{meta_key} must be a Hash, but #{meta.class} was given"
35
+ end
36
+
37
+ super
38
+ end
39
+ end
40
+
41
+ module ConvertInstanceMethods
42
+ def to_h
43
+ super.tap do |hash|
44
+ add_context_metadata(hash)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def add_context_metadata(hash)
51
+ context_metadata_key = self.class.serializer_class.config[:context_metadata][:key]
52
+ return unless context_metadata_key
53
+
54
+ metadata = opts[context_metadata_key]
55
+ return unless metadata
56
+
57
+ deep_merge_context_metadata(hash, metadata)
58
+ end
59
+
60
+ def deep_merge_context_metadata(hash, metadata)
61
+ hash.merge!(metadata) do |_key, this_val, other_val|
62
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
63
+ deep_merge_context_metadata(this_val, other_val)
64
+ else
65
+ other_val
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ register_plugin(ContextMetadata.plugin_name, ContextMetadata)
73
+ end
74
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ module Formatters
6
+ def self.plugin_name
7
+ :formatters
8
+ end
9
+
10
+ def self.load_plugin(serializer_class, **_opts)
11
+ serializer_class::Attribute.include(AttributeInstanceMethods)
12
+ end
13
+
14
+ def self.after_load_plugin(serializer_class, **_opts)
15
+ config = serializer_class.config
16
+ config[plugin_name] = {}
17
+ config[:attribute_keys] << :format
18
+ end
19
+
20
+ module AttributeInstanceMethods
21
+ def value_block
22
+ return @value_block if instance_variable_defined?(:@value_block)
23
+
24
+ original_block = super
25
+ formatter = opts[:format]
26
+ return original_block unless formatter
27
+
28
+ @value_block = formatted_block(formatter, original_block)
29
+ end
30
+
31
+ private
32
+
33
+ def formatted_block(formatter, original_block)
34
+ proc do |object, context|
35
+ value = original_block.call(object, context)
36
+
37
+ if formatter.is_a?(Symbol)
38
+ self.class.serializer_class.config.fetch(:formatters).fetch(formatter).call(value)
39
+ else
40
+ formatter.call(value)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ register_plugin(Formatters.plugin_name, Formatters)
48
+ end
49
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module Plugins
5
+ #
6
+ # Plugin adds `:hide_nil` option to attributes to delete them from final result
7
+ # if value is nil
8
+ #
9
+ module HideNil
10
+ # @return [Symbol] plugin name
11
+ def self.plugin_name
12
+ :hide_nil
13
+ end
14
+
15
+ #
16
+ # Includes plugin modules to current serializer
17
+ #
18
+ # @param serializer_class [Class] current serializer class
19
+ # @param _opts [Hash] plugin opts
20
+ #
21
+ # @return [void]
22
+ #
23
+ def self.load_plugin(serializer_class, **_opts)
24
+ serializer_class::Attribute.include(AttributeMethods)
25
+ serializer_class::Attribute::CheckOpts.extend(CheckOptsClassMethods)
26
+ serializer_class::ConvertItem.extend(ConvertItemClassMethods)
27
+ end
28
+
29
+ def self.after_load_plugin(serializer_class, **opts)
30
+ serializer_class.config[:attribute_keys] << :hide_nil
31
+ end
32
+
33
+ # Adds #hide_nil? Attribute instance method
34
+ module AttributeMethods
35
+ def hide_nil?
36
+ !!opts[:hide_nil]
37
+ end
38
+ end
39
+
40
+ module CheckOptsClassMethods
41
+ private
42
+
43
+ def check_each_opt(opts)
44
+ super
45
+ CheckOptHideNil.call(opts)
46
+ end
47
+ end
48
+
49
+ class CheckOptHideNil
50
+ #
51
+ # Checks attribute :hide_nil option
52
+ #
53
+ # @param opts [Hash] Attribute options
54
+ #
55
+ # @raise [Serega::Error] Error that option has invalid value
56
+ #
57
+ # @return [void]
58
+ #
59
+ def self.call(opts)
60
+ return unless opts.key?(:hide_nil)
61
+
62
+ value = opts[:hide_nil]
63
+ return if (value == true) || (value == false)
64
+
65
+ raise Error, "Invalid option :hide_nil => #{value.inspect}. Must have a boolean value"
66
+ end
67
+ end
68
+
69
+ module ConvertItemClassMethods
70
+ private
71
+
72
+ def attach_value(value, *args)
73
+ super unless value.nil?
74
+ end
75
+ end
76
+ end
77
+
78
+ register_plugin(HideNil.plugin_name, HideNil)
79
+ end
80
+ end