serega 0.18.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.18.0
1
+ 0.20.0
@@ -21,6 +21,10 @@ class Serega
21
21
  # @return [Boolean, nil] Attribute :many option
22
22
  attr_reader :many
23
23
 
24
+ # Attribute :default option
25
+ # @return [Object, nil] Attribute :default option
26
+ attr_reader :default
27
+
24
28
  # Attribute :hide option
25
29
  # @return [Boolean, nil] Attribute :hide option
26
30
  attr_reader :hide
@@ -108,9 +112,10 @@ class Serega
108
112
 
109
113
  def set_normalized_vars(normalizer)
110
114
  @name = normalizer.name
115
+ @many = normalizer.many
116
+ @default = normalizer.default
111
117
  @value_block = normalizer.value_block
112
118
  @hide = normalizer.hide
113
- @many = normalizer.many
114
119
  @serializer = normalizer.serializer
115
120
  end
116
121
  end
@@ -86,6 +86,19 @@ class Serega
86
86
  @serializer = prepare_serializer
87
87
  end
88
88
 
89
+ #
90
+ # Shows the default attribute value. It is a value that replaces found nils.
91
+ #
92
+ # When custom :default is not specified, we set empty array as default when `many: true` specified
93
+ #
94
+ # @return [Object] Attribute default value
95
+ #
96
+ def default
97
+ return @default if instance_variable_defined?(:@default)
98
+
99
+ @default = prepare_default
100
+ end
101
+
89
102
  private
90
103
 
91
104
  def prepare_name
@@ -97,11 +110,14 @@ class Serega
97
110
  # - plugin :formatters (wraps resulted block in formatter block and formats :const values)
98
111
  #
99
112
  def prepare_value_block
100
- prepare_init_block ||
113
+ value_block =
114
+ prepare_init_block ||
101
115
  prepare_value_option_block ||
102
116
  prepare_const_block ||
103
117
  prepare_delegate_block ||
104
118
  prepare_keyword_block
119
+
120
+ prepare_value_block_with_default(value_block)
105
121
  end
106
122
 
107
123
  #
@@ -158,6 +174,20 @@ class Serega
158
174
  end
159
175
  end
160
176
 
177
+ def prepare_value_block_with_default(callable)
178
+ default_value = default
179
+ return callable if default_value.nil?
180
+
181
+ proc { |obj, ctx|
182
+ res = callable.call(obj, ctx)
183
+ res.nil? ? default_value : res
184
+ }
185
+ end
186
+
187
+ def prepare_default
188
+ init_opts.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
189
+ end
190
+
161
191
  def prepare_delegate_block
162
192
  delegate = init_opts[:delegate]
163
193
  return unless delegate
data/lib/serega/config.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
4
-
5
3
  class Serega
6
4
  #
7
5
  # Stores serialization config
@@ -15,7 +13,7 @@ class Serega
15
13
  DEFAULTS = {
16
14
  plugins: [],
17
15
  initiate_keys: %i[only with except check_initiate_params].freeze,
18
- attribute_keys: %i[method value serializer many hide const delegate].freeze,
16
+ attribute_keys: %i[method value serializer many hide const delegate default].freeze,
19
17
  serialize_keys: %i[context many].freeze,
20
18
  check_attribute_name: true,
21
19
  check_initiate_params: true,
data/lib/serega/errors.rb CHANGED
@@ -11,5 +11,13 @@ class Serega
11
11
  # @example
12
12
  # Serega.new(only: 'FOO')
13
13
  # # => Attribute 'FOO' not exists (Serega::AttributeNotExist)
14
- class AttributeNotExist < SeregaError; end
14
+ class AttributeNotExist < SeregaError
15
+ attr_reader :serializer, :attributes
16
+
17
+ def initialize(message = nil, serializer = nil, attributes = nil)
18
+ super(message)
19
+ @serializer = serializer
20
+ @attributes = attributes
21
+ end
22
+ end
15
23
  end
@@ -38,8 +38,8 @@ class Serega
38
38
 
39
39
  private
40
40
 
41
- def serialize_array(object)
42
- object.map { |obj| serialize_object(obj) }
41
+ def serialize_array(objects)
42
+ objects.map { |object| serialize_object(object) }
43
43
  end
44
44
 
45
45
  # Patched in:
@@ -79,11 +79,16 @@ class Serega
79
79
  end
80
80
 
81
81
  def relation_value(value, point)
82
- child_plan = point.child_plan
83
- child_serializer = point.child_object_serializer
84
- child_many = point.many
85
- serializer = child_serializer.new(context: context, plan: child_plan, many: child_many, **opts)
86
- serializer.serialize(value)
82
+ child_serializer(point).serialize(value)
83
+ end
84
+
85
+ def child_serializer(point)
86
+ point.child_object_serializer.new(
87
+ context: context,
88
+ plan: point.child_plan,
89
+ many: point.many,
90
+ **opts
91
+ )
87
92
  end
88
93
 
89
94
  def array?(object, many)
@@ -9,8 +9,6 @@ class Serega
9
9
  # SeregaPlanPoint instance methods
10
10
  #
11
11
  module InstanceMethods
12
- extend Forwardable
13
-
14
12
  # Link to current plan this point belongs to
15
13
  # @return [SeregaAttribute] Current plan
16
14
  attr_reader :plan
@@ -27,20 +25,6 @@ class Serega
27
25
  # @return [Hash] Attributes to serialize
28
26
  attr_reader :modifiers
29
27
 
30
- # @!method name
31
- # Attribute `name`
32
- # @see SeregaAttribute::AttributeInstanceMethods#name
33
- # @!method value
34
- # Attribute `value` block
35
- # @see SeregaAttribute::AttributeInstanceMethods#value
36
- # @!method many
37
- # Attribute `many` option
38
- # @see SeregaAttribute::AttributeInstanceMethods#many
39
- # @!method serializer
40
- # Attribute `serializer` option
41
- # @see SeregaAttribute::AttributeInstanceMethods#serializer
42
- def_delegators :@attribute, :name, :value, :many, :serializer
43
-
44
28
  #
45
29
  # Initializes plan point
46
30
  #
@@ -60,6 +44,30 @@ class Serega
60
44
  set_normalized_vars
61
45
  end
62
46
 
47
+ # Attribute `value`
48
+ # @see SeregaAttribute::AttributeInstanceMethods#value
49
+ def value(obj, ctx)
50
+ attribute.value(obj, ctx)
51
+ end
52
+
53
+ # Attribute `name`
54
+ # @see SeregaAttribute::AttributeInstanceMethods#value
55
+ def name
56
+ attribute.name
57
+ end
58
+
59
+ # Attribute `many` option
60
+ # @see SeregaAttribute::AttributeInstanceMethods#many
61
+ def many
62
+ attribute.many
63
+ end
64
+
65
+ # Attribute `serializer` option
66
+ # @see SeregaAttribute::AttributeInstanceMethods#serializer
67
+ def serializer
68
+ attribute.serializer
69
+ end
70
+
63
71
  #
64
72
  # @return [SeregaObjectSerializer] object serializer for child plan
65
73
  #
@@ -54,17 +54,21 @@ class Serega
54
54
  # Checks requirements to load plugin
55
55
  #
56
56
  # @param serializer_class [Class<Serega>] Current serializer class
57
- # @param _opts [Hash] plugins options
57
+ # @param opts [Hash] plugin options
58
58
  #
59
59
  # @return [void]
60
60
  #
61
- def self.before_load_plugin(serializer_class, **_opts)
61
+ def self.before_load_plugin(serializer_class, **opts)
62
+ opts.each_key do |key|
63
+ raise SeregaError, "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. No options are allowed"
64
+ end
65
+
62
66
  unless serializer_class.plugin_used?(:preloads)
63
- raise SeregaError, "Please load `plugin :preloads` first"
67
+ raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded after the :preloads plugin. Please load the :preloads plugin first"
64
68
  end
65
69
 
66
70
  if serializer_class.plugin_used?(:batch)
67
- raise SeregaError, "Plugin `activerecord_preloads` must be loaded before `batch`"
71
+ raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded before the :batch plugin"
68
72
  end
69
73
  end
70
74
 
@@ -72,7 +76,7 @@ class Serega
72
76
  # Applies plugin code to specific serializer
73
77
  #
74
78
  # @param serializer_class [Class<Serega>] Current serializer class
75
- # @param _opts [Hash] Loaded plugins options
79
+ # @param _opts [Hash] Plugin options
76
80
  #
77
81
  # @return [void]
78
82
  #
@@ -14,7 +14,7 @@ class Serega
14
14
  # end
15
15
  #
16
16
  # class UserSerializer < AppSerializer
17
- # attribute :comments_count, batch: { loader: CommentsCountBatchLoader, default: 0 }
17
+ # attribute :comments_count, batch: { loader: CommentsCountBatchLoader }, default: 0
18
18
  # attribute :company, serializer: CompanySerializer, batch: { loader: UserCompanyBatchLoader }
19
19
  # end
20
20
  #
@@ -25,11 +25,30 @@ class Serega
25
25
  :batch
26
26
  end
27
27
 
28
+ # Checks requirements to load plugin
29
+ #
30
+ # @param serializer_class [Class<Serega>] Current serializer class
31
+ # @param opts [Hash] plugin options
32
+ #
33
+ # @return [void]
34
+ #
35
+ def self.before_load_plugin(serializer_class, **opts)
36
+ allowed_keys = %i[auto_hide id_method]
37
+ opts.each_key do |key|
38
+ next if allowed_keys.include?(key)
39
+
40
+ raise SeregaError,
41
+ "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
42
+ " - :auto_hide [Boolean] - Marks attribute as hidden when it has :batch loader specified\n" \
43
+ " - :id_method [Symbol, #call] - Specified the default method to use to find object identifier"
44
+ end
45
+ end
46
+
28
47
  #
29
48
  # Applies plugin code to specific serializer
30
49
  #
31
50
  # @param serializer_class [Class<Serega>] Current serializer class
32
- # @param _opts [Hash] Loaded plugins options
51
+ # @param _opts [Hash] Plugin options
33
52
  #
34
53
  # @return [void]
35
54
  #
@@ -60,7 +79,7 @@ class Serega
60
79
  # Runs callbacks after plugin was attached
61
80
  #
62
81
  # @param serializer_class [Class<Serega>] Current serializer class
63
- # @param opts [Hash] loaded plugins opts
82
+ # @param opts [Hash] Plugin options
64
83
  #
65
84
  # @return [void]
66
85
  #
@@ -47,8 +47,6 @@ class Serega
47
47
  id_method = batch[:id_method] || self.class.serializer_class.config.batch.id_method
48
48
  id_method = prepare_batch_id_method(id_method)
49
49
 
50
- default = batch.fetch(:default) { many ? FROZEN_EMPTY_ARRAY : nil }
51
-
52
50
  {loader: loader, id_method: id_method, default: default}
53
51
  end
54
52
 
@@ -23,7 +23,7 @@ class Serega
23
23
  SeregaValidations::Utils::CheckOptIsHash.call(opts, :batch)
24
24
 
25
25
  batch = opts[:batch]
26
- SeregaValidations::Utils::CheckAllowedKeys.call(batch, %i[id_method loader default], :batch)
26
+ SeregaValidations::Utils::CheckAllowedKeys.call(batch, %i[id_method loader], :batch)
27
27
 
28
28
  check_batch_opt_id_method(batch, serializer_class)
29
29
  check_batch_opt_loader(batch, serializer_class)
@@ -48,11 +48,29 @@ class Serega
48
48
  :camel_case
49
49
  end
50
50
 
51
+ # Checks requirements to load plugin
52
+ #
53
+ # @param serializer_class [Class<Serega>] Current serializer class
54
+ # @param opts [Hash] plugin options
55
+ #
56
+ # @return [void]
57
+ #
58
+ def self.before_load_plugin(serializer_class, **opts)
59
+ allowed_keys = %i[transform]
60
+ opts.each_key do |key|
61
+ next if allowed_keys.include?(key)
62
+
63
+ raise SeregaError,
64
+ "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
65
+ " - :transform [#call] - Custom transformation"
66
+ end
67
+ end
68
+
51
69
  #
52
70
  # Applies plugin code to specific serializer
53
71
  #
54
72
  # @param serializer_class [Class<Serega>] Current serializer class
55
- # @param _opts [Hash] Loaded plugins options
73
+ # @param _opts [Hash] Plugin options
56
74
  #
57
75
  # @return [void]
58
76
  #
@@ -66,7 +84,7 @@ class Serega
66
84
  # Adds config options and runs other callbacks after plugin was loaded
67
85
  #
68
86
  # @param serializer_class [Class<Serega>] Current serializer class
69
- # @param opts [Hash] loaded plugins opts
87
+ # @param opts [Hash] Plugin options
70
88
  #
71
89
  # @return [void]
72
90
  #
@@ -30,13 +30,22 @@ class Serega
30
30
  # Checks requirements and loads additional plugins
31
31
  #
32
32
  # @param serializer_class [Class<Serega>] Current serializer class
33
- # @param opts [Hash] loaded plugins opts
33
+ # @param opts [Hash] Plugin options
34
34
  #
35
35
  # @return [void]
36
36
  #
37
37
  def self.before_load_plugin(serializer_class, **opts)
38
+ allowed_keys = %i[context_metadata_key]
39
+ opts.each_key do |key|
40
+ next if allowed_keys.include?(key)
41
+
42
+ raise SeregaError,
43
+ "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
44
+ " - :context_metadata_key [Symbol] - The key name that must be used to add metadata. Default is :meta."
45
+ end
46
+
38
47
  unless serializer_class.plugin_used?(:root)
39
- raise SeregaError, "Please load :root plugin first so we can wrap serialization response into top-level hash to add metadata there"
48
+ raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded after the :root plugin. Please load the :root plugin first"
40
49
  end
41
50
  end
42
51
 
@@ -44,7 +53,7 @@ class Serega
44
53
  # Applies plugin code to specific serializer
45
54
  #
46
55
  # @param serializer_class [Class<Serega>] Current serializer class
47
- # @param _opts [Hash] Loaded plugins options
56
+ # @param _opts [Hash] Plugin options
48
57
  #
49
58
  # @return [void]
50
59
  #
@@ -58,7 +67,7 @@ class Serega
58
67
  # Adds config options and runs other callbacks after plugin was loaded
59
68
  #
60
69
  # @param serializer_class [Class<Serega>] Current serializer class
61
- # @param opts [Hash] loaded plugins opts
70
+ # @param opts [Hash] Plugin options
62
71
  #
63
72
  # @return [void]
64
73
  #
@@ -41,11 +41,29 @@ class Serega
41
41
  :depth_limit
42
42
  end
43
43
 
44
+ # Checks requirements
45
+ #
46
+ # @param serializer_class [Class<Serega>] Current serializer class
47
+ # @param opts [Hash] Plugin options
48
+ #
49
+ # @return [void]
50
+ #
51
+ def self.before_load_plugin(serializer_class, **opts)
52
+ allowed_keys = %i[limit]
53
+ opts.each_key do |key|
54
+ next if allowed_keys.include?(key)
55
+
56
+ raise SeregaError,
57
+ "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
58
+ " - :limit [Integer] - Maximum serialization depth."
59
+ end
60
+ end
61
+
44
62
  #
45
63
  # Applies plugin code to specific serializer
46
64
  #
47
65
  # @param serializer_class [Class<Serega>] Current serializer class
48
- # @param _opts [Hash] Loaded plugins options
66
+ # @param _opts [Hash] Plugin options
49
67
  #
50
68
  # @return [void]
51
69
  #
@@ -58,7 +76,7 @@ class Serega
58
76
  # Adds config options and runs other callbacks after plugin was loaded
59
77
  #
60
78
  # @param serializer_class [Class<Serega>] Current serializer class
61
- # @param opts [Hash] loaded plugins opts
79
+ # @param opts [Hash] Plugin options
62
80
  #
63
81
  # @return [void]
64
82
  #
@@ -35,7 +35,7 @@ class Serega
35
35
  # Applies plugin code to specific serializer
36
36
  #
37
37
  # @param serializer_class [Class<Serega>] Current serializer class
38
- # @param _opts [Hash] Loaded plugins options
38
+ # @param _opts [Hash] Plugin options
39
39
  #
40
40
  # @return [void]
41
41
  #
@@ -51,13 +51,22 @@ class Serega
51
51
  # Checks requirements and loads additional plugins
52
52
  #
53
53
  # @param serializer_class [Class<Serega>] Current serializer class
54
- # @param opts [Hash] loaded plugins opts
54
+ # @param opts [Hash] Plugin options
55
55
  #
56
56
  # @return [void]
57
57
  #
58
58
  def self.before_load_plugin(serializer_class, **opts)
59
+ allowed_keys = %i[formatters]
60
+ opts.each_key do |key|
61
+ next if allowed_keys.include?(key)
62
+
63
+ raise SeregaError,
64
+ "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
65
+ " - :formatters [Hash<Symbol, #call>] - Formatters (names and according callable values)"
66
+ end
67
+
59
68
  if serializer_class.plugin_used?(:batch)
60
- raise SeregaError, "Plugin `formatters` must be loaded before `batch`"
69
+ raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded before the :batch plugin"
61
70
  end
62
71
  end
63
72
 
@@ -65,7 +74,7 @@ class Serega
65
74
  # Applies plugin code to specific serializer
66
75
  #
67
76
  # @param serializer_class [Class<Serega>] Current serializer class
68
- # @param _opts [Hash] Loaded plugins options
77
+ # @param _opts [Hash] Plugin options
69
78
  #
70
79
  # @return [void]
71
80
  #
@@ -79,7 +88,7 @@ class Serega
79
88
  # Adds config options and runs other callbacks after plugin was loaded
80
89
  #
81
90
  # @param serializer_class [Class<Serega>] Current serializer class
82
- # @param opts [Hash] loaded plugins opts
91
+ # @param opts [Hash] Plugin options
83
92
  #
84
93
  # @return [void]
85
94
  #
@@ -51,7 +51,7 @@ class Serega
51
51
  # Applies plugin code to specific serializer
52
52
  #
53
53
  # @param serializer_class [Class<Serega>] Current serializer class
54
- # @param _opts [Hash] Loaded plugins options
54
+ # @param _opts [Hash] Plugin options
55
55
  #
56
56
  # @return [void]
57
57
  #
@@ -72,7 +72,7 @@ class Serega
72
72
  # Adds config options and runs other callbacks after plugin was loaded
73
73
  #
74
74
  # @param serializer_class [Class<Serega>] Current serializer class
75
- # @param opts [Hash] loaded plugins opts
75
+ # @param opts [Hash] Plugin options
76
76
  #
77
77
  # @return [void]
78
78
  #
@@ -65,7 +65,7 @@ class Serega
65
65
  def normalize_block(value, const, block)
66
66
  return proc { const } if const
67
67
 
68
- callable = (value || block)
68
+ callable = value || block
69
69
  params_count = SeregaUtils::ParamsCount.call(callable, max_count: 2)
70
70
 
71
71
  case params_count
@@ -46,13 +46,13 @@ class Serega
46
46
  # Checks requirements and loads additional plugins
47
47
  #
48
48
  # @param serializer_class [Class<Serega>] Current serializer class
49
- # @param _opts [Hash] loaded plugins opts
49
+ # @param _opts [Hash] Plugin options
50
50
  #
51
51
  # @return [void]
52
52
  #
53
53
  def self.before_load_plugin(serializer_class, **_opts)
54
54
  unless serializer_class.plugin_used?(:root)
55
- raise SeregaError, "Please load :root plugin first so we can wrap serialization response into top-level hash to add metadata there"
55
+ raise SeregaError, "Plugin #{plugin_name.inspect} must be loaded after the :root plugin. Please load the :root plugin first"
56
56
  end
57
57
  end
58
58
 
@@ -60,7 +60,7 @@ class Serega
60
60
  # Applies plugin code to specific serializer
61
61
  #
62
62
  # @param serializer_class [Class<Serega>] Current serializer class
63
- # @param _opts [Hash] Loaded plugins options
63
+ # @param _opts [Hash] Plugin options
64
64
  #
65
65
  # @return [void]
66
66
  #
@@ -87,7 +87,7 @@ class Serega
87
87
  # Adds config options and runs other callbacks after plugin was loaded
88
88
  #
89
89
  # @param serializer_class [Class<Serega>] Current serializer class
90
- # @param _opts [Hash] loaded plugins opts
90
+ # @param _opts [Hash] Plugin options
91
91
  #
92
92
  # @return [void]
93
93
  #
@@ -51,7 +51,7 @@ class Serega
51
51
  opts.fetch(method_name)
52
52
  end
53
53
 
54
- define_method("#{method_name}=") do |value|
54
+ define_method(:"#{method_name}=") do |value|
55
55
  raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
56
56
  opts[method_name] = value
57
57
  end
@@ -70,11 +70,31 @@ class Serega
70
70
  :preloads
71
71
  end
72
72
 
73
+ # Checks requirements to load plugin
74
+ #
75
+ # @param serializer_class [Class<Serega>] Current serializer class
76
+ # @param opts [Hash] plugin options
77
+ #
78
+ # @return [void]
79
+ #
80
+ def self.before_load_plugin(serializer_class, **opts)
81
+ allowed_keys = DEFAULT_CONFIG.keys
82
+ opts.each_key do |key|
83
+ next if allowed_keys.include?(key)
84
+
85
+ raise SeregaError,
86
+ "Plugin #{plugin_name.inspect} does not accept the #{key.inspect} option. Allowed options:\n" \
87
+ " - :auto_preload_attributes_with_delegate [Boolean] - Automatically adds `preload: <delegate_to>` option to attributes with :delegate option specified\n" \
88
+ " - :auto_preload_attributes_with_serializer [Boolean] - Automatically adds `preload: <attribute_name>` option to attributes with :serializer option specified\n" \
89
+ " - :auto_hide_attributes_with_preload [Boolean] - Automatically adds `hide: true` option to attributes with :preload option (specified manually or added automatically)"
90
+ end
91
+ end
92
+
73
93
  #
74
94
  # Applies plugin code to specific serializer
75
95
  #
76
96
  # @param serializer_class [Class<Serega>] Current serializer class
77
- # @param _opts [Hash] Loaded plugins options
97
+ # @param _opts [Hash] Plugin options
78
98
  #
79
99
  # @return [void]
80
100
  #
@@ -104,7 +124,7 @@ class Serega
104
124
  # Adds config options and runs other callbacks after plugin was loaded
105
125
  #
106
126
  # @param serializer_class [Class<Serega>] Current serializer class
107
- # @param opts [Hash] loaded plugins opts
127
+ # @param opts [Hash] Plugin options
108
128
  #
109
129
  # @return [void]
110
130
  #
@@ -29,7 +29,7 @@ class Serega
29
29
  # Applies plugin code to specific serializer
30
30
  #
31
31
  # @param serializer_class [Class<Serega>] Current serializer class
32
- # @param _opts [Hash] Loaded plugins options
32
+ # @param _opts [Hash] Plugin options
33
33
  #
34
34
  # @return [void]
35
35
  #
@@ -42,7 +42,7 @@ class Serega
42
42
  # Runs callbacks after plugin was attached
43
43
  #
44
44
  # @param serializer_class [Class<Serega>] Current serializer class
45
- # @param _opts [Hash] loaded plugins opts
45
+ # @param _opts [Hash] Plugin options
46
46
  #
47
47
  # @return [void]
48
48
  #