serega 0.8.2 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d467016ac66fcd328919f3d7068dd556ac5a7768f657adb30948c2c316480a1
4
- data.tar.gz: 3ae0c46dc6760cdf94299c0edc34b41a8b195cea92dff13b91a4017bb21653f7
3
+ metadata.gz: 6b6c3672a4fc40557f4fef3e650de56c84bf23d96defabd3f3f04aa5f273aa90
4
+ data.tar.gz: 79bcc2c3526b0ae141de69003e0834c94f9b7bade028cbc85bed20c3f3ba1757
5
5
  SHA512:
6
- metadata.gz: 9f2a9f714be636a26f4ca2de8a5f692455080354f495dc5cc2d69dd874d207ddd0bc3286b47ea600ebf5a0ba2ddfd774d6876da67e82906d35319f4a54614caa
7
- data.tar.gz: 766931dbb8aaa415dcc008a477258ba6f63ca35b664267278ee31ab0becc91f124e16eb1f57bd5968df03e333bb59c98a8396f796be84c254a76e674a472d6e1
6
+ metadata.gz: edcc6856f212848b721af517bb0f77695e6e7f2adbc7bb687e4573a62efe84e02266301cc3e70647b7cd8385b96901436ce1ded9446e9712f3884d8d6348c855
7
+ data.tar.gz: e0fec25bc4471ebbf3b6fbfb1967101af7f8cd7e703cb56eba935fd51bc6de0efe87d802be8257a950648afc633ed3ceb3bfc78f67b3905197ffa0a6e39ada32
data/README.md CHANGED
@@ -20,6 +20,7 @@ It has some great features:
20
20
  - Built-in object presenter ([presenter][presenter] plugin)
21
21
  - Adding custom metadata (via [metadata][metadata] or [context_metadata][context_metadata] plugins)
22
22
  - Attributes formatters ([formatters][formatters] plugin)
23
+ - Conditional attributes ([if][if] plugin)
23
24
 
24
25
  ## Installation
25
26
 
@@ -94,9 +95,11 @@ class UserSerializer < Serega
94
95
  # It allows to specify associations to preload to attribute value
95
96
  attribute :email, preload: :emails, value: proc { |user| user.emails.find(&:verified?) }
96
97
 
97
- # Option `:hide_nil` can be specified when enabled `:hide_nil` plugin
98
- # It is literally hides attribute if its value is nil
99
- attribute :email, hide_nil: true
98
+ # Options `:if`, `:unless`, `:if_value`, `:unless_value` can be specified when enabled `:if` plugin
99
+ # They hide attribute key and value from response when conditions satisfied
100
+ # See more usage examples in :if plugin section.
101
+ attribute :email, if: proc { |user, ctx| user == ctx[:current_user] }
102
+ attribute :email, if_value: :present?
100
103
 
101
104
  # Option `:format` can be specified when enabled `:formatters` plugin
102
105
  # It changes attribute value
@@ -600,16 +603,45 @@ PostSerializer.new(with: "user(email)").to_h(post)
600
603
  PostSerializer.new(with: {user: %i[email, username]}).to_h(post)
601
604
  ```
602
605
 
603
- ### Plugin :hide_nil
606
+ ### Plugin :if
604
607
 
605
- Allows to hide attributes with `nil` values
608
+ Plugin adds `:if`, `:unless`, `:if_value`, `:unless_value` options to
609
+ attributes so we can remove attributes from response in various ways.
606
610
 
607
- ```ruby
608
- class UserSerializer < Serega
609
- plugin :hide_nil
611
+ Use `:if` and `:unless` when you want to hide attributes before finding attribute value,
612
+ and use `:if_value` and `:unless_value` to hide attributes after we find final value.
610
613
 
611
- attribute :email, hide_nil: true
612
- end
614
+ Options `:if` and `:unless` accept currently serialized object and context as parameters.
615
+ Options `:if_value` and `:unless_value` accept already found serialized value and context as parameters.
616
+
617
+ Options `:if_value` and `:unless_value` cannot be used with :serializer option, as
618
+ serialized objects have no "serialized value". Use `:if` and `:unless` in this case.
619
+
620
+ See also a `:hide` option that is available without any plugins to hide
621
+ attribute without conditions. Look at [select serialized fields](#selecting-fields) for `:hide` usage examples.
622
+
623
+ ```ruby
624
+ class UserSerializer < Serega
625
+ attribute :email, if: :active? # if user.active?
626
+ attribute :email, if: proc {|user| user.active?} # same
627
+ attribute :email, if: proc {|user, ctx| user == ctx[:current_user]} # using context
628
+ attribute :email, if: CustomPolicy.method(:view_email?) # You can provide own callable object
629
+
630
+ attribute :email, unless: :hidden? # unless user.hidden?
631
+ attribute :email, unless: proc {|user| user.hidden?} # same
632
+ attribute :email, unless: proc {|user, context| context[:show_emails]} # using context
633
+ attribute :email, unless: CustomPolicy.method(:hide_email?) # You can provide own callable object
634
+
635
+ attribute :email, if_value: :present? # if email.present?
636
+ attribute :email, if_value: proc {|email| email.present?} # same
637
+ attribute :email, if_value: proc {|email, ctx| ctx[:show_emails]} # using context
638
+ attribute :email, if_value: CustomPolicy.method(:view_email?) # You can provide own callable object
639
+
640
+ attribute :email, unless_value: :blank? # unless email.blank?
641
+ attribute :email, unless_value: proc {|email| email.blank?} # same
642
+ attribute :email, unless_value: proc {|email, context| context[:show_emails]} # using context
643
+ attribute :email, unless_value: CustomPolicy.method(:hide_email?) # You can provide own callable object
644
+ end
613
645
  ```
614
646
 
615
647
  ## Errors
@@ -648,3 +680,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
648
680
  [presenter]: #plugin-presenter
649
681
  [root]: #plugin-root
650
682
  [string_modifiers]: #plugin-string_modifiers
683
+ [if]: #plugin-if
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.2
1
+ 0.9.0
@@ -6,6 +6,9 @@ class Serega
6
6
  # JSON dump adapter for ::Oj
7
7
  #
8
8
  class OjDump
9
+ # Default Oj serialization options
10
+ OPTS = {mode: :compat}.freeze
11
+
9
12
  #
10
13
  # Dumps data to JSON string
11
14
  #
@@ -14,7 +17,7 @@ class Serega
14
17
  # @return [String] Data serialized to JSON
15
18
  #
16
19
  def self.call(data)
17
- ::Oj.dump(data, mode: :compat)
20
+ ::Oj.dump(data, OPTS)
18
21
  end
19
22
  end
20
23
 
data/lib/serega/map.rb CHANGED
@@ -35,7 +35,8 @@ class Serega
35
35
 
36
36
  def cached_map_for(opts, max_cache_size)
37
37
  @cache ||= {}
38
- cache_key = opts.to_s
38
+ cache_key = construct_cache_key(opts)
39
+
39
40
  map = @cache[cache_key] ||= map_for(opts)
40
41
  @cache.shift if @cache.length > max_cache_size
41
42
  map
@@ -50,7 +51,8 @@ class Serega
50
51
  end
51
52
 
52
53
  def construct_map(serializer_class, only:, except:, with:)
53
- serializer_class.attributes.each_with_object([]) do |(name, attribute), map|
54
+ map = []
55
+ serializer_class.attributes.each do |name, attribute|
54
56
  next unless attribute.visible?(only: only, except: except, with: with)
55
57
 
56
58
  nested_points =
@@ -65,6 +67,21 @@ class Serega
65
67
 
66
68
  map << serializer_class::SeregaMapPoint.new(attribute, nested_points)
67
69
  end
70
+ map
71
+ end
72
+
73
+ def construct_cache_key(opts, cache_key = nil)
74
+ return nil if opts.empty?
75
+
76
+ cache_key ||= +""
77
+
78
+ opts.each do |key, nested_opts|
79
+ cache_key.insert(-1, SeregaUtils::SymbolName.call(key))
80
+ cache_key.insert(-1, "-")
81
+ construct_cache_key(nested_opts, cache_key)
82
+ end
83
+
84
+ cache_key
68
85
  end
69
86
  end
70
87
 
@@ -43,10 +43,14 @@ class Serega
43
43
 
44
44
  def serialize_object(object)
45
45
  points.each_with_object({}) do |point, container|
46
- attach_value(object, point, container)
46
+ serialize_point(object, point, container)
47
47
  end
48
48
  end
49
49
 
50
+ def serialize_point(object, point, container)
51
+ attach_value(object, point, container)
52
+ end
53
+
50
54
  def attach_value(object, point, container)
51
55
  value = point.value(object, context)
52
56
  final_value = final_value(value, point)
@@ -264,13 +264,17 @@ class Serega
264
264
  batch = point.batch
265
265
 
266
266
  if batch
267
- key = batch.key.call(object, context)
268
- opts[:batch_loaders].get(point, self).remember(key, container)
269
- container[point.name] = nil # Reserve attribute place in resulted hash. We will set correct value later
267
+ remember_key_for_batch_loading(batch, object, point, container)
270
268
  else
271
269
  super
272
270
  end
273
271
  end
272
+
273
+ def remember_key_for_batch_loading(batch, object, point, container)
274
+ key = batch.key.call(object, context)
275
+ opts[:batch_loaders].get(point, self).remember(key, container)
276
+ container[point.name] = nil # Reserve attribute place in resulted hash. We will set correct value later
277
+ end
274
278
  end
275
279
  end
276
280
 
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ #
6
+ # Plugin adds `:if`, `:unless`, `:if_value`, `:unless_value` options to
7
+ # attributes so we can remove attributes from response in various ways.
8
+ #
9
+ # Use `:if` and `:unless` when you want to hide attributes before finding attribute value,
10
+ # and use `:if_value` and `:unless_value` to hide attributes after we find final value.
11
+ #
12
+ # Options `:if` and `:unless` accept currently serialized object and context as parameters.
13
+ # Options `:if_value` and `:unless_value` accept already found serialized value and context as parameters.
14
+ #
15
+ # Options `:if_value` and `:unless_value` cannot be used with :serializer option, as
16
+ # serialized objects have no "serialized value". Use `:if` and `:unless` in this case.
17
+ #
18
+ # See also a `:hide` option that is available without any plugins to hide
19
+ # attribute without conditions. Look at README.md#selecting-fields for `:hide` usage examples.
20
+ #
21
+ # Examples:
22
+ # class UserSerializer < Serega
23
+ # attribute :email, if: :active? # if user.active?
24
+ # attribute :email, if: proc {|user| user.active?} # same
25
+ # attribute :email, if: proc {|user, ctx| user == ctx[:current_user]} # using context
26
+ # attribute :email, if: CustomPolicy.method(:view_email?) # You can provide own callable object
27
+ #
28
+ # attribute :email, unless: :hidden? # unless user.hidden?
29
+ # attribute :email, unless: proc {|user| user.hidden?} # same
30
+ # attribute :email, unless: proc {|user, context| context[:show_emails]} # using context
31
+ # attribute :email, unless: CustomPolicy.method(:hide_email?) # You can provide own callable object
32
+ #
33
+ # attribute :email, if_value: :present? # if email.present?
34
+ # attribute :email, if_value: proc {|email| email.present?} # same
35
+ # attribute :email, if_value: proc {|email, ctx| ctx[:show_emails]} # using context
36
+ # attribute :email, if_value: CustomPolicy.method(:view_email?) # You can provide own callable object
37
+ #
38
+ # attribute :email, unless_value: :blank? # unless email.blank?
39
+ # attribute :email, unless_value: proc {|email| email.blank?} # same
40
+ # attribute :email, unless_value: proc {|email, context| context[:show_emails]} # using context
41
+ # attribute :email, unless_value: CustomPolicy.method(:hide_email?) # You can provide own callable object
42
+ # end
43
+ #
44
+ module If
45
+ # @return [Symbol] Plugin name
46
+ def self.plugin_name
47
+ :if
48
+ end
49
+
50
+ #
51
+ # Applies plugin code to specific serializer
52
+ #
53
+ # @param serializer_class [Class<Serega>] Current serializer class
54
+ # @param _opts [Hash] Loaded plugins options
55
+ #
56
+ # @return [void]
57
+ #
58
+ def self.load_plugin(serializer_class, **_opts)
59
+ require_relative "./validations/check_opt_if"
60
+ require_relative "./validations/check_opt_if_value"
61
+ require_relative "./validations/check_opt_unless"
62
+ require_relative "./validations/check_opt_unless_value"
63
+
64
+ serializer_class::SeregaMapPoint.include(MapPointInstanceMethods)
65
+ serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
66
+ serializer_class::SeregaObjectSerializer.include(SeregaObjectSerializerInstanceMethods)
67
+ end
68
+
69
+ # Checks requirements and loads additional plugins
70
+ #
71
+ # @param serializer_class [Class<Serega>] Current serializer class
72
+ # @param opts [Hash] loaded plugins opts
73
+ #
74
+ # @return [void]
75
+ #
76
+ def self.before_load_plugin(serializer_class, **opts)
77
+ if serializer_class.plugin_used?(:batch)
78
+ raise SeregaError, "Plugin `#{plugin_name}` must be loaded before `batch`"
79
+ end
80
+ end
81
+
82
+ #
83
+ # Adds config options and runs other callbacks after plugin was loaded
84
+ #
85
+ # @param serializer_class [Class<Serega>] Current serializer class
86
+ # @param opts [Hash] loaded plugins opts
87
+ #
88
+ # @return [void]
89
+ #
90
+ def self.after_load_plugin(serializer_class, **opts)
91
+ serializer_class.config.attribute_keys << :if << :if_value << :unless << :unless_value
92
+ end
93
+
94
+ #
95
+ # Serega::SeregaMapPoint additional/patched instance methods
96
+ #
97
+ # @see Serega::SeregaMapPoint::InstanceMethods
98
+ #
99
+ module MapPointInstanceMethods
100
+ #
101
+ # @return [Boolean] Should we show attribute or not
102
+ # Conditions for this checks are specified by :if and :unless attribute options.
103
+ #
104
+ def satisfy_if_conditions?(obj, ctx)
105
+ check_if_unless(obj, ctx, :if, :unless)
106
+ end
107
+
108
+ #
109
+ # @return [Boolean] Should we show attribute with specific value or not.
110
+ # Conditions for this checks are specified by :if_value and :unless_value attribute options.
111
+ #
112
+ def satisfy_if_value_conditions?(value, ctx)
113
+ check_if_unless(value, ctx, :if_value, :unless_value)
114
+ end
115
+
116
+ private
117
+
118
+ def check_if_unless(obj, ctx, opt_if_name, opt_unless_name)
119
+ opt_if = attribute.opts[opt_if_name]
120
+ opt_unless = attribute.opts[opt_unless_name]
121
+ return true if opt_if.nil? && opt_unless.nil?
122
+
123
+ res_if =
124
+ case opt_if
125
+ when NilClass then true
126
+ when Symbol then obj.public_send(opt_if)
127
+ else opt_if.call(obj, ctx)
128
+ end
129
+
130
+ res_unless =
131
+ case opt_unless
132
+ when NilClass then true
133
+ when Symbol then !obj.public_send(opt_unless)
134
+ else !opt_unless.call(obj, ctx)
135
+ end
136
+
137
+ res_if && res_unless
138
+ end
139
+ end
140
+
141
+ #
142
+ # Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
143
+ #
144
+ # @see Serega::SeregaValidations::CheckAttributeParams
145
+ #
146
+ module CheckAttributeParamsInstanceMethods
147
+ private
148
+
149
+ def check_opts
150
+ super
151
+
152
+ CheckOptIf.call(opts)
153
+ CheckOptUnless.call(opts)
154
+ CheckOptIfValue.call(opts)
155
+ CheckOptUnlessValue.call(opts)
156
+ end
157
+ end
158
+
159
+ #
160
+ # SeregaObjectSerializer additional/patched class methods
161
+ #
162
+ # @see Serega::SeregaObjectSerializer
163
+ #
164
+ module SeregaObjectSerializerInstanceMethods
165
+ private
166
+
167
+ def serialize_point(object, point, _container)
168
+ return unless point.satisfy_if_conditions?(object, context)
169
+ super
170
+ end
171
+
172
+ def attach_final_value(value, point, _container)
173
+ return unless point.satisfy_if_value_conditions?(value, context)
174
+ super
175
+ end
176
+ end
177
+ end
178
+
179
+ register_plugin(If.plugin_name, If)
180
+ end
181
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module If
6
+ #
7
+ # Validator for attribute :if option
8
+ #
9
+ class CheckOptIf
10
+ class << self
11
+ #
12
+ # Checks attribute :if option that must be [nil, Symbol, Proc, #call]
13
+ #
14
+ # @param opts [Hash] Attribute options
15
+ #
16
+ # @raise [SeregaError] Attribute validation error
17
+ #
18
+ # @return [void]
19
+ #
20
+ def call(opts)
21
+ return unless opts.key?(:if)
22
+
23
+ check_type(opts[:if])
24
+ end
25
+
26
+ private
27
+
28
+ def check_type(value)
29
+ return if value.is_a?(Symbol)
30
+
31
+ raise SeregaError, must_be_callable unless value.respond_to?(:call)
32
+
33
+ if value.is_a?(Proc)
34
+ check_block(value)
35
+ else
36
+ check_callable(value)
37
+ end
38
+ end
39
+
40
+ def check_block(block)
41
+ return if valid_parameters?(block, accepted_count: 0..2)
42
+
43
+ raise SeregaError, block_parameters_error
44
+ end
45
+
46
+ def check_callable(callable)
47
+ return if valid_parameters?(callable.method(:call), accepted_count: 2..2)
48
+
49
+ raise SeregaError, callable_parameters_error
50
+ end
51
+
52
+ def valid_parameters?(data, accepted_count:)
53
+ params = data.parameters
54
+ accepted_count.include?(params.count) && valid_parameters_types?(params)
55
+ end
56
+
57
+ def valid_parameters_types?(params)
58
+ params.all? do |param|
59
+ type = param[0]
60
+ (type == :req) || (type == :opt)
61
+ end
62
+ end
63
+
64
+ def block_parameters_error
65
+ "Invalid attribute option :if. When it is a Proc it can have maximum two regular parameters (object, context)"
66
+ end
67
+
68
+ def callable_parameters_error
69
+ "Invalid attribute option :if. When it is a callable object it must have two regular parameters (object, context)"
70
+ end
71
+
72
+ def must_be_callable
73
+ "Invalid attribute option :if. It must be a Symbol, a Proc or respond to :call"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module If
6
+ #
7
+ # Validator for attribute :if_value option
8
+ #
9
+ class CheckOptIfValue
10
+ class << self
11
+ #
12
+ # Checks attribute :if_value option that must be [nil, Symbol, Proc, #call]
13
+ #
14
+ # @param opts [Hash] Attribute options
15
+ #
16
+ # @raise [SeregaError] Attribute validation error
17
+ #
18
+ # @return [void]
19
+ #
20
+ def call(opts)
21
+ return unless opts.key?(:if_value)
22
+
23
+ check_usage_with_other_params(opts)
24
+ check_type(opts[:if_value])
25
+ end
26
+
27
+ private
28
+
29
+ def check_type(value)
30
+ return if value.is_a?(Symbol)
31
+
32
+ raise SeregaError, must_be_callable unless value.respond_to?(:call)
33
+
34
+ if value.is_a?(Proc)
35
+ check_block(value)
36
+ else
37
+ check_callable(value)
38
+ end
39
+ end
40
+
41
+ def check_usage_with_other_params(opts)
42
+ raise SeregaError, "Option :if_value can not be used together with option :serializer" if opts.key?(:serializer)
43
+ end
44
+
45
+ def check_block(block)
46
+ return if valid_parameters?(block, accepted_count: 0..2)
47
+
48
+ raise SeregaError, block_parameters_error
49
+ end
50
+
51
+ def check_callable(callable)
52
+ return if valid_parameters?(callable.method(:call), accepted_count: 2..2)
53
+
54
+ raise SeregaError, callable_parameters_error
55
+ end
56
+
57
+ def valid_parameters?(data, accepted_count:)
58
+ params = data.parameters
59
+ accepted_count.include?(params.count) && valid_parameters_types?(params)
60
+ end
61
+
62
+ def valid_parameters_types?(params)
63
+ params.all? do |param|
64
+ type = param[0]
65
+ (type == :req) || (type == :opt)
66
+ end
67
+ end
68
+
69
+ def block_parameters_error
70
+ "Invalid attribute option :if_value. When it is a Proc it can have maximum two regular parameters (object, context)"
71
+ end
72
+
73
+ def callable_parameters_error
74
+ "Invalid attribute option :if_value. When it is a callable object it must have two regular parameters (object, context)"
75
+ end
76
+
77
+ def must_be_callable
78
+ "Invalid attribute option :if_value. It must be a Symbol, a Proc or respond to :call"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module If
6
+ #
7
+ # Validator for attribute :unless option
8
+ #
9
+ class CheckOptUnless
10
+ class << self
11
+ #
12
+ # Checks attribute :unless option that must be [nil, Symbol, Proc, #call]
13
+ #
14
+ # @param opts [Hash] Attribute options
15
+ #
16
+ # @raise [SeregaError] Attribute validation error
17
+ #
18
+ # @return [void]
19
+ #
20
+ def call(opts)
21
+ return unless opts.key?(:unless)
22
+
23
+ check_type(opts[:unless])
24
+ end
25
+
26
+ private
27
+
28
+ #
29
+ # Checks attribute :unless option
30
+ #
31
+ # @param value [nil, Symbol, Proc, #call] Attribute :unless option
32
+ #
33
+ # @raise [SeregaError] validation error
34
+ #
35
+ # @return [void]
36
+ #
37
+ def check_type(value)
38
+ return if value.is_a?(Symbol)
39
+
40
+ raise SeregaError, must_be_callable unless value.respond_to?(:call)
41
+
42
+ if value.is_a?(Proc)
43
+ check_block(value)
44
+ else
45
+ check_callable(value)
46
+ end
47
+ end
48
+
49
+ def check_block(block)
50
+ return if valid_parameters?(block, accepted_count: 0..2)
51
+
52
+ raise SeregaError, block_parameters_error
53
+ end
54
+
55
+ def check_callable(callable)
56
+ return if valid_parameters?(callable.method(:call), accepted_count: 2..2)
57
+
58
+ raise SeregaError, callable_parameters_error
59
+ end
60
+
61
+ def valid_parameters?(data, accepted_count:)
62
+ params = data.parameters
63
+ accepted_count.include?(params.count) && valid_parameters_types?(params)
64
+ end
65
+
66
+ def valid_parameters_types?(params)
67
+ params.all? do |param|
68
+ type = param[0]
69
+ (type == :req) || (type == :opt)
70
+ end
71
+ end
72
+
73
+ def block_parameters_error
74
+ "Invalid attribute option :unless. When it is a Proc it can have maximum two regular parameters (object, context)"
75
+ end
76
+
77
+ def callable_parameters_error
78
+ "Invalid attribute option :unless. When it is a callable object it must have two regular parameters (object, context)"
79
+ end
80
+
81
+ def must_be_callable
82
+ "Invalid attribute option :unless. It must be a Symbol, a Proc or respond to :call"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaPlugins
5
+ module If
6
+ #
7
+ # Validator for attribute :unless_value option
8
+ #
9
+ class CheckOptUnlessValue
10
+ class << self
11
+ #
12
+ # Checks attribute :unless_value option that must be [nil, Symbol, Proc, #call]
13
+ #
14
+ # @param opts [Hash] Attribute options
15
+ #
16
+ # @raise [SeregaError] Attribute validation error
17
+ #
18
+ # @return [void]
19
+ #
20
+ def call(opts)
21
+ return unless opts.key?(:unless_value)
22
+
23
+ check_usage_with_other_params(opts)
24
+ check_type(opts[:unless_value])
25
+ end
26
+
27
+ private
28
+
29
+ def check_type(value)
30
+ return if value.is_a?(Symbol)
31
+
32
+ raise SeregaError, must_be_callable unless value.respond_to?(:call)
33
+
34
+ if value.is_a?(Proc)
35
+ check_block(value)
36
+ else
37
+ check_callable(value)
38
+ end
39
+ end
40
+
41
+ def check_usage_with_other_params(opts)
42
+ raise SeregaError, "Option :unless_value can not be used together with option :serializer" if opts.key?(:serializer)
43
+ end
44
+
45
+ def check_block(block)
46
+ return if valid_parameters?(block, accepted_count: 0..2)
47
+
48
+ raise SeregaError, block_parameters_error
49
+ end
50
+
51
+ def check_callable(callable)
52
+ return if valid_parameters?(callable.method(:call), accepted_count: 2..2)
53
+
54
+ raise SeregaError, callable_parameters_error
55
+ end
56
+
57
+ def valid_parameters?(data, accepted_count:)
58
+ params = data.parameters
59
+ accepted_count.include?(params.count) && valid_parameters_types?(params)
60
+ end
61
+
62
+ def valid_parameters_types?(params)
63
+ params.all? do |param|
64
+ type = param[0]
65
+ (type == :req) || (type == :opt)
66
+ end
67
+ end
68
+
69
+ def block_parameters_error
70
+ "Invalid attribute option :unless_value. When it is a Proc it can have maximum two regular parameters (object, context)"
71
+ end
72
+
73
+ def callable_parameters_error
74
+ "Invalid attribute option :unless_value. When it is a callable object it must have two regular parameters (object, context)"
75
+ end
76
+
77
+ def must_be_callable
78
+ "Invalid attribute option :unless_value. It must be a Symbol, a Proc or respond to :call"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "stringio"
4
+
3
5
  class Serega
4
6
  module SeregaPlugins
5
7
  #
@@ -14,7 +16,7 @@ class Serega
14
16
  # PostSerializer.new(only: "id,user(id,username)").to_h(post)
15
17
  # PostSerializer.new(except: "user(username,email)").to_h(post)
16
18
  # PostSerializer.new(with: "user(email)").to_h(post)
17
-
19
+ #
18
20
  # # Modifiers can still be provided old way with nested hashes or arrays.
19
21
  # PostSerializer.new(with: {user: %i[email, username]}).to_h(post)
20
22
  #
@@ -52,9 +54,11 @@ class Serega
52
54
  def parse(fields)
53
55
  res = {}
54
56
  attribute = +""
57
+ char = +""
55
58
  path_stack = nil
59
+ fields = StringIO.new(fields)
56
60
 
57
- fields.each_char do |char|
61
+ while fields.read(1, char)
58
62
  case char
59
63
  when ","
60
64
  add_attribute(res, path_stack, attribute, FROZEN_EMPTY_HASH)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ module SeregaUtils
5
+ #
6
+ # Utility to get frozen string from symbol in any ruby version
7
+ #
8
+ class SymbolName
9
+ class << self
10
+ #
11
+ # Returns frozen string corresponding to provided symbol
12
+ #
13
+ # @param key [Symbol]
14
+ #
15
+ # @return [String] frozen string corresponding to provided symbol
16
+ #
17
+ def call(key)
18
+ key.is_a?(String) ? key : to_frozen_string(key)
19
+ end
20
+
21
+ private
22
+
23
+ # :nocov:
24
+ if RUBY_VERSION < "3"
25
+ def to_frozen_string(key)
26
+ key.to_s.freeze
27
+ end
28
+ else
29
+ def to_frozen_string(key)
30
+ key.name
31
+ end
32
+ end
33
+ # :nocov:
34
+ end
35
+ end
36
+ end
37
+ end
@@ -27,7 +27,7 @@ class Serega
27
27
  # @return [void]
28
28
  #
29
29
  def call(name)
30
- name = name.to_s
30
+ name = SeregaUtils::SymbolName.call(name)
31
31
 
32
32
  valid =
33
33
  case name.size
data/lib/serega.rb CHANGED
@@ -16,6 +16,7 @@ end
16
16
  require_relative "serega/errors"
17
17
  require_relative "serega/helpers/serializer_class_helper"
18
18
  require_relative "serega/utils/enum_deep_dup"
19
+ require_relative "serega/utils/symbol_name"
19
20
  require_relative "serega/utils/to_hash"
20
21
  require_relative "serega/json/adapter"
21
22
 
@@ -184,7 +185,7 @@ class Serega
184
185
  # Serializes provided object to Hash
185
186
  #
186
187
  # @param object [Object] Serialized object
187
- # @param opts [Hash] Serializer modifiers and other instantiating options
188
+ # @param opts [Hash, nil] Serializer modifiers and other instantiating options
188
189
  # @option opts [Array, Hash, String, Symbol] :only The only attributes to serialize
189
190
  # @option opts [Array, Hash, String, Symbol] :except Attributes to hide
190
191
  # @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
@@ -194,13 +195,23 @@ class Serega
194
195
  #
195
196
  # @return [Hash] Serialization result
196
197
  #
197
- def call(object, opts = FROZEN_EMPTY_HASH)
198
+ def call(object, opts = nil)
199
+ opts ||= FROZEN_EMPTY_HASH
198
200
  initiate_keys = config.initiate_keys
199
- new(opts.slice(*initiate_keys)).to_h(object, opts.except(*initiate_keys))
201
+
202
+ if opts.empty?
203
+ modifiers_opts = FROZEN_EMPTY_HASH
204
+ serialize_opts = nil
205
+ else
206
+ serialize_opts = opts.except(*initiate_keys)
207
+ modifiers_opts = opts.slice(*initiate_keys)
208
+ end
209
+
210
+ new(modifiers_opts).to_h(object, serialize_opts)
200
211
  end
201
212
 
202
213
  # @see #call
203
- def to_h(object, opts = FROZEN_EMPTY_HASH)
214
+ def to_h(object, opts = nil)
204
215
  call(object, opts)
205
216
  end
206
217
 
@@ -208,7 +219,7 @@ class Serega
208
219
  # Serializes provided object to JSON string
209
220
  #
210
221
  # @param object [Object] Serialized object
211
- # @param opts [Hash] Serializer modifiers and other instantiating options
222
+ # @param opts [Hash, nil] Serializer modifiers and other instantiating options
212
223
  # @option opts [Array, Hash, String, Symbol] :only The only attributes to serialize
213
224
  # @option opts [Array, Hash, String, Symbol] :except Attributes to hide
214
225
  # @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
@@ -218,16 +229,15 @@ class Serega
218
229
  #
219
230
  # @return [String] Serialization result
220
231
  #
221
- def to_json(object, opts = FROZEN_EMPTY_HASH)
222
- initiate_keys = config.initiate_keys
223
- new(opts.slice(*initiate_keys)).to_json(object, opts.except(*initiate_keys))
232
+ def to_json(object, opts = nil)
233
+ config.to_json.call(to_h(object, opts))
224
234
  end
225
235
 
226
236
  #
227
237
  # Serializes provided object as JSON
228
238
  #
229
239
  # @param object [Object] Serialized object
230
- # @param opts [Hash] Serializer modifiers and other instantiating options
240
+ # @param opts [Hash, nil] Serializer modifiers and other instantiating options
231
241
  # @option opts [Array, Hash, String, Symbol] :only The only attributes to serialize
232
242
  # @option opts [Array, Hash, String, Symbol] :except Attributes to hide
233
243
  # @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
@@ -237,7 +247,7 @@ class Serega
237
247
  #
238
248
  # @return [Hash] Serialization result
239
249
  #
240
- def as_json(object, opts = FROZEN_EMPTY_HASH)
250
+ def as_json(object, opts = nil)
241
251
  config.from_json.call(to_json(object, opts))
242
252
  end
243
253
  end
@@ -249,36 +259,37 @@ class Serega
249
259
  #
250
260
  # Instantiates new Serega class
251
261
  #
252
- # @param opts [Hash] Serializer modifiers and other instantiating options
262
+ # @param opts [Hash, nil] Serializer modifiers and other instantiating options
253
263
  # @option opts [Array, Hash, String, Symbol] :only The only attributes to serialize
254
264
  # @option opts [Array, Hash, String, Symbol] :except Attributes to hide
255
265
  # @option opts [Array, Hash, String, Symbol] :with Attributes (usually hidden) to serialize additionally
256
266
  # @option opts [Boolean] :validate Validates provided modifiers (Default is true)
257
267
  #
258
- def initialize(opts = FROZEN_EMPTY_HASH)
259
- @opts = (opts == FROZEN_EMPTY_HASH) ? opts : prepare_modifiers(opts)
260
- self.class::CheckInitiateParams.new(@opts).validate if opts.fetch(:check_initiate_params) { config.check_initiate_params }
268
+ def initialize(opts = nil)
269
+ @opts = (opts.nil? || opts.empty?) ? FROZEN_EMPTY_HASH : prepare_modifiers(opts)
270
+ self.class::CheckInitiateParams.new(@opts).validate if opts&.fetch(:check_initiate_params) { config.check_initiate_params }
261
271
  end
262
272
 
263
273
  #
264
274
  # Serializes provided object to Hash
265
275
  #
266
276
  # @param object [Object] Serialized object
267
- # @param opts [Hash] Serializer modifiers and other instantiating options
277
+ # @param opts [Hash, nil] Serializer modifiers and other instantiating options
268
278
  # @option opts [Hash] :context Serialization context
269
279
  # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
270
280
  #
271
281
  # @return [Hash] Serialization result
272
282
  #
273
- def call(object, opts = {})
274
- self.class::CheckSerializeParams.new(opts).validate
283
+ def call(object, opts = nil)
284
+ self.class::CheckSerializeParams.new(opts).validate if opts&.any?
285
+ opts ||= {}
275
286
  opts[:context] ||= {}
276
287
 
277
288
  serialize(object, opts)
278
289
  end
279
290
 
280
291
  # @see #call
281
- def to_h(object, opts = {})
292
+ def to_h(object, opts = nil)
282
293
  call(object, opts)
283
294
  end
284
295
 
@@ -286,13 +297,13 @@ class Serega
286
297
  # Serializes provided object to JSON string
287
298
  #
288
299
  # @param object [Object] Serialized object
289
- # @param opts [Hash] Serializer modifiers and other instantiating options
300
+ # @param opts [Hash, nil] Serializer modifiers and other instantiating options
290
301
  # @option opts [Hash] :context Serialization context
291
302
  # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
292
303
  #
293
304
  # @return [Hash] Serialization result
294
305
  #
295
- def to_json(object, opts = {})
306
+ def to_json(object, opts = nil)
296
307
  hash = to_h(object, opts)
297
308
  config.to_json.call(hash)
298
309
  end
@@ -301,13 +312,13 @@ class Serega
301
312
  # Serializes provided object as JSON
302
313
  #
303
314
  # @param object [Object] Serialized object
304
- # @param opts [Hash] Serializer modifiers and other instantiating options
315
+ # @param opts [Hash, nil] Serializer modifiers and other instantiating options
305
316
  # @option opts [Hash] :context Serialization context
306
317
  # @option opts [Boolean] :many Set true if provided multiple objects (Default `object.is_a?(Enumerable)`)
307
318
  #
308
319
  # @return [Hash] Serialization result
309
320
  #
310
- def as_json(object, opts = {})
321
+ def as_json(object, opts = nil)
311
322
  json = to_json(object, opts)
312
323
  config.from_json.call(json)
313
324
  end
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.8.2
4
+ version: 0.9.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-12-20 00:00:00.000000000 Z
11
+ date: 2023-03-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  JSON Serializer
@@ -53,7 +53,11 @@ files:
53
53
  - lib/serega/plugins/batch/lib/validations/check_opt_batch.rb
54
54
  - lib/serega/plugins/context_metadata/context_metadata.rb
55
55
  - lib/serega/plugins/formatters/formatters.rb
56
- - lib/serega/plugins/hide_nil/hide_nil.rb
56
+ - lib/serega/plugins/if/if.rb
57
+ - lib/serega/plugins/if/validations/check_opt_if.rb
58
+ - lib/serega/plugins/if/validations/check_opt_if_value.rb
59
+ - lib/serega/plugins/if/validations/check_opt_unless.rb
60
+ - lib/serega/plugins/if/validations/check_opt_unless_value.rb
57
61
  - lib/serega/plugins/metadata/meta_attribute.rb
58
62
  - lib/serega/plugins/metadata/metadata.rb
59
63
  - lib/serega/plugins/metadata/validations/check_block.rb
@@ -73,6 +77,7 @@ files:
73
77
  - lib/serega/plugins/string_modifiers/parse_string_modifiers.rb
74
78
  - lib/serega/plugins/string_modifiers/string_modifiers.rb
75
79
  - lib/serega/utils/enum_deep_dup.rb
80
+ - lib/serega/utils/symbol_name.rb
76
81
  - lib/serega/utils/to_hash.rb
77
82
  - lib/serega/validations/attribute/check_block.rb
78
83
  - lib/serega/validations/attribute/check_name.rb
@@ -114,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
119
  - !ruby/object:Gem::Version
115
120
  version: '0'
116
121
  requirements: []
117
- rubygems_version: 3.3.15
122
+ rubygems_version: 3.4.7
118
123
  signing_key:
119
124
  specification_version: 4
120
125
  summary: JSON Serializer
@@ -1,106 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Serega
4
- module SeregaPlugins
5
- #
6
- # Plugin adds `:hide_nil` option to attributes to delete them from final result
7
- # if value is nil
8
- #
9
- module HideNil
10
- # @return [Symbol] Plugin name
11
- def self.plugin_name
12
- :hide_nil
13
- end
14
-
15
- #
16
- # Applies plugin code to specific serializer
17
- #
18
- # @param serializer_class [Class<Serega>] Current serializer class
19
- # @param _opts [Hash] Loaded plugins options
20
- #
21
- # @return [void]
22
- #
23
- def self.load_plugin(serializer_class, **_opts)
24
- serializer_class::SeregaAttribute.include(AttributeInstanceMethods)
25
- serializer_class::CheckAttributeParams.include(CheckAttributeParamsInstanceMethods)
26
- serializer_class::SeregaObjectSerializer.include(SeregaObjectSerializerInstanceMethods)
27
- end
28
-
29
- #
30
- # Adds config options and runs other callbacks after plugin was loaded
31
- #
32
- # @param serializer_class [Class<Serega>] Current serializer class
33
- # @param opts [Hash] loaded plugins opts
34
- #
35
- # @return [void]
36
- #
37
- def self.after_load_plugin(serializer_class, **opts)
38
- serializer_class.config.attribute_keys << :hide_nil
39
- end
40
-
41
- #
42
- # Serega::SeregaAttribute additional/patched instance methods
43
- #
44
- # @see Serega::SeregaValidations::CheckAttributeParams
45
- #
46
- module AttributeInstanceMethods
47
- # Check hide_nil is specified
48
- def hide_nil?
49
- !!opts[:hide_nil]
50
- end
51
- end
52
-
53
- #
54
- # Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
55
- #
56
- # @see Serega::SeregaValidations::CheckAttributeParams
57
- #
58
- module CheckAttributeParamsInstanceMethods
59
- private
60
-
61
- def check_opts
62
- super
63
- CheckOptHideNil.call(opts)
64
- end
65
- end
66
-
67
- #
68
- # Validator class for :hide_nil attribute option
69
- #
70
- class CheckOptHideNil
71
- #
72
- # Checks attribute :hide_nil option
73
- #
74
- # @param opts [Hash] Attribute options
75
- #
76
- # @raise [Serega::SeregaError] SeregaError that option has invalid value
77
- #
78
- # @return [void]
79
- #
80
- def self.call(opts)
81
- return unless opts.key?(:hide_nil)
82
-
83
- value = opts[:hide_nil]
84
- return if (value == true) || (value == false)
85
-
86
- raise SeregaError, "Invalid option :hide_nil => #{value.inspect}. Must have a boolean value"
87
- end
88
- end
89
-
90
- #
91
- # SeregaObjectSerializer additional/patched class methods
92
- #
93
- # @see Serega::SeregaObjectSerializer
94
- #
95
- module SeregaObjectSerializerInstanceMethods
96
- private
97
-
98
- def attach_final_value(final_value, *)
99
- super unless final_value.nil?
100
- end
101
- end
102
- end
103
-
104
- register_plugin(HideNil.plugin_name, HideNil)
105
- end
106
- end