serega 0.2.0 → 0.4.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/serega/config.rb +65 -23
  4. data/lib/serega/errors.rb +12 -0
  5. data/lib/serega/json/adapter.rb +17 -0
  6. data/lib/serega/json/json.rb +17 -0
  7. data/lib/serega/json/oj.rb +17 -0
  8. data/lib/serega/map.rb +23 -13
  9. data/lib/serega/plugins/context_metadata/context_metadata.rb +31 -8
  10. data/lib/serega/plugins/formatters/formatters.rb +26 -4
  11. data/lib/serega/plugins/hide_nil/hide_nil.rb +1 -1
  12. data/lib/serega/plugins/lazy/lazy.rb +53 -0
  13. data/lib/serega/plugins/metadata/meta_attribute.rb +1 -1
  14. data/lib/serega/plugins/metadata/metadata.rb +27 -2
  15. data/lib/serega/plugins/preloads/preloads.rb +47 -11
  16. data/lib/serega/plugins/root/root.rb +45 -5
  17. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +6 -6
  18. data/lib/serega/validations/attribute/check_opt_delegate.rb +3 -3
  19. data/lib/serega/validations/attribute/check_opt_hide.rb +1 -1
  20. data/lib/serega/validations/attribute/check_opt_key.rb +1 -1
  21. data/lib/serega/validations/attribute/check_opt_many.rb +1 -1
  22. data/lib/serega/validations/check_attribute_params.rb +2 -2
  23. data/lib/serega/validations/check_initiate_params.rb +21 -8
  24. data/lib/serega/validations/check_serialize_params.rb +16 -10
  25. data/lib/serega/{plugins/validate_modifiers/validate.rb → validations/initiate/check_modifiers.rb} +3 -6
  26. data/lib/serega/validations/utils/check_allowed_keys.rb +1 -1
  27. data/lib/serega/validations/utils/check_opt_is_bool.rb +1 -1
  28. data/lib/serega/validations/utils/check_opt_is_hash.rb +1 -1
  29. data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +1 -1
  30. data/lib/serega.rb +29 -36
  31. metadata +8 -6
  32. data/lib/serega/plugins/validate_modifiers/validate_modifiers.rb +0 -44
  33. data/lib/serega/utils/as_json.rb +0 -35
  34. data/lib/serega/utils/to_json.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88e403fa3d489c2c708aca3f9df3734bcfc64f8e61eed4b96b9635cdf1a16622
4
- data.tar.gz: 1c7f92f7defa3c2415257e69021ed70f0c07b43b0f468969589a1ed18148507b
3
+ metadata.gz: 1aec10f062296a0af1d3f6b445f189ab2d84a145a8ed3cf0d52c81aa0c4f2c69
4
+ data.tar.gz: 1038ea10d7ea09b25d8f74b0561319d6f518a447121686ed689ff16385ec8806
5
5
  SHA512:
6
- metadata.gz: 50aec430bd1d42cccc89c82635700734569ffde5de0780782af0454feae87fbb11fc273b91bb1c936fb3001c26d24db72726928fc8f7d93ccf4b4b846a43147f
7
- data.tar.gz: 883c4e0e945663939b75c62e237b0268b9d8fd60ff2f31f7066a72021a9f90c045ca404c725ae787bc7268cf5f5cbd208f626c8f97ec22ebbc4208f0208e8233
6
+ metadata.gz: 8d568f45860671e760f56f67fd8206b2476b97ee5d415e881f23c9226285ef5b99a87b12ebd5da552664b6cd345ccb90cf7d43e10ed2a563ef436159f81afec5
7
+ data.tar.gz: 3100f9a6bd3b8fba77175677d4e53c01abf467cf2c57a5c1eea6a71ac2fbd73e7d2c0b0ff7071a032acdd73e6b37af37cc3259421e4fc58a96d5400fdea215ed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.4.0
data/lib/serega/config.rb CHANGED
@@ -7,39 +7,81 @@ class Serega
7
7
  # Core class that stores serializer configuration
8
8
  #
9
9
  class SeregaConfig
10
- module SeregaConfigInstanceMethods
11
- extend Forwardable
10
+ # :nocov: We can't use both :oj and :json adapters together
11
+ DEFAULTS = {
12
+ plugins: [],
13
+ initiate_keys: %i[only with except check_initiate_params].freeze,
14
+ attribute_keys: %i[key value serializer many hide const delegate].freeze,
15
+ serialize_keys: %i[context many].freeze,
16
+ check_initiate_params: true,
17
+ max_cached_map_per_serializer_count: 0,
18
+ to_json: SeregaJSON.adapter == :oj ? SeregaJSON::OjDump : SeregaJSON::JSONDump,
19
+ from_json: SeregaJSON.adapter == :oj ? SeregaJSON::OjLoad : SeregaJSON::JSONLoad
20
+ }.freeze
21
+ # :nocov:
12
22
 
13
- # @return [Hash] Current config data
23
+ module SeregaConfigInstanceMethods
14
24
  attr_reader :opts
15
25
 
16
26
  #
17
- # Initializes new config instance and deeply duplicates all provided options to
18
- # remove possibility of accidental overwriting of parent/nested configs.
27
+ # Initializes new config instance.
19
28
  #
20
29
  # @param opts [Hash] Initial config options
21
30
  #
22
- def initialize(opts = {})
31
+ def initialize(opts = nil)
32
+ opts ||= DEFAULTS
23
33
  @opts = SeregaUtils::EnumDeepDup.call(opts)
24
34
  end
25
35
 
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?
36
+ def plugins
37
+ opts.fetch(:plugins)
38
+ end
39
+
40
+ def initiate_keys
41
+ opts.fetch(:initiate_keys)
42
+ end
43
+
44
+ def attribute_keys
45
+ opts.fetch(:attribute_keys)
46
+ end
47
+
48
+ def serialize_keys
49
+ opts.fetch(:serialize_keys)
50
+ end
51
+
52
+ def check_initiate_params
53
+ opts.fetch(:check_initiate_params)
54
+ end
55
+
56
+ def check_initiate_params=(value)
57
+ raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
58
+ opts[:check_initiate_params] = value
59
+ end
60
+
61
+ def max_cached_map_per_serializer_count
62
+ opts.fetch(:max_cached_map_per_serializer_count)
63
+ end
64
+
65
+ def max_cached_map_per_serializer_count=(value)
66
+ raise SeregaError, "Must have Integer value, #{value.inspect} provided" unless value.is_a?(Integer)
67
+ opts[:max_cached_map_per_serializer_count] = value
68
+ end
69
+
70
+ def to_json
71
+ opts.fetch(:to_json)
72
+ end
73
+
74
+ def to_json=(value)
75
+ opts[:to_json] = value
76
+ end
77
+
78
+ def from_json
79
+ opts.fetch(:from_json)
80
+ end
81
+
82
+ def from_json=(value)
83
+ opts[:from_json] = value
84
+ end
43
85
  end
44
86
 
45
87
  include SeregaConfigInstanceMethods
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ # A generic exception Serega uses.
5
+ class SeregaError < StandardError; end
6
+
7
+ # AttributeNotExist is raised when serializer is initiated using not existing attribute
8
+ # Example:
9
+ # UserSerializer.new(only: 'FOO', except: 'FOO', with: 'FOO')
10
+ # UserSerializer.to_h(user, only: 'FOO', except: 'FOO', with: 'FOO' )
11
+ class AttributeNotExist < SeregaError; end
12
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaJSON
5
+ def self.adapter
6
+ @adapter ||=
7
+ if defined?(::Oj)
8
+ require_relative "oj"
9
+ :oj
10
+ else
11
+ require "json"
12
+ require_relative "json"
13
+ :json
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaJSON
5
+ class JSONDump
6
+ def self.call(data)
7
+ ::JSON.dump(data)
8
+ end
9
+ end
10
+
11
+ class JSONLoad
12
+ def self.call(json_string)
13
+ ::JSON.parse(json_string)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaJSON
5
+ class OjDump
6
+ def self.call(data)
7
+ ::Oj.dump(data, mode: :compat)
8
+ end
9
+ end
10
+
11
+ class OjLoad
12
+ def self.call(json_string)
13
+ ::Oj.load(json_string)
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/serega/map.rb CHANGED
@@ -4,24 +4,34 @@ class Serega
4
4
  class SeregaMap
5
5
  module ClassMethods
6
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
- }
7
+ max_cache_size = serializer_class.config.max_cached_map_per_serializer_count
8
+ return map_for(opts) if max_cache_size.zero?
16
9
 
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
10
+ cached_map_for(opts, max_cache_size)
21
11
  end
22
12
 
23
13
  private
24
14
 
15
+ def map_for(opts)
16
+ construct_map(serializer_class, **modifiers(opts))
17
+ end
18
+
19
+ def cached_map_for(opts, max_cache_size)
20
+ @cache ||= {}
21
+ cache_key = opts.to_s
22
+ map = @cache[cache_key] ||= map_for(opts)
23
+ @cache.shift if @cache.length > max_cache_size
24
+ map
25
+ end
26
+
27
+ def modifiers(opts)
28
+ {
29
+ only: opts[:only] || FROZEN_EMPTY_HASH,
30
+ except: opts[:except] || FROZEN_EMPTY_HASH,
31
+ with: opts[:with] || FROZEN_EMPTY_HASH
32
+ }
33
+ end
34
+
25
35
  def construct_map(serializer_class, only:, except:, with:)
26
36
  serializer_class.attributes.each_with_object([]) do |(name, attribute), map|
27
37
  next unless attribute.visible?(only: only, except: except, with: with)
@@ -14,23 +14,46 @@ class Serega
14
14
  end
15
15
 
16
16
  def self.load_plugin(serializer_class, **_opts)
17
+ serializer_class::SeregaConfig.include(ConfigInstanceMethods)
17
18
  serializer_class::SeregaConvert.include(SeregaConvertInstanceMethods)
18
- serializer_class::CheckSerializeParams.extend(CheckSerializeParamsClassMethods)
19
+ serializer_class::CheckSerializeParams.include(CheckSerializeParamsInstanceMethods)
19
20
  end
20
21
 
21
22
  def self.after_load_plugin(serializer_class, **opts)
22
23
  config = serializer_class.config
23
24
  meta_key = opts[:context_metadata_key] || DEFAULT_CONTEXT_METADATA_KEY
24
- config[plugin_name] = {key: meta_key}
25
- config[:serialize_keys] << meta_key
25
+ config.opts[:context_metadata] = {key: meta_key}
26
+ config.serialize_keys << meta_key
26
27
  end
27
28
 
28
- module CheckSerializeParamsClassMethods
29
- def check_opts(opts)
29
+ class ContextMetadataConfig
30
+ attr_reader :opts
31
+
32
+ def initialize(opts)
33
+ @opts = opts
34
+ end
35
+
36
+ def key
37
+ opts.fetch(:key)
38
+ end
39
+
40
+ def key=(value)
41
+ opts[:key] = value
42
+ end
43
+ end
44
+
45
+ module ConfigInstanceMethods
46
+ def context_metadata
47
+ ContextMetadataConfig.new(opts.fetch(:context_metadata))
48
+ end
49
+ end
50
+
51
+ module CheckSerializeParamsInstanceMethods
52
+ def check_opts
30
53
  super
31
54
 
32
- meta_key = serializer_class.config[:context_metadata][:key]
33
- SeregaValidations::SeregaUtils::CheckOptIsHash.call(opts, meta_key)
55
+ meta_key = self.class.serializer_class.config.context_metadata.key
56
+ SeregaValidations::Utils::CheckOptIsHash.call(opts, meta_key)
34
57
  end
35
58
  end
36
59
 
@@ -44,7 +67,7 @@ class Serega
44
67
  private
45
68
 
46
69
  def add_context_metadata(hash)
47
- context_metadata_key = self.class.serializer_class.config[:context_metadata][:key]
70
+ context_metadata_key = self.class.serializer_class.config.context_metadata.key
48
71
  return unless context_metadata_key
49
72
 
50
73
  metadata = opts[context_metadata_key]
@@ -8,13 +8,35 @@ class Serega
8
8
  end
9
9
 
10
10
  def self.load_plugin(serializer_class, **_opts)
11
+ serializer_class::SeregaConfig.include(ConfigInstanceMethods)
11
12
  serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
12
13
  end
13
14
 
14
- def self.after_load_plugin(serializer_class, **_opts)
15
+ def self.after_load_plugin(serializer_class, **opts)
15
16
  config = serializer_class.config
16
- config[plugin_name] = {}
17
- config[:attribute_keys] << :format
17
+ config.opts[:formatters] = {}
18
+ config.formatters.add(opts[:formatters] || {})
19
+ config.attribute_keys << :format
20
+ end
21
+
22
+ class FormattersConfig
23
+ attr_reader :opts
24
+
25
+ def initialize(opts)
26
+ @opts = opts
27
+ end
28
+
29
+ def add(formatters)
30
+ formatters.each_pair do |key, value|
31
+ opts[key] = value
32
+ end
33
+ end
34
+ end
35
+
36
+ module ConfigInstanceMethods
37
+ def formatters
38
+ FormattersConfig.new(opts.fetch(:formatters))
39
+ end
18
40
  end
19
41
 
20
42
  module AttributeInstanceMethods
@@ -43,7 +65,7 @@ class Serega
43
65
  value = original_block.call(object, context)
44
66
 
45
67
  if formatter.is_a?(Symbol)
46
- self.class.serializer_class.config.fetch(:formatters).fetch(formatter).call(value)
68
+ self.class.serializer_class.config.formatters.opts.fetch(formatter).call(value)
47
69
  else
48
70
  formatter.call(value)
49
71
  end
@@ -27,7 +27,7 @@ class Serega
27
27
  end
28
28
 
29
29
  def self.after_load_plugin(serializer_class, **opts)
30
- serializer_class.config[:attribute_keys] << :hide_nil
30
+ serializer_class.config.attribute_keys << :hide_nil
31
31
  end
32
32
 
33
33
  # Adds #hide_nil? Attribute instance method
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module Lazy
6
+ def self.plugin_name
7
+ :lazy
8
+ end
9
+
10
+ def self.before_load_plugin(serializer_class, **opts)
11
+ end
12
+
13
+ def self.load_plugin(serializer_class, **_opts)
14
+ serializer_class.extend(ClassMethods)
15
+ serializer_class.include(InstanceMethods)
16
+ end
17
+
18
+ def self.after_load_plugin(serializer_class, **_opts)
19
+ serializer_class.config.attribute_keys << :lazy
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def lazy(key:, buffer:, resolver:)
25
+ @lazy[key] = { buffer: buffer, resolver: resolver }
26
+ end
27
+ end
28
+
29
+ module InstanceMethods
30
+ def initialize(*args, **kwargs)
31
+ super
32
+ @lazy = {}
33
+ end
34
+
35
+ def to_h(*args, **kwargs)
36
+ result = super
37
+ lazy.each_key do |key, |
38
+ buffer = lazy.delete(key) # { val => [paths], val2 => [paths2] }
39
+ buffer_values = self.class.lazy[key][:resolver].(buffer.keys)
40
+
41
+ buffer_values.each do |key, resolved_value|
42
+ paths = buffer[key]
43
+ paths.each do |path|
44
+ result.dig(path[0, -2])
45
+ replace(result, buffer_values)
46
+ end
47
+
48
+ end
49
+ end
50
+
51
+ register_plugin(Metadata.plugin_name, Metadata)
52
+ end
53
+ end
@@ -61,7 +61,7 @@ class Serega
61
61
 
62
62
  def check(path, opts, block)
63
63
  CheckPath.call(path)
64
- CheckOpts.call(opts, self.class.serializer_class.config[:metadata][:attribute_keys])
64
+ CheckOpts.call(opts, self.class.serializer_class.config.metadata.attribute_keys)
65
65
  CheckBlock.call(block)
66
66
  end
67
67
  end
@@ -8,11 +8,18 @@ class Serega
8
8
  end
9
9
 
10
10
  def self.before_load_plugin(serializer_class, **opts)
11
- serializer_class.plugin(:root, **opts) unless serializer_class.plugin_used?(:root)
11
+ if serializer_class.plugin_used?(:root)
12
+ root = serializer_class.config.root
13
+ root.one = opts[:root_one] if opts.key?(:root_one)
14
+ root.many = opts[:root_many] if opts.key?(:root_many)
15
+ else
16
+ serializer_class.plugin(:root, **opts)
17
+ end
12
18
  end
13
19
 
14
20
  def self.load_plugin(serializer_class, **_opts)
15
21
  serializer_class.extend(ClassMethods)
22
+ serializer_class::SeregaConfig.include(ConfigInstanceMethods)
16
23
  serializer_class::SeregaConvert.include(SeregaConvertInstanceMethods)
17
24
 
18
25
  require_relative "./meta_attribute"
@@ -28,7 +35,25 @@ class Serega
28
35
  end
29
36
 
30
37
  def self.after_load_plugin(serializer_class, **_opts)
31
- serializer_class.config[plugin_name] = {attribute_keys: %i[path hide_nil hide_empty]}
38
+ serializer_class.config.opts[:metadata] = {attribute_keys: %i[path hide_nil hide_empty]}
39
+ end
40
+
41
+ class MetadataConfig
42
+ attr_reader :opts
43
+
44
+ def initialize(opts)
45
+ @opts = opts
46
+ end
47
+
48
+ def attribute_keys
49
+ opts.fetch(:attribute_keys)
50
+ end
51
+ end
52
+
53
+ module ConfigInstanceMethods
54
+ def metadata
55
+ MetadataConfig.new(opts.fetch(:metadata))
56
+ end
32
57
  end
33
58
 
34
59
  module ClassMethods
@@ -6,6 +6,12 @@ class Serega
6
6
  # Plugin adds `.preloads` method to find relations that must be preloaded
7
7
  #
8
8
  module Preloads
9
+ DEFAULT_CONFIG = {
10
+ auto_preload_attributes_with_delegate: false,
11
+ auto_preload_attributes_with_serializer: false,
12
+ auto_hide_attributes_with_preload: false
13
+ }.freeze
14
+
9
15
  # @return [Symbol] plugin name
10
16
  def self.plugin_name
11
17
  :preloads
@@ -22,6 +28,7 @@ class Serega
22
28
  def self.load_plugin(serializer_class, **_opts)
23
29
  serializer_class.include(InstanceMethods)
24
30
  serializer_class::SeregaAttribute.include(AttributeMethods)
31
+ serializer_class::SeregaConfig.include(ConfigInstanceMethods)
25
32
 
26
33
  serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
27
34
 
@@ -35,12 +42,14 @@ class Serega
35
42
 
36
43
  def self.after_load_plugin(serializer_class, **opts)
37
44
  config = serializer_class.config
38
- config[:attribute_keys] += [:preload, :preload_path]
39
- config[:preloads] = {
40
- auto_preload_attributes_with_delegate: opts.fetch(:auto_preload_attributes_with_delegate, false),
41
- auto_preload_attributes_with_serializer: opts.fetch(:auto_preload_attributes_with_serializer, false),
42
- auto_hide_attributes_with_preload: opts.fetch(:auto_hide_attributes_with_preload, false)
43
- }
45
+ config.attribute_keys << :preload << :preload_path
46
+
47
+ preloads_opts = DEFAULT_CONFIG.merge(opts.slice(*DEFAULT_CONFIG.keys))
48
+ config.opts[:preloads] = {}
49
+ preloads_config = config.preloads
50
+ preloads_config.auto_preload_attributes_with_delegate = preloads_opts[:auto_preload_attributes_with_delegate]
51
+ preloads_config.auto_preload_attributes_with_serializer = preloads_opts[:auto_preload_attributes_with_serializer]
52
+ preloads_config.auto_hide_attributes_with_preload = preloads_opts[:auto_hide_attributes_with_preload]
44
53
  end
45
54
 
46
55
  # Adds #preloads instance method
@@ -51,6 +60,35 @@ class Serega
51
60
  end
52
61
  end
53
62
 
63
+ class PreloadsConfig
64
+ attr_reader :opts
65
+
66
+ def initialize(opts)
67
+ @opts = opts
68
+ end
69
+
70
+ %i[
71
+ auto_preload_attributes_with_delegate
72
+ auto_preload_attributes_with_serializer
73
+ auto_hide_attributes_with_preload
74
+ ].each do |method_name|
75
+ define_method(method_name) do
76
+ opts.fetch(method_name)
77
+ end
78
+
79
+ define_method("#{method_name}=") do |value|
80
+ raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
81
+ opts[method_name] = value
82
+ end
83
+ end
84
+ end
85
+
86
+ module ConfigInstanceMethods
87
+ def preloads
88
+ PreloadsConfig.new(opts.fetch(:preloads))
89
+ end
90
+ end
91
+
54
92
  # Adds #preloads and #preloads_path Attribute instance method
55
93
  module AttributeMethods
56
94
  def preloads
@@ -75,9 +113,7 @@ class Serega
75
113
  private
76
114
 
77
115
  def auto_hide_attribute_with_preloads?
78
- return @auto_hide_attribute_with_preloads if defined?(@auto_hide_attribute_with_preloads)
79
-
80
- auto = self.class.serializer_class.config[:preloads][:auto_hide_attributes_with_preload]
116
+ auto = self.class.serializer_class.config.preloads.auto_hide_attributes_with_preload
81
117
  @auto_hide_attribute_with_preloads = auto && !preloads.nil? && (preloads != false) && (preloads != {})
82
118
  end
83
119
 
@@ -86,9 +122,9 @@ class Serega
86
122
  preloads =
87
123
  if preloads_provided
88
124
  opts[:preload]
89
- elsif relation? && self.class.serializer_class.config[:preloads][:auto_preload_attributes_with_serializer]
125
+ elsif relation? && self.class.serializer_class.config.preloads.auto_preload_attributes_with_serializer
90
126
  key
91
- elsif opts.key?(:delegate) && self.class.serializer_class.config[:preloads][:auto_preload_attributes_with_delegate]
127
+ elsif opts.key?(:delegate) && self.class.serializer_class.config.preloads.auto_preload_attributes_with_delegate
92
128
  opts[:delegate].fetch(:to)
93
129
  end
94
130
 
@@ -12,12 +12,17 @@ class Serega
12
12
 
13
13
  def self.load_plugin(serializer_class, **_opts)
14
14
  serializer_class.extend(ClassMethods)
15
+ serializer_class::SeregaConfig.include(SeregaConfigInstanceMethods)
15
16
  serializer_class::SeregaConvert.include(SeregaConvertInstanceMethods)
16
17
  end
17
18
 
18
19
  def self.after_load_plugin(serializer_class, **opts)
19
- serializer_class.root(opts[:root] || ROOT_DEFAULT, one: opts[:root_one], many: opts[:root_many])
20
- serializer_class.config[:serialize_keys] << :root
20
+ config = serializer_class.config
21
+ default = opts[:root] || ROOT_DEFAULT
22
+ one = (opts[:root_one] || default).to_sym
23
+ many = (opts[:root_many] || default).to_sym
24
+ config.opts[:root] = {one: one, many: many}
25
+ config.serialize_keys << :root
21
26
  end
22
27
 
23
28
  module ClassMethods
@@ -37,7 +42,42 @@ class Serega
37
42
  one = one.to_sym if one
38
43
  many = many.to_sym if many
39
44
 
40
- config[:root] = {one: one, many: many}
45
+ config.root = {one: one, many: many}
46
+ end
47
+ end
48
+
49
+ class RootConfig
50
+ attr_reader :opts
51
+
52
+ def initialize(opts)
53
+ @opts = opts
54
+ end
55
+
56
+ def one
57
+ opts.fetch(:one)
58
+ end
59
+
60
+ def many
61
+ opts.fetch(:many)
62
+ end
63
+
64
+ def one=(value)
65
+ opts[:one] = value
66
+ end
67
+
68
+ def many=(value)
69
+ opts[:many] = value
70
+ end
71
+ end
72
+
73
+ module SeregaConfigInstanceMethods
74
+ def root
75
+ RootConfig.new(opts.fetch(:root))
76
+ end
77
+
78
+ def root=(value)
79
+ root.one = value.fetch(:one)
80
+ root.many = value.fetch(:many)
41
81
  end
42
82
  end
43
83
 
@@ -54,8 +94,8 @@ class Serega
54
94
  def build_root(opts)
55
95
  return opts[:root] if opts.key?(:root)
56
96
 
57
- root_config = self.class.serializer_class.config[:root]
58
- many? ? root_config[:many] : root_config[:one]
97
+ root = self.class.serializer_class.config.root
98
+ many? ? root.many : root.one
59
99
  end
60
100
  end
61
101
  end
@@ -16,13 +16,13 @@ class Serega
16
16
  private
17
17
 
18
18
  def prepare_modifiers(opts)
19
- opts = {
20
- only: ParseStringModifiers.call(opts[:only]),
21
- except: ParseStringModifiers.call(opts[:except]),
22
- with: ParseStringModifiers.call(opts[:with])
23
- }
19
+ parsed_opts =
20
+ opts.each_with_object({}) do |(key, value), obj|
21
+ value = ParseStringModifiers.call(value) if (key == :only) || (key == :except) || (key == :with)
22
+ obj[key] = value
23
+ end
24
24
 
25
- super
25
+ super(parsed_opts)
26
26
  end
27
27
  end
28
28
  end
@@ -25,7 +25,7 @@ class Serega
25
25
  private
26
26
 
27
27
  def check_opt_delegate(opts)
28
- SeregaUtils::CheckOptIsHash.call(opts, :delegate)
28
+ Utils::CheckOptIsHash.call(opts, :delegate)
29
29
 
30
30
  delegate_opts = opts[:delegate]
31
31
  check_opt_delegate_to(delegate_opts)
@@ -36,13 +36,13 @@ class Serega
36
36
  to_exist = delegate_opts.key?(:to)
37
37
  raise SeregaError, "Option :delegate must have a :to option" unless to_exist
38
38
 
39
- SeregaUtils::CheckOptIsStringOrSymbol.call(delegate_opts, :to)
39
+ Utils::CheckOptIsStringOrSymbol.call(delegate_opts, :to)
40
40
  end
41
41
 
42
42
  def check_opt_delegate_allow_nil(delegate_opts)
43
43
  return unless delegate_opts.key?(:allow_nil)
44
44
 
45
- SeregaUtils::CheckOptIsBool.call(delegate_opts, :allow_nil)
45
+ Utils::CheckOptIsBool.call(delegate_opts, :allow_nil)
46
46
  end
47
47
 
48
48
  def check_usage_with_other_params(opts, block)
@@ -14,7 +14,7 @@ class Serega
14
14
  # @return [void]
15
15
  #
16
16
  def self.call(opts)
17
- SeregaUtils::CheckOptIsBool.call(opts, :hide)
17
+ Utils::CheckOptIsBool.call(opts, :hide)
18
18
  end
19
19
  end
20
20
  end
@@ -18,7 +18,7 @@ class Serega
18
18
  return unless opts.key?(:key)
19
19
 
20
20
  check_usage_with_other_params(opts, block)
21
- SeregaUtils::CheckOptIsStringOrSymbol.call(opts, :key)
21
+ Utils::CheckOptIsStringOrSymbol.call(opts, :key)
22
22
  end
23
23
 
24
24
  private
@@ -14,7 +14,7 @@ class Serega
14
14
  # @return [void]
15
15
  #
16
16
  def self.call(opts)
17
- SeregaUtils::CheckOptIsBool.call(opts, :many)
17
+ Utils::CheckOptIsBool.call(opts, :many)
18
18
  end
19
19
  end
20
20
  end
@@ -25,7 +25,7 @@ class Serega
25
25
  end
26
26
 
27
27
  def check_opts
28
- SeregaUtils::CheckAllowedKeys.call(opts, allowed_opts_keys)
28
+ Utils::CheckAllowedKeys.call(opts, allowed_opts_keys)
29
29
 
30
30
  Attribute::CheckOptConst.call(opts, block)
31
31
  Attribute::CheckOptDelegate.call(opts, block)
@@ -41,7 +41,7 @@ class Serega
41
41
  end
42
42
 
43
43
  def allowed_opts_keys
44
- self.class.serializer_class.config[:attribute_keys]
44
+ self.class.serializer_class.config.attribute_keys
45
45
  end
46
46
  end
47
47
 
@@ -3,23 +3,36 @@
3
3
  class Serega
4
4
  module SeregaValidations
5
5
  class CheckInitiateParams
6
- module ClassMethods
7
- def call(opts)
8
- check_opts(opts)
6
+ module InstanceMethods
7
+ attr_reader :opts
8
+
9
+ def initialize(opts)
10
+ @opts = opts
11
+ end
12
+
13
+ def validate
14
+ check_allowed_keys
15
+ check_modifiers
9
16
  end
10
17
 
11
18
  private
12
19
 
13
- def check_opts(opts)
14
- SeregaUtils::CheckAllowedKeys.call(opts, allowed_opts_keys)
20
+ def check_allowed_keys
21
+ Utils::CheckAllowedKeys.call(opts, serializer_class.config.initiate_keys)
22
+ end
23
+
24
+ def check_modifiers
25
+ Initiate::CheckModifiers.call(serializer_class, opts[:only])
26
+ Initiate::CheckModifiers.call(serializer_class, opts[:except])
27
+ Initiate::CheckModifiers.call(serializer_class, opts[:with])
15
28
  end
16
29
 
17
- def allowed_opts_keys
18
- serializer_class.config[:initiate_keys]
30
+ def serializer_class
31
+ self.class.serializer_class
19
32
  end
20
33
  end
21
34
 
22
- extend ClassMethods
35
+ include InstanceMethods
23
36
  extend Serega::SeregaHelpers::SerializerClassHelper
24
37
  end
25
38
  end
@@ -3,26 +3,32 @@
3
3
  class Serega
4
4
  module SeregaValidations
5
5
  class CheckSerializeParams
6
- module ClassMethods
7
- def call(opts)
8
- check_opts(opts)
6
+ module InstanceMethods
7
+ attr_reader :opts
8
+
9
+ def initialize(opts)
10
+ @opts = opts
11
+ end
12
+
13
+ def validate
14
+ check_opts
9
15
  end
10
16
 
11
17
  private
12
18
 
13
- def check_opts(opts)
14
- SeregaUtils::CheckAllowedKeys.call(opts, allowed_opts_keys)
19
+ def check_opts
20
+ Utils::CheckAllowedKeys.call(opts, serializer_class.config.serialize_keys)
15
21
 
16
- SeregaUtils::CheckOptIsHash.call(opts, :context)
17
- SeregaUtils::CheckOptIsBool.call(opts, :many)
22
+ Utils::CheckOptIsHash.call(opts, :context)
23
+ Utils::CheckOptIsBool.call(opts, :many)
18
24
  end
19
25
 
20
- def allowed_opts_keys
21
- serializer_class.config[:serialize_keys]
26
+ def serializer_class
27
+ self.class.serializer_class
22
28
  end
23
29
  end
24
30
 
25
- extend ClassMethods
31
+ include InstanceMethods
26
32
  extend Serega::SeregaHelpers::SerializerClassHelper
27
33
  end
28
34
  end
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
- class AttributeNotExist < SeregaError
5
- end
6
-
7
- module SeregaPlugins
8
- module ValidateModifiers
9
- class Validate
4
+ module SeregaValidations
5
+ module Initiate
6
+ class CheckModifiers
10
7
  class << self
11
8
  def call(serializer_class, fields)
12
9
  return unless fields
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Serega
4
4
  module SeregaValidations
5
- module SeregaUtils
5
+ module Utils
6
6
  class CheckAllowedKeys
7
7
  def self.call(opts, allowed_keys)
8
8
  opts.each_key do |key|
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Serega
4
4
  module SeregaValidations
5
- module SeregaUtils
5
+ module Utils
6
6
  class CheckOptIsBool
7
7
  def self.call(opts, key)
8
8
  return unless opts.key?(key)
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Serega
4
4
  module SeregaValidations
5
- module SeregaUtils
5
+ module Utils
6
6
  class CheckOptIsHash
7
7
  def self.call(opts, key)
8
8
  return unless opts.key?(key)
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Serega
4
4
  module SeregaValidations
5
- module SeregaUtils
5
+ module Utils
6
6
  class CheckOptIsStringOrSymbol
7
7
  def self.call(opts, key)
8
8
  return unless opts.key?(key)
data/lib/serega.rb CHANGED
@@ -4,9 +4,6 @@ require_relative "serega/version"
4
4
 
5
5
  # Parent class for your serializers
6
6
  class Serega
7
- # A generic exception Serega uses.
8
- class SeregaError < StandardError; end
9
-
10
7
  # @return [Hash] frozen hash
11
8
  FROZEN_EMPTY_HASH = {}.freeze
12
9
 
@@ -14,11 +11,11 @@ class Serega
14
11
  FROZEN_EMPTY_ARRAY = [].freeze
15
12
  end
16
13
 
14
+ require_relative "serega/errors"
17
15
  require_relative "serega/helpers/serializer_class_helper"
18
16
  require_relative "serega/utils/enum_deep_dup"
19
17
  require_relative "serega/utils/to_hash"
20
- require_relative "serega/utils/to_json"
21
- require_relative "serega/utils/as_json"
18
+ require_relative "serega/json/adapter"
22
19
 
23
20
  require_relative "serega/attribute"
24
21
  require_relative "serega/validations/utils/check_allowed_keys"
@@ -34,6 +31,7 @@ require_relative "serega/validations/attribute/check_opt_key"
34
31
  require_relative "serega/validations/attribute/check_opt_many"
35
32
  require_relative "serega/validations/attribute/check_opt_serializer"
36
33
  require_relative "serega/validations/attribute/check_opt_value"
34
+ require_relative "serega/validations/initiate/check_modifiers"
37
35
  require_relative "serega/validations/check_attribute_params"
38
36
  require_relative "serega/validations/check_initiate_params"
39
37
  require_relative "serega/validations/check_serialize_params"
@@ -45,25 +43,19 @@ require_relative "serega/map"
45
43
  require_relative "serega/plugins"
46
44
 
47
45
  class Serega
48
- @config = SeregaConfig.new(
49
- {
50
- plugins: [],
51
- initiate_keys: %i[only with except],
52
- attribute_keys: %i[key value serializer many hide const delegate],
53
- serialize_keys: %i[context many],
54
- max_cached_map_per_serializer_count: 50,
55
- to_json: ->(data) { SeregaUtils::ToJSON.call(data) }
56
- }
57
- )
46
+ @config = SeregaConfig.new
58
47
 
48
+ # Validates `Serializer.attribute` params
59
49
  check_attribute_params_class = Class.new(SeregaValidations::CheckAttributeParams)
60
50
  check_attribute_params_class.serializer_class = self
61
51
  const_set(:CheckAttributeParams, check_attribute_params_class)
62
52
 
53
+ # Validates `Serializer#new` params
63
54
  check_initiate_params_class = Class.new(SeregaValidations::CheckInitiateParams)
64
55
  check_initiate_params_class.serializer_class = self
65
56
  const_set(:CheckInitiateParams, check_initiate_params_class)
66
57
 
58
+ # Validates `serializer#call(obj, PARAMS)` params
67
59
  check_serialize_params_class = Class.new(SeregaValidations::CheckSerializeParams)
68
60
  check_serialize_params_class.serializer_class = self
69
61
  const_set(:CheckSerializeParams, check_serialize_params_class)
@@ -138,7 +130,7 @@ class Serega
138
130
  plugin.after_load_plugin(self, **opts) if plugin.respond_to?(:after_load_plugin)
139
131
 
140
132
  # Store attached plugins, so we can check it is loaded later
141
- config[:plugins] << (plugin.respond_to?(:plugin_name) ? plugin.plugin_name : plugin)
133
+ config.plugins << (plugin.respond_to?(:plugin_name) ? plugin.plugin_name : plugin)
142
134
 
143
135
  plugin
144
136
  end
@@ -157,7 +149,7 @@ class Serega
157
149
  else name
158
150
  end
159
151
 
160
- config[:plugins].include?(plugin_name)
152
+ config.plugins.include?(plugin_name)
161
153
  end
162
154
 
163
155
  #
@@ -184,7 +176,7 @@ class Serega
184
176
  end
185
177
 
186
178
  def call(object, opts = FROZEN_EMPTY_HASH)
187
- initiate_keys = config[:initiate_keys]
179
+ initiate_keys = config.initiate_keys
188
180
  new(opts.slice(*initiate_keys)).to_h(object, opts.except(*initiate_keys))
189
181
  end
190
182
 
@@ -193,13 +185,12 @@ class Serega
193
185
  end
194
186
 
195
187
  def to_json(object, opts = FROZEN_EMPTY_HASH)
196
- initiate_keys = config[:initiate_keys]
188
+ initiate_keys = config.initiate_keys
197
189
  new(opts.slice(*initiate_keys)).to_json(object, opts.except(*initiate_keys))
198
190
  end
199
191
 
200
192
  def as_json(object, opts = FROZEN_EMPTY_HASH)
201
- initiate_keys = config[:initiate_keys]
202
- new(opts.slice(*initiate_keys)).as_json(object, opts.except(*initiate_keys))
193
+ config.from_json.call(to_json(object, opts))
203
194
  end
204
195
  end
205
196
 
@@ -217,9 +208,8 @@ class Serega
217
208
  # @param with [Array, Hash, String, Symbol] Attributes (usually hidden) to serialize additionally
218
209
  #
219
210
  def initialize(opts = FROZEN_EMPTY_HASH)
220
- self.class::CheckInitiateParams.call(opts)
221
- opts = prepare_modifiers(opts) if opts && (opts != FROZEN_EMPTY_HASH)
222
- @opts = opts
211
+ @opts = opts == FROZEN_EMPTY_HASH ? opts : prepare_modifiers(opts)
212
+ self.class::CheckInitiateParams.new(@opts).validate if opts.fetch(:check_initiate_params) { config.check_initiate_params }
223
213
  end
224
214
 
225
215
  #
@@ -231,7 +221,7 @@ class Serega
231
221
  # @return [Hash] Serialization result
232
222
  #
233
223
  def call(object, opts = {})
234
- self.class::CheckSerializeParams.call(opts)
224
+ self.class::CheckSerializeParams.new(opts).validate
235
225
  opts[:context] ||= {}
236
226
 
237
227
  self.class::SeregaConvert.call(object, **opts, map: map)
@@ -249,37 +239,40 @@ class Serega
249
239
  #
250
240
  # @return [Hash] Serialization result
251
241
  #
252
- def to_json(object, opts = FROZEN_EMPTY_HASH)
242
+ def to_json(object, opts = {})
253
243
  hash = to_h(object, opts)
254
- self.class.config[:to_json].call(hash)
244
+ config.to_json.call(hash)
255
245
  end
256
246
 
257
247
  #
258
248
  # Serializes provided object as json (uses only JSON-compatible types)
259
- # When you later serialize/deserialize it from JSON you should receive
249
+ # When you later serialize/de-serialize it from JSON you should receive
260
250
  # equal object
261
251
  #
262
252
  # @param object [Object] Serialized object
263
253
  #
264
254
  # @return [Hash] Serialization result
265
255
  #
266
- def as_json(object, opts = FROZEN_EMPTY_HASH)
267
- hash = to_h(object, opts)
268
- SeregaUtils::AsJSON.call(hash, to_json: self.class.config[:to_json])
256
+ def as_json(object, opts = {})
257
+ json = to_json(object, opts)
258
+ config.from_json.call(json)
269
259
  end
270
260
 
271
261
  private
272
262
 
263
+ def config
264
+ self.class.config
265
+ end
266
+
273
267
  def map
274
268
  @map ||= self.class::SeregaMap.call(opts)
275
269
  end
276
270
 
277
271
  def prepare_modifiers(opts)
278
- {
279
- only: SeregaUtils::ToHash.call(opts[:only]),
280
- except: SeregaUtils::ToHash.call(opts[:except]),
281
- with: SeregaUtils::ToHash.call(opts[:with])
282
- }
272
+ opts.each_with_object({}) do |(key, value), obj|
273
+ value = SeregaUtils::ToHash.call(value) if (key == :only) || (key == :except) || (key == :with)
274
+ obj[key] = value
275
+ end
283
276
  end
284
277
  end
285
278
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serega
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Glushkov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-01 00:00:00.000000000 Z
11
+ date: 2022-09-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -23,7 +23,11 @@ files:
23
23
  - lib/serega/config.rb
24
24
  - lib/serega/convert.rb
25
25
  - lib/serega/convert_item.rb
26
+ - lib/serega/errors.rb
26
27
  - lib/serega/helpers/serializer_class_helper.rb
28
+ - lib/serega/json/adapter.rb
29
+ - lib/serega/json/json.rb
30
+ - lib/serega/json/oj.rb
27
31
  - lib/serega/map.rb
28
32
  - lib/serega/plugins.rb
29
33
  - lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb
@@ -31,6 +35,7 @@ files:
31
35
  - lib/serega/plugins/context_metadata/context_metadata.rb
32
36
  - lib/serega/plugins/formatters/formatters.rb
33
37
  - lib/serega/plugins/hide_nil/hide_nil.rb
38
+ - lib/serega/plugins/lazy/lazy.rb
34
39
  - lib/serega/plugins/metadata/meta_attribute.rb
35
40
  - lib/serega/plugins/metadata/metadata.rb
36
41
  - lib/serega/plugins/metadata/validations/check_block.rb
@@ -49,12 +54,8 @@ files:
49
54
  - lib/serega/plugins/root/root.rb
50
55
  - lib/serega/plugins/string_modifiers/parse_string_modifiers.rb
51
56
  - lib/serega/plugins/string_modifiers/string_modifiers.rb
52
- - lib/serega/plugins/validate_modifiers/validate.rb
53
- - lib/serega/plugins/validate_modifiers/validate_modifiers.rb
54
- - lib/serega/utils/as_json.rb
55
57
  - lib/serega/utils/enum_deep_dup.rb
56
58
  - lib/serega/utils/to_hash.rb
57
- - lib/serega/utils/to_json.rb
58
59
  - lib/serega/validations/attribute/check_block.rb
59
60
  - lib/serega/validations/attribute/check_name.rb
60
61
  - lib/serega/validations/attribute/check_opt_const.rb
@@ -67,6 +68,7 @@ files:
67
68
  - lib/serega/validations/check_attribute_params.rb
68
69
  - lib/serega/validations/check_initiate_params.rb
69
70
  - lib/serega/validations/check_serialize_params.rb
71
+ - lib/serega/validations/initiate/check_modifiers.rb
70
72
  - lib/serega/validations/utils/check_allowed_keys.rb
71
73
  - lib/serega/validations/utils/check_opt_is_bool.rb
72
74
  - lib/serega/validations/utils/check_opt_is_hash.rb
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaPlugins
5
- module ValidateModifiers
6
- def self.plugin_name
7
- :validate_modifiers
8
- end
9
-
10
- def self.load_plugin(serializer_class, **_opts)
11
- serializer_class.include(InstanceMethods)
12
- require_relative "./validate"
13
- end
14
-
15
- def self.after_load_plugin(serializer_class, **opts)
16
- serializer_class.config[:validate_modifiers] = {auto: opts.fetch(:auto, true)}
17
- end
18
-
19
- module InstanceMethods
20
- # Raises error if some modifiers are invalid
21
- def validate_modifiers
22
- @modifiers_validated ||= begin
23
- Validate.call(self.class, opts[:only])
24
- Validate.call(self.class, opts[:except])
25
- Validate.call(self.class, opts[:with])
26
- true
27
- end
28
- end
29
-
30
- private
31
-
32
- def initialize(opts)
33
- super
34
- validate_modifiers if self.class.config[:validate_modifiers][:auto]
35
- end
36
- end
37
-
38
- module InstanceMethods
39
- end
40
- end
41
-
42
- register_plugin(ValidateModifiers.plugin_name, ValidateModifiers)
43
- end
44
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaUtils
5
- class AsJSON
6
- DOUBLE_QUOTE = '"'
7
-
8
- class << self
9
- def call(data, to_json:)
10
- case data
11
- when Hash
12
- data.each_with_object({}) do |(key, value), new_data|
13
- new_key = key.to_s
14
- new_value = call(value, to_json: to_json)
15
- new_data[new_key] = new_value
16
- end
17
- when Array
18
- data.map { |value| call(value, to_json: to_json) }
19
- when NilClass, Integer, Float, String, TrueClass, FalseClass
20
- data
21
- when Symbol
22
- data.to_s
23
- else
24
- res = to_json.call(data)
25
- if res.start_with?(DOUBLE_QUOTE) && res.end_with?(DOUBLE_QUOTE)
26
- res.delete_prefix!(DOUBLE_QUOTE)
27
- res.delete_suffix!(DOUBLE_QUOTE)
28
- end
29
- res
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaUtils
5
- class ToJSON
6
- class << self
7
- def call(data)
8
- json_adapter.dump(data)
9
- end
10
-
11
- private
12
-
13
- def json_adapter
14
- @json_adapter ||= begin
15
- require "json"
16
- ::JSON
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end