serega 0.8.3 → 0.10.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 +4 -4
- data/README.md +60 -10
- data/VERSION +1 -1
- data/lib/serega/attribute.rb +9 -1
- data/lib/serega/config.rb +16 -0
- data/lib/serega/object_serializer.rb +13 -1
- data/lib/serega/plugins/batch/batch.rb +8 -7
- data/lib/serega/plugins/batch/lib/loader.rb +3 -0
- data/lib/serega/plugins/if/if.rb +168 -0
- data/lib/serega/plugins/if/validations/check_opt_if.rb +79 -0
- data/lib/serega/plugins/if/validations/check_opt_if_value.rb +84 -0
- data/lib/serega/plugins/if/validations/check_opt_unless.rb +88 -0
- data/lib/serega/plugins/if/validations/check_opt_unless_value.rb +84 -0
- data/lib/serega/plugins/metadata/meta_attribute.rb +10 -2
- data/lib/serega/plugins/metadata/metadata.rb +15 -13
- data/lib/serega/plugins/metadata/validations/check_path.rb +10 -17
- data/lib/serega/plugins/preloads/preloads.rb +2 -0
- data/lib/serega/plugins/presenter/presenter.rb +10 -8
- data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +46 -57
- data/lib/serega/plugins/string_modifiers/string_modifiers.rb +3 -7
- data/lib/serega/utils/enum_deep_dup.rb +2 -2
- data/lib/serega/validations/attribute/check_name.rb +8 -21
- data/lib/serega/validations/check_attribute_params.rb +10 -1
- data/lib/serega.rb +72 -48
- metadata +8 -4
- data/lib/serega/plugins/hide_nil/hide_nil.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a94d74dd45c74e499cd1a3abec082d8776019538b67bf24fc840512271adb6b
|
4
|
+
data.tar.gz: a22bc547e6ac94b0988e2cfef2af31ad659beb53058a90159e7f46f6470c3390
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d89c6180d4af0273eee6dd6179a0ac6f0157409f07ae6c18985941ecd8971bd17eb8b547a764d97afd12cdc08e565cb8749e9d9e0a70d8ab8add5dbad3e349db
|
7
|
+
data.tar.gz: 4cd808491f38a469ac705f6884007a3d71cead7f12d8e9730ec8c15ee1da97cb2d0d7503aafc0925a5bf41c7b660f7aa71a62da6fda295e713b62114ee83842a
|
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
|
-
#
|
98
|
-
#
|
99
|
-
|
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
|
@@ -108,6 +111,23 @@ class UserSerializer < Serega
|
|
108
111
|
end
|
109
112
|
```
|
110
113
|
|
114
|
+
---
|
115
|
+
|
116
|
+
⚠️ Attribute names are checked to include only "a-z", "A-Z", "0-9", "_", "-", "~" characters.
|
117
|
+
We have this check as:
|
118
|
+
- Attributes names can be used in URL without encoding.
|
119
|
+
- Plugin [string_modifiers][string_modifiers] already uses "," and "()" as attribute names delimeters.
|
120
|
+
- We are protected from errors when added some non-english character looking as english.
|
121
|
+
|
122
|
+
This names check can be disabled globally or per-serializer via:
|
123
|
+
```ruby
|
124
|
+
Serega.config.check_attribute_name = false
|
125
|
+
|
126
|
+
class SomeSerializer < Serega
|
127
|
+
config.check_attribute_name = false
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
111
131
|
### Serializing
|
112
132
|
|
113
133
|
We can serialize objects using class methods `.to_h`, `.to_json`, `.as_json` and same instance methods `#to_h`, `#to_json`, `#as_json`.
|
@@ -600,16 +620,45 @@ PostSerializer.new(with: "user(email)").to_h(post)
|
|
600
620
|
PostSerializer.new(with: {user: %i[email, username]}).to_h(post)
|
601
621
|
```
|
602
622
|
|
603
|
-
### Plugin :
|
623
|
+
### Plugin :if
|
604
624
|
|
605
|
-
|
625
|
+
Plugin adds `:if`, `:unless`, `:if_value`, `:unless_value` options to
|
626
|
+
attributes so we can remove attributes from response in various ways.
|
606
627
|
|
607
|
-
|
608
|
-
|
609
|
-
plugin :hide_nil
|
628
|
+
Use `:if` and `:unless` when you want to hide attributes before finding attribute value,
|
629
|
+
and use `:if_value` and `:unless_value` to hide attributes after we find final value.
|
610
630
|
|
611
|
-
|
612
|
-
|
631
|
+
Options `:if` and `:unless` accept currently serialized object and context as parameters.
|
632
|
+
Options `:if_value` and `:unless_value` accept already found serialized value and context as parameters.
|
633
|
+
|
634
|
+
Options `:if_value` and `:unless_value` cannot be used with :serializer option, as
|
635
|
+
serialized objects have no "serialized value". Use `:if` and `:unless` in this case.
|
636
|
+
|
637
|
+
See also a `:hide` option that is available without any plugins to hide
|
638
|
+
attribute without conditions. Look at [select serialized fields](#selecting-fields) for `:hide` usage examples.
|
639
|
+
|
640
|
+
```ruby
|
641
|
+
class UserSerializer < Serega
|
642
|
+
attribute :email, if: :active? # if user.active?
|
643
|
+
attribute :email, if: proc {|user| user.active?} # same
|
644
|
+
attribute :email, if: proc {|user, ctx| user == ctx[:current_user]} # using context
|
645
|
+
attribute :email, if: CustomPolicy.method(:view_email?) # You can provide own callable object
|
646
|
+
|
647
|
+
attribute :email, unless: :hidden? # unless user.hidden?
|
648
|
+
attribute :email, unless: proc {|user| user.hidden?} # same
|
649
|
+
attribute :email, unless: proc {|user, context| context[:show_emails]} # using context
|
650
|
+
attribute :email, unless: CustomPolicy.method(:hide_email?) # You can provide own callable object
|
651
|
+
|
652
|
+
attribute :email, if_value: :present? # if email.present?
|
653
|
+
attribute :email, if_value: proc {|email| email.present?} # same
|
654
|
+
attribute :email, if_value: proc {|email, ctx| ctx[:show_emails]} # using context
|
655
|
+
attribute :email, if_value: CustomPolicy.method(:view_email?) # You can provide own callable object
|
656
|
+
|
657
|
+
attribute :email, unless_value: :blank? # unless email.blank?
|
658
|
+
attribute :email, unless_value: proc {|email| email.blank?} # same
|
659
|
+
attribute :email, unless_value: proc {|email, context| context[:show_emails]} # using context
|
660
|
+
attribute :email, unless_value: CustomPolicy.method(:hide_email?) # You can provide own callable object
|
661
|
+
end
|
613
662
|
```
|
614
663
|
|
615
664
|
## Errors
|
@@ -648,3 +697,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
648
697
|
[presenter]: #plugin-presenter
|
649
698
|
[root]: #plugin-root
|
650
699
|
[string_modifiers]: #plugin-string_modifiers
|
700
|
+
[if]: #plugin-if
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.0
|
data/lib/serega/attribute.rb
CHANGED
@@ -51,6 +51,10 @@ class Serega
|
|
51
51
|
end
|
52
52
|
|
53
53
|
# Shows current opts[:hide] option
|
54
|
+
#
|
55
|
+
# Patched in:
|
56
|
+
# - plugin :preloads (returns true by default if config option auto_hide_attribute_with_preloads is enabled)
|
57
|
+
#
|
54
58
|
# @return [Boolean, nil] Attribute :hide option value
|
55
59
|
def hide
|
56
60
|
opts[:hide]
|
@@ -82,7 +86,11 @@ class Serega
|
|
82
86
|
end
|
83
87
|
end
|
84
88
|
|
85
|
-
# Returns final block
|
89
|
+
# Returns final block used to find attribute value
|
90
|
+
#
|
91
|
+
# Patched in:
|
92
|
+
# - plugin :formatters (wraps resulted block in formatter block and formats :const values)
|
93
|
+
#
|
86
94
|
# @return [Proc] Proc to find attribute value
|
87
95
|
def value_block
|
88
96
|
return @value_block if instance_variable_defined?(:@value_block)
|
data/lib/serega/config.rb
CHANGED
@@ -17,6 +17,7 @@ class Serega
|
|
17
17
|
initiate_keys: %i[only with except check_initiate_params].freeze,
|
18
18
|
attribute_keys: %i[key value serializer many hide const delegate].freeze,
|
19
19
|
serialize_keys: %i[context many].freeze,
|
20
|
+
check_attribute_name: true,
|
20
21
|
check_initiate_params: true,
|
21
22
|
max_cached_map_per_serializer_count: 0,
|
22
23
|
to_json: (SeregaJSON.adapter == :oj) ? SeregaJSON::OjDump : SeregaJSON::JSONDump,
|
@@ -102,6 +103,21 @@ class Serega
|
|
102
103
|
opts[:max_cached_map_per_serializer_count] = value
|
103
104
|
end
|
104
105
|
|
106
|
+
# Returns whether attributes names check is disabled
|
107
|
+
def check_attribute_name
|
108
|
+
opts.fetch(:check_attribute_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sets :check_attribute_name config option
|
112
|
+
#
|
113
|
+
# @param value [Boolean] Set :check_attribute_name config option
|
114
|
+
#
|
115
|
+
# @return [Boolean] New :check_attribute_name config option
|
116
|
+
def check_attribute_name=(value)
|
117
|
+
raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
|
118
|
+
opts[:check_attribute_name] = value
|
119
|
+
end
|
120
|
+
|
105
121
|
# Returns current `to_json` adapter
|
106
122
|
# @return [#call] Callable that used to construct JSON
|
107
123
|
def to_json
|
@@ -41,18 +41,30 @@ class Serega
|
|
41
41
|
object.map { |obj| serialize_object(obj) }
|
42
42
|
end
|
43
43
|
|
44
|
+
# Patched in:
|
45
|
+
# - plugin :presenter (makes presenter_object and serializes it)
|
44
46
|
def serialize_object(object)
|
45
47
|
points.each_with_object({}) do |point, container|
|
46
|
-
|
48
|
+
serialize_point(object, point, container)
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
52
|
+
# Patched in:
|
53
|
+
# - plugin :if (conditionally skips serializing this point)
|
54
|
+
def serialize_point(object, point, container)
|
55
|
+
attach_value(object, point, container)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Patched in:
|
59
|
+
# - plugin :batch (remembers key for batch loading values instead of attaching)
|
50
60
|
def attach_value(object, point, container)
|
51
61
|
value = point.value(object, context)
|
52
62
|
final_value = final_value(value, point)
|
53
63
|
attach_final_value(final_value, point, container)
|
54
64
|
end
|
55
65
|
|
66
|
+
# Patched in:
|
67
|
+
# - plugin :if (conditionally skips attaching)
|
56
68
|
def attach_final_value(final_value, point, container)
|
57
69
|
container[point.name] = final_value
|
58
70
|
end
|
@@ -262,14 +262,15 @@ class Serega
|
|
262
262
|
|
263
263
|
def attach_value(object, point, container)
|
264
264
|
batch = point.batch
|
265
|
+
return super unless batch
|
265
266
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
267
|
+
remember_key_for_batch_loading(batch, object, point, container)
|
268
|
+
end
|
269
|
+
|
270
|
+
def remember_key_for_batch_loading(batch, object, point, container)
|
271
|
+
key = batch.key.call(object, context)
|
272
|
+
opts[:batch_loaders].get(point, self).remember(key, container)
|
273
|
+
container[point.name] = nil # Reserve attribute place in resulted hash. We will set correct value later
|
273
274
|
end
|
274
275
|
end
|
275
276
|
end
|
@@ -0,0 +1,168 @@
|
|
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
|
+
#
|
70
|
+
# Adds config options and runs other callbacks after plugin was loaded
|
71
|
+
#
|
72
|
+
# @param serializer_class [Class<Serega>] Current serializer class
|
73
|
+
# @param opts [Hash] loaded plugins opts
|
74
|
+
#
|
75
|
+
# @return [void]
|
76
|
+
#
|
77
|
+
def self.after_load_plugin(serializer_class, **opts)
|
78
|
+
serializer_class.config.attribute_keys << :if << :if_value << :unless << :unless_value
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Serega::SeregaMapPoint additional/patched instance methods
|
83
|
+
#
|
84
|
+
# @see Serega::SeregaMapPoint::InstanceMethods
|
85
|
+
#
|
86
|
+
module MapPointInstanceMethods
|
87
|
+
#
|
88
|
+
# @return [Boolean] Should we show attribute or not
|
89
|
+
# Conditions for this checks are specified by :if and :unless attribute options.
|
90
|
+
#
|
91
|
+
def satisfy_if_conditions?(obj, ctx)
|
92
|
+
check_if_unless(obj, ctx, :if, :unless)
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# @return [Boolean] Should we show attribute with specific value or not.
|
97
|
+
# Conditions for this checks are specified by :if_value and :unless_value attribute options.
|
98
|
+
#
|
99
|
+
def satisfy_if_value_conditions?(value, ctx)
|
100
|
+
check_if_unless(value, ctx, :if_value, :unless_value)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def check_if_unless(obj, ctx, opt_if_name, opt_unless_name)
|
106
|
+
opt_if = attribute.opts[opt_if_name]
|
107
|
+
opt_unless = attribute.opts[opt_unless_name]
|
108
|
+
return true if opt_if.nil? && opt_unless.nil?
|
109
|
+
|
110
|
+
res_if =
|
111
|
+
case opt_if
|
112
|
+
when NilClass then true
|
113
|
+
when Symbol then obj.public_send(opt_if)
|
114
|
+
else opt_if.call(obj, ctx)
|
115
|
+
end
|
116
|
+
|
117
|
+
res_unless =
|
118
|
+
case opt_unless
|
119
|
+
when NilClass then true
|
120
|
+
when Symbol then !obj.public_send(opt_unless)
|
121
|
+
else !opt_unless.call(obj, ctx)
|
122
|
+
end
|
123
|
+
|
124
|
+
res_if && res_unless
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Serega::SeregaValidations::CheckAttributeParams additional/patched class methods
|
130
|
+
#
|
131
|
+
# @see Serega::SeregaValidations::CheckAttributeParams
|
132
|
+
#
|
133
|
+
module CheckAttributeParamsInstanceMethods
|
134
|
+
private
|
135
|
+
|
136
|
+
def check_opts
|
137
|
+
super
|
138
|
+
|
139
|
+
CheckOptIf.call(opts)
|
140
|
+
CheckOptUnless.call(opts)
|
141
|
+
CheckOptIfValue.call(opts)
|
142
|
+
CheckOptUnlessValue.call(opts)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# SeregaObjectSerializer additional/patched class methods
|
148
|
+
#
|
149
|
+
# @see Serega::SeregaObjectSerializer
|
150
|
+
#
|
151
|
+
module SeregaObjectSerializerInstanceMethods
|
152
|
+
private
|
153
|
+
|
154
|
+
def serialize_point(object, point, _container)
|
155
|
+
return unless point.satisfy_if_conditions?(object, context)
|
156
|
+
super
|
157
|
+
end
|
158
|
+
|
159
|
+
def attach_final_value(value, point, _container)
|
160
|
+
return unless point.satisfy_if_value_conditions?(value, context)
|
161
|
+
super
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
register_plugin(If.plugin_name, If)
|
167
|
+
end
|
168
|
+
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
|