serega 0.6.1 → 0.7.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/serega/attribute.rb +2 -2
  4. data/lib/serega/config.rb +23 -1
  5. data/lib/serega/errors.rb +8 -5
  6. data/lib/serega/helpers/serializer_class_helper.rb +5 -0
  7. data/lib/serega/json/adapter.rb +6 -0
  8. data/lib/serega/json/json.rb +20 -0
  9. data/lib/serega/json/oj.rb +20 -0
  10. data/lib/serega/map.rb +17 -0
  11. data/lib/serega/map_point.rb +36 -1
  12. data/lib/serega/object_serializer.rb +17 -4
  13. data/lib/serega/plugins/activerecord_preloads/activerecord_preloads.rb +66 -18
  14. data/lib/serega/plugins/activerecord_preloads/lib/preloader.rb +100 -40
  15. data/lib/serega/plugins/batch/batch.rb +136 -10
  16. data/lib/serega/plugins/batch/lib/loader.rb +33 -1
  17. data/lib/serega/plugins/batch/lib/loaders.rb +15 -2
  18. data/lib/serega/plugins/batch/lib/plugins_extensions.rb +23 -1
  19. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_key.rb +12 -0
  20. data/lib/serega/plugins/batch/lib/validations/check_batch_opt_loader.rb +12 -0
  21. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +12 -0
  22. data/lib/serega/plugins/context_metadata/context_metadata.rb +95 -13
  23. data/lib/serega/plugins/formatters/formatters.rb +94 -8
  24. data/lib/serega/plugins/hide_nil/hide_nil.rb +33 -7
  25. data/lib/serega/plugins/metadata/metadata.rb +108 -35
  26. data/lib/serega/plugins/metadata/validations/check_block.rb +3 -0
  27. data/lib/serega/plugins/metadata/validations/check_opt_hide_empty.rb +4 -1
  28. data/lib/serega/plugins/metadata/validations/check_opt_hide_nil.rb +4 -1
  29. data/lib/serega/plugins/metadata/validations/check_opts.rb +3 -0
  30. data/lib/serega/plugins/metadata/validations/check_path.rb +3 -0
  31. data/lib/serega/plugins/preloads/lib/enum_deep_freeze.rb +10 -1
  32. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +13 -4
  33. data/lib/serega/plugins/preloads/lib/main_preload_path.rb +16 -3
  34. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +4 -6
  35. data/lib/serega/plugins/preloads/preloads.rb +145 -12
  36. data/lib/serega/plugins/preloads/validations/check_opt_preload.rb +11 -0
  37. data/lib/serega/plugins/preloads/validations/check_opt_preload_path.rb +12 -0
  38. data/lib/serega/plugins/presenter/presenter.rb +20 -11
  39. data/lib/serega/plugins/root/root.rb +131 -19
  40. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +42 -12
  41. data/lib/serega/plugins/string_modifiers/string_modifiers.rb +14 -0
  42. data/lib/serega/plugins.rb +7 -1
  43. data/lib/serega/utils/enum_deep_dup.rb +5 -0
  44. data/lib/serega/utils/to_hash.rb +21 -3
  45. data/lib/serega/validations/attribute/check_block.rb +7 -2
  46. data/lib/serega/validations/attribute/check_name.rb +6 -0
  47. data/lib/serega/validations/attribute/check_opt_const.rb +12 -9
  48. data/lib/serega/validations/attribute/check_opt_delegate.rb +13 -10
  49. data/lib/serega/validations/attribute/check_opt_hide.rb +3 -0
  50. data/lib/serega/validations/attribute/check_opt_key.rb +12 -9
  51. data/lib/serega/validations/attribute/check_opt_many.rb +3 -0
  52. data/lib/serega/validations/attribute/check_opt_serializer.rb +3 -0
  53. data/lib/serega/validations/attribute/check_opt_value.rb +12 -9
  54. data/lib/serega/validations/check_attribute_params.rb +29 -1
  55. data/lib/serega/validations/check_initiate_params.rb +17 -0
  56. data/lib/serega/validations/check_serialize_params.rb +16 -0
  57. data/lib/serega/validations/initiate/check_modifiers.rb +15 -0
  58. data/lib/serega/validations/utils/check_allowed_keys.rb +14 -0
  59. data/lib/serega/validations/utils/check_opt_is_bool.rb +11 -0
  60. data/lib/serega/validations/utils/check_opt_is_hash.rb +11 -0
  61. data/lib/serega/validations/utils/check_opt_is_string_or_symbol.rb +11 -0
  62. data/lib/serega/version.rb +4 -0
  63. data/lib/serega.rb +83 -24
  64. metadata +2 -3
  65. data/lib/serega/serializer.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23e6466f457dd12a35967fc62aa58e6322dcca3c4a6731f7b3cd7d750f1fec87
4
- data.tar.gz: 1a669d52eafa9703f5a787d5af8dfa439101f4f6024fbf70095b67c8edd06dcc
3
+ metadata.gz: '001918eece6824f604d5bdd2a43321346d474355fd629759eaa894dabf94d7f4'
4
+ data.tar.gz: e68ee37be935e8c4a9bc2eca849a6d56b68feb56155519a612976cddd2013871
5
5
  SHA512:
6
- metadata.gz: 68caa2feaa385cf5fb031867bbaed551c770a5883cea1c3494714425244726fc51cfa49e02bd573437fab1d9e3812527e98fec4df04bc5a92ddfdc819209ca5f
7
- data.tar.gz: 91854118f3058bdf899f2d6a8472b67bf0aefd60767c77f5954892c885d031bc9265423a240b9f19744679a74368bd0586cc30a4e83defa5a32a6e28fb2d9039
6
+ metadata.gz: 4683bc9c753fbbcfc40c31e7a2833bddfe3a01711067a718ad9a01f263d8e7e576b768432e33dcaad688ddd2d3017e60ebfc3111d22a661d6f670ae465f9210c
7
+ data.tar.gz: ad5fd5c647484d9b68053e28a0e8a728e285727b7419e797cf89f73ea00abaf226c6c59d137afb7ba41b47fc1490b4948e11128d059257ed431da0574657998e
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.1
1
+ 0.7.0
@@ -2,11 +2,11 @@
2
2
 
3
3
  class Serega
4
4
  #
5
- # Stores Attribute data
5
+ # Stores serialized attribute data
6
6
  #
7
7
  class SeregaAttribute
8
8
  #
9
- # Stores Attribute instance methods
9
+ # Attribute instance methods
10
10
  #
11
11
  module AttributeInstanceMethods
12
12
  # @return [Symbol] Attribute name
data/lib/serega/config.rb CHANGED
@@ -4,7 +4,7 @@ require "forwardable"
4
4
 
5
5
  class Serega
6
6
  #
7
- # Core class that stores serializer configuration
7
+ # Stores serialization config
8
8
  #
9
9
  class SeregaConfig
10
10
  # :nocov: We can't use both :oj and :json adapters together
@@ -20,7 +20,11 @@ class Serega
20
20
  }.freeze
21
21
  # :nocov:
22
22
 
23
+ # SeregaConfig Instance methods
23
24
  module SeregaConfigInstanceMethods
25
+ #
26
+ # @return [Hash] All config options
27
+ #
24
28
  attr_reader :opts
25
29
 
26
30
  #
@@ -33,52 +37,70 @@ class Serega
33
37
  @opts = SeregaUtils::EnumDeepDup.call(opts)
34
38
  end
35
39
 
40
+ # @return [Array] Used plugins
36
41
  def plugins
37
42
  opts.fetch(:plugins)
38
43
  end
39
44
 
45
+ # @return [Array<Symbol>] Allowed options keys for serializer initialization
40
46
  def initiate_keys
41
47
  opts.fetch(:initiate_keys)
42
48
  end
43
49
 
50
+ # @return [Array<Symbol>] Allowed options keys for attribute initialization
44
51
  def attribute_keys
45
52
  opts.fetch(:attribute_keys)
46
53
  end
47
54
 
55
+ # @return [Array<Symbol>] Allowed options keys for serialization
48
56
  def serialize_keys
49
57
  opts.fetch(:serialize_keys)
50
58
  end
51
59
 
60
+ # @return [Boolean] Current :check_initiate_params config option
52
61
  def check_initiate_params
53
62
  opts.fetch(:check_initiate_params)
54
63
  end
55
64
 
65
+ # @param value [Boolean] Set :check_initiate_params config option
66
+ #
67
+ # @return [Boolean] :check_initiate_params config option
56
68
  def check_initiate_params=(value)
57
69
  raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
58
70
  opts[:check_initiate_params] = value
59
71
  end
60
72
 
73
+ # @return [Boolean] Current :max_cached_map_per_serializer_count config option
61
74
  def max_cached_map_per_serializer_count
62
75
  opts.fetch(:max_cached_map_per_serializer_count)
63
76
  end
64
77
 
78
+ # @param value [Boolean] Set :check_initiate_params config option
79
+ #
80
+ # @return [Boolean] New :max_cached_map_per_serializer_count config option
65
81
  def max_cached_map_per_serializer_count=(value)
66
82
  raise SeregaError, "Must have Integer value, #{value.inspect} provided" unless value.is_a?(Integer)
67
83
  opts[:max_cached_map_per_serializer_count] = value
68
84
  end
69
85
 
86
+ # @return [#call] Callable that used to construct JSON
70
87
  def to_json
71
88
  opts.fetch(:to_json)
72
89
  end
73
90
 
91
+ # @param value [#call] Callable that used to construct JSON
92
+ # @return [#call] Provided callable object
74
93
  def to_json=(value)
75
94
  opts[:to_json] = value
76
95
  end
77
96
 
97
+ # @return [#call] Callable that used to parse JSON
78
98
  def from_json
79
99
  opts.fetch(:from_json)
80
100
  end
81
101
 
102
+ # @param value [#call] Callable that used to parse JSON
103
+ # @return [#call] Provided callable object
82
104
  def from_json=(value)
83
105
  opts[:from_json] = value
84
106
  end
data/lib/serega/errors.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
- # A generic exception Serega uses.
4
+ #
5
+ # Base exception class
6
+ #
5
7
  class SeregaError < StandardError; end
6
8
 
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' )
9
+ # Raised when serializer is initiated using not existing attribute
10
+ #
11
+ # @example
12
+ # Serega.new(only: 'FOO')
13
+ # # => Attribute 'FOO' not exists (Serega::AttributeNotExist)
11
14
  class AttributeNotExist < SeregaError; end
12
15
  end
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
+ #
5
+ # Helpers
6
+ #
4
7
  module SeregaHelpers
8
+ #
5
9
  # Stores link to current serializer class
10
+ #
6
11
  module SerializerClassHelper
7
12
  # @return [Class<Serega>] Serializer class that current class is namespaced under.
8
13
  attr_accessor :serializer_class
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
+ #
5
+ # JSON adapters
6
+ #
4
7
  module SeregaJSON
8
+ # Current JSON adapter
9
+ #
10
+ # @return [Symbol] Current JSON adapter name - :oj or :json
5
11
  def self.adapter
6
12
  @adapter ||=
7
13
  if defined?(::Oj)
@@ -2,13 +2,33 @@
2
2
 
3
3
  class Serega
4
4
  module SeregaJSON
5
+ #
6
+ # JSON dump adapter for ::JSON
7
+ #
5
8
  class JSONDump
9
+ #
10
+ # Dumps data to JSON string
11
+ #
12
+ # @param data [Object] Anything
13
+ #
14
+ # @return [String] Data serialized to JSON
15
+ #
6
16
  def self.call(data)
7
17
  ::JSON.dump(data)
8
18
  end
9
19
  end
10
20
 
21
+ #
22
+ # JSON parse adapter for ::JSON
23
+ #
11
24
  class JSONLoad
25
+ #
26
+ # Loads object from JSON string
27
+ #
28
+ # @param json_string [String] JSON String
29
+ #
30
+ # @return [Object] Deserialized data
31
+ #
12
32
  def self.call(json_string)
13
33
  ::JSON.parse(json_string)
14
34
  end
@@ -2,13 +2,33 @@
2
2
 
3
3
  class Serega
4
4
  module SeregaJSON
5
+ #
6
+ # JSON dump adapter for ::Oj
7
+ #
5
8
  class OjDump
9
+ #
10
+ # Dumps data to JSON string
11
+ #
12
+ # @param data [Object] Anything
13
+ #
14
+ # @return [String] Data serialized to JSON
15
+ #
6
16
  def self.call(data)
7
17
  ::Oj.dump(data, mode: :compat)
8
18
  end
9
19
  end
10
20
 
21
+ #
22
+ # JSON parse adapter for ::Oj
23
+ #
11
24
  class OjLoad
25
+ #
26
+ # Loads object from JSON string
27
+ #
28
+ # @param json_string [String] JSON String
29
+ #
30
+ # @return [Object] Deserialized data
31
+ #
12
32
  def self.call(json_string)
13
33
  ::Oj.load(json_string)
14
34
  end
data/lib/serega/map.rb CHANGED
@@ -1,8 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
+ #
5
+ # Constructs map of attributes that should be serialized.
6
+ # We will traverse this map to construct serialized response.
7
+ #
4
8
  class SeregaMap
9
+ #
10
+ # SeregaMap class methods
11
+ #
5
12
  module ClassMethods
13
+ #
14
+ # Constructs map of attributes that should be serialized.
15
+ #
16
+ # @param opts Serialization parameters
17
+ # @option opts [Hash] :only The only attributes to serialize
18
+ # @option opts [Hash] :except Attributes to hide
19
+ # @option opts [Hash] :with Attributes (usually hidden) to serialize additionally
20
+ #
21
+ # @return [Array<Serega::SeregaMapPoint>] map
22
+ #
6
23
  def call(opts)
7
24
  max_cache_size = serializer_class.config.max_cached_map_per_serializer_count
8
25
  return map_for(opts) if max_cache_size.zero?
@@ -1,23 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
+ #
5
+ # Combines attribute and nested attributes
6
+ #
4
7
  class SeregaMapPoint
8
+ #
9
+ # SeregaMapPoint instance methods
10
+ #
5
11
  module InstanceMethods
6
12
  extend Forwardable
7
13
 
8
- attr_reader :attribute, :nested_points
14
+ # @return [Serega::SeregaAttribute] Current attribute
15
+ attr_reader :attribute
9
16
 
17
+ # @return [NilClass, Array<Serega::SeregaMapPoint>] Nested points or nil
18
+ attr_reader :nested_points
19
+
20
+ # @!method name
21
+ # Attribute `name`
22
+ # @see Serega::SeregaAttribute::AttributeInstanceMethods#name
23
+ # @!method value
24
+ # Attribute `value` block
25
+ # @see Serega::SeregaAttribute::AttributeInstanceMethods#value
26
+ # @!method many
27
+ # Attribute `many` option
28
+ # @see Serega::SeregaAttribute::AttributeInstanceMethods#many
10
29
  def_delegators :@attribute, :name, :value, :many
11
30
 
31
+ #
32
+ # Initializes map point
33
+ #
34
+ # @param attribute [Serega::SeregaAttribute] Attribute to construct map point
35
+ # @param nested_points [NilClass, Array<Serega::SeregaMapPoint>] Nested map points for provided attribute
36
+ #
37
+ # @return [Serega::SeregaMapPoint] New map point
38
+ #
12
39
  def initialize(attribute, nested_points)
13
40
  @attribute = attribute
14
41
  @nested_points = nested_points
15
42
  end
16
43
 
44
+ #
45
+ # Checks if attribute has nested points (is a link to another serializer)
46
+ #
47
+ # @return [Boolean] whether attribute has nested points
48
+ #
17
49
  def has_nested_points?
18
50
  !nested_points.nil?
19
51
  end
20
52
 
53
+ #
54
+ # @return [Serega::SeregaObjectSerializer] object serializer for nested points
55
+ #
21
56
  def nested_object_serializer
22
57
  attribute.serializer::SeregaObjectSerializer
23
58
  end
@@ -1,13 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Serega
4
+ #
5
+ # Low-level class that is used by more high-level SeregaSerializer
6
+ # to construct serialized to hash response
7
+ #
4
8
  class SeregaObjectSerializer
5
- module SeregaObjectSerializerInstanceMethods
9
+ #
10
+ # SeregaObjectSerializer instance methods
11
+ #
12
+ module InstanceMethods
6
13
  attr_reader :context, :points, :many, :opts
7
14
 
8
15
  # @param context [Hash] Serialization context
9
16
  # @param many [TrueClass|FalseClass] is object is enumerable
10
17
  # @param points [Array<MapPoint>] Serialization points (attributes)
18
+ #
19
+ # @return [SeregaObjectSerializer] New SeregaObjectSerializer
11
20
  def initialize(context:, points:, many: nil, **opts)
12
21
  @context = context
13
22
  @points = points
@@ -15,8 +24,14 @@ class Serega
15
24
  @opts = opts
16
25
  end
17
26
 
27
+ # Serializes object(s)
28
+ #
18
29
  # @param object [Object] Serialized object
30
+ #
31
+ # @return [Hash, Array<Hash>] Serialized object(s)
19
32
  def serialize(object)
33
+ return if object.nil?
34
+
20
35
  array?(object, many) ? serialize_array(object) : serialize_object(object)
21
36
  end
22
37
 
@@ -27,8 +42,6 @@ class Serega
27
42
  end
28
43
 
29
44
  def serialize_object(object)
30
- return unless object
31
-
32
45
  points.each_with_object({}) do |point, container|
33
46
  attach_value(object, point, container)
34
47
  end
@@ -62,6 +75,6 @@ class Serega
62
75
  end
63
76
 
64
77
  extend Serega::SeregaHelpers::SerializerClassHelper
65
- include SeregaObjectSerializerInstanceMethods
78
+ include InstanceMethods
66
79
  end
67
80
  end
@@ -3,56 +3,104 @@
3
3
  class Serega
4
4
  module SeregaPlugins
5
5
  #
6
- # Plugin that checks used plugins and loads correct Preloader for selected response type
7
- # @see Serega::SeregaPlugins::JsonApiActiverecordPreloader
8
- # @see Serega::SeregaPlugins::SimpleApiActiverecordPreloader
6
+ # Plugin :activerecord_preloads
7
+ # (depends on :preloads plugin, that must be loaded first)
8
+ #
9
+ # Automatically preloads associations to serialized objects
10
+ #
11
+ # It takes all defined preloads from serialized attributes (including attributes from serialized relations),
12
+ # merges them into single associations hash and then uses ActiveRecord::Associations::Preloader
13
+ # to preload all associations.
14
+ #
15
+ # @example
16
+ # class AppSerializer < Serega
17
+ # plugin :preloads,
18
+ # auto_preload_attributes_with_delegate: true,
19
+ # auto_preload_attributes_with_serializer: true,
20
+ # auto_hide_attributes_with_preload: true
21
+ #
22
+ # plugin :activerecord_preloads
23
+ # end
24
+ #
25
+ # class UserSerializer < AppSerializer
26
+ # # no preloads
27
+ # attribute :username
28
+ #
29
+ # # preloads `:user_stats` as auto_preload_attributes_with_delegate option is true
30
+ # attribute :comments_count, delegate: { to: :user_stats }
31
+ #
32
+ # # preloads `:albums` as auto_preload_attributes_with_serializer option is true
33
+ # attribute :albums, serializer: AlbumSerializer, hide: false
34
+ # end
35
+ #
36
+ # class AlbumSerializer < AppSerializer
37
+ # # no preloads
38
+ # attribute :title
39
+ #
40
+ # # preloads :downloads_count as manually specified
41
+ # attribute :downloads_count, preload: :downloads, value: proc { |album| album.downloads.count }
42
+ # end
43
+ #
44
+ # UserSerializer.to_h(user) # => preloads {users_stats: {}, albums: { downloads: {} }}
9
45
  #
10
46
  module ActiverecordPreloads
11
- # @return [Symbol] plugin name
47
+ #
48
+ # @return [Symbol] Plugin name
49
+ #
12
50
  def self.plugin_name
13
51
  :activerecord_preloads
14
52
  end
15
53
 
16
- def self.before_load_plugin(serializer_class, **opts)
54
+ # Checks requirements to load plugin
55
+ #
56
+ # @param serializer_class [Class<Serega>] Current serializer class
57
+ # @param _opts [Hash] plugins options
58
+ #
59
+ # @return [void]
60
+ #
61
+ def self.before_load_plugin(serializer_class, **_opts)
62
+ unless serializer_class.plugin_used?(:preloads)
63
+ raise SeregaError, "Please load `plugin :preloads` first"
64
+ end
65
+
17
66
  if serializer_class.plugin_used?(:batch)
18
67
  raise SeregaError, "Plugin `activerecord_preloads` must be loaded before `batch`"
19
68
  end
20
-
21
- serializer_class.plugin(:preloads, **opts) unless serializer_class.plugin_used?(:preloads)
22
69
  end
23
70
 
24
71
  #
25
- # Loads plugin code and additional plugins
72
+ # Applies plugin code to specific serializer
26
73
  #
27
74
  # @param serializer_class [Class<Serega>] Current serializer class
28
- # @param opts [Hash] loaded plugins opts
75
+ # @param _opts [Hash] Loaded plugins options
29
76
  #
30
77
  # @return [void]
31
78
  #
32
- def self.load_plugin(serializer_class, **opts)
79
+ def self.load_plugin(serializer_class, **_opts)
33
80
  require_relative "./lib/preloader"
34
81
 
35
82
  serializer_class.include(InstanceMethods)
36
83
  end
37
84
 
38
- # Overrides Serega classes instance methods
85
+ #
86
+ # Overrides Serega class instance methods
87
+ #
39
88
  module InstanceMethods
89
+ private
90
+
40
91
  #
41
- # Override original #to_h method
42
- # @see Serega#to_h
92
+ # Override original #serialize method
93
+ # Preloads associations to object before serialization
43
94
  #
44
- def to_h(object, *)
95
+ def serialize(object, _opts)
45
96
  object = add_preloads(object)
46
97
  super
47
98
  end
48
99
 
49
- private
50
-
51
100
  def add_preloads(obj)
52
101
  return obj if obj.nil? || (obj.is_a?(Array) && obj.empty?)
53
102
 
54
- # preloads() method comes from :preloads plugin
55
- preloads = preloads()
103
+ preloads = preloads() # `preloads()` method comes from :preloads plugin
56
104
  return obj if preloads.empty?
57
105
 
58
106
  Preloader.preload(obj, preloads)