serega 0.8.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 116b00d5c709f65a90899a9eda49f6e2c95cc9a47781a1cddf2499057553f427
4
- data.tar.gz: 5f8fe19470218be68c09704964228be0381e677f5bd3cbcf9688f92974e9392d
3
+ metadata.gz: 6a94d74dd45c74e499cd1a3abec082d8776019538b67bf24fc840512271adb6b
4
+ data.tar.gz: a22bc547e6ac94b0988e2cfef2af31ad659beb53058a90159e7f46f6470c3390
5
5
  SHA512:
6
- metadata.gz: 047de5335017cf65ee0f887124f81a755b94e19eea0e53a56ae24cb81698e7bdcc7e8eb6eba9256b5160a24bb011a5c0a1c1628cca77c7e7951b8883629ad96f
7
- data.tar.gz: 5836df28c6e1cf2238d8c8471280dc806dd41a0cbbe474a101118d801e460830cda97dcb419e0885f9106d1e4e5ae795776ff5cc475555bdab6cb4c5408c1c94
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
- # 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
@@ -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 :hide_nil
623
+ ### Plugin :if
604
624
 
605
- Allows to hide attributes with `nil` values
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
- ```ruby
608
- class UserSerializer < Serega
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
- attribute :email, hide_nil: true
612
- end
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.8.3
1
+ 0.10.0
@@ -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 that will be used to find attribute value
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
- attach_value(object, point, container)
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
- 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
270
- else
271
- super
272
- end
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
@@ -68,6 +68,9 @@ class Serega
68
68
  end
69
69
  end
70
70
 
71
+ # Patched in:
72
+ # - plugin batch (extension :activerecord_preloads - preloads data to found values)
73
+ # - plugin batch (extension :formatters - formats values)
71
74
  def keys_values
72
75
  ids = keys.keys
73
76
 
@@ -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