serega 0.2.0 → 0.4.0

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