serega 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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