12
12
#
13
13
# To make a feature conditionally supported, pass a block to the +supports+ method.
14
14
# The block is evaluated in the context of the instance.
15
- # If you call the private method +unsupported_reason_add+ with the feature
16
- # and a reason, then the feature will be unsupported and the reason will be
15
+ # If a feature is not supported, return a string for the reason. A nil means it is supported
16
+ # Alternatively, calling the private method +unsupported_reason_add+ with the feature
17
+ # and a reason, marks the feature as unsupported, and the reason will be
17
18
# accessible through
18
19
#
19
20
# instance.unsupported_reason(:feature)
20
21
#
21
- # The above allows you to call +supports_feature?+ or +supports?(feature) :methods
22
- # on the Class and Instance
23
- #
24
- # Post.supports_publish? # => true
25
22
# Post.supports?(:publish) # => true
26
- # Post.new.supports_publish? # => true
27
- # Post.supports_fake? # => false
28
- # Post.supports_archive? # => true
29
- # Post.new(featured: true).supports_archive? # => false
23
+ # Post.new.supports?(:publish) # => true
24
+ # Post.supports?(:archive) # => true
25
+ # Post.new(featured: true).supports?(:archive) # => false
30
26
#
31
27
# To get a reason why a feature is unsupported use the +unsupported_reason+ method
32
28
#
51
47
module SupportsFeatureMixin
52
48
extend ActiveSupport ::Concern
53
49
54
- COMMON_FEATURES = %i[ create delete destroy refresh_ems update ] . freeze
55
-
56
50
# Whenever this mixin is included we define all features as unsupported by default.
57
51
# This way we can query for every feature
58
52
included do
59
53
private_class_method :unsupported
60
54
private_class_method :unsupported_reason_add
61
- private_class_method :define_supports_feature_methods
55
+ class_attribute :supports_features , :instance_writer => false , :default => { }
62
56
end
63
57
64
- def self . reason_or_default ( reason )
65
- ( reason . presence || _ ( "Feature not available/supported" ) )
58
+ def self . default_supports_reason
59
+ _ ( "Feature not available/supported" )
66
60
end
67
61
68
62
# query instance for the reason why the feature is unsupported
@@ -74,22 +68,16 @@ def unsupported_reason(feature)
74
68
75
69
# query the instance if the feature is supported or not
76
70
def supports? ( feature )
77
- method_name = "supports_#{ feature } ?"
78
- if respond_to? ( method_name )
79
- public_send ( method_name )
80
- else
81
- unsupported_reason_add ( feature )
82
- false
83
- end
71
+ self . class . check_supports ( feature . to_sym , :instance => self )
84
72
end
85
73
86
74
private
87
75
88
76
# used inside a +supports+ block to add a reason why the feature is not supported
89
77
# just adding a reason will make the feature unsupported
90
- def unsupported_reason_add ( feature , reason = nil )
78
+ def unsupported_reason_add ( feature , reason )
91
79
feature = feature . to_sym
92
- unsupported [ feature ] = SupportsFeatureMixin . reason_or_default ( reason )
80
+ unsupported [ feature ] = reason
93
81
end
94
82
95
83
def unsupported
@@ -99,24 +87,43 @@ def unsupported
99
87
class_methods do
100
88
# This is the DSL used a class level to define what is supported
101
89
def supports ( feature , &block )
102
- define_supports_feature_methods ( feature , & block )
90
+ self . supports_features = supports_features . merge ( feature . to_sym => block || true )
103
91
end
104
92
105
93
# supports_not does not take a block, because its never supported
106
94
# and not conditionally supported
107
95
def supports_not ( feature , reason : nil )
108
- define_supports_feature_methods ( feature , :is_supported => false , : reason => reason )
96
+ self . supports_features = supports_features . merge ( feature . to_sym => reason . presence || false )
109
97
end
110
98
111
99
# query the class if the feature is supported or not
112
100
def supports? ( feature )
113
- method_name = "supports_#{ feature } ?"
114
- if respond_to? ( method_name )
115
- public_send ( method_name )
116
- else
117
- unsupported_reason_add ( feature )
118
- false
101
+ check_supports ( feature . to_sym , :instance => self )
102
+ end
103
+
104
+ def check_supports ( feature , instance :)
105
+ instance . send ( :unsupported ) . delete ( feature )
106
+
107
+ # undeclared features are not supported
108
+ value = supports_features [ feature . to_sym ]
109
+
110
+ if value . respond_to? ( :call )
111
+ begin
112
+ # for class level supports, blocks are not evaluated and assumed to be true
113
+ result = instance . instance_eval ( &value ) unless instance . kind_of? ( Class )
114
+ # if no errors yet but result was an error message
115
+ # then add the error
116
+ if !instance . send ( :unsupported ) . key? ( feature ) && result . kind_of? ( String )
117
+ instance . send ( :unsupported_reason_add , feature , result )
118
+ end
119
+ rescue => e
120
+ _log . log_backtrace ( e )
121
+ instance . send ( :unsupported_reason_add , feature , "Internal Error: #{ e . message } " )
122
+ end
123
+ elsif value != true
124
+ instance . send ( :unsupported_reason_add , feature , value || SupportsFeatureMixin . default_supports_reason )
119
125
end
126
+ !instance . send ( :unsupported ) . key? ( feature )
120
127
end
121
128
122
129
# all subclasses that are considered for supporting features
@@ -166,46 +173,8 @@ def unsupported
166
173
end
167
174
168
175
# use this for making a class not support a feature
169
- def unsupported_reason_add ( feature , reason = nil )
170
- feature = feature . to_sym
171
- unsupported [ feature ] = SupportsFeatureMixin . reason_or_default ( reason )
172
- end
173
-
174
- def define_supports_feature_methods ( feature , is_supported : true , reason : nil , &block )
175
- method_name = "supports_#{ feature } ?"
176
- feature = feature . to_sym
177
-
178
- # silence potential redefinition warnings
179
- silence_warnings do
180
- # defines the method on the instance
181
- define_method ( method_name ) do
182
- unsupported . delete ( feature )
183
- if block
184
- begin
185
- result = instance_eval ( &block )
186
- # if no errors yet but result was an error message
187
- # then add the error
188
- if !unsupported . key? ( feature ) && result . kind_of? ( String )
189
- unsupported_reason_add ( feature , result )
190
- end
191
- rescue => e
192
- _log . log_backtrace ( e )
193
- unsupported_reason_add ( feature , "Internal Error: #{ e . message } " )
194
- end
195
- else
196
- unsupported_reason_add ( feature , reason ) unless is_supported
197
- end
198
- !unsupported . key? ( feature )
199
- end
200
-
201
- # defines the method on the class
202
- define_singleton_method ( method_name ) do
203
- unsupported . delete ( feature )
204
- # TODO: durandom - make reason evaluate in class context, to e.g. include the name of a subclass (.to_proc?)
205
- unsupported_reason_add ( feature , reason ) unless is_supported
206
- !unsupported . key? ( feature )
207
- end
208
- end
176
+ def unsupported_reason_add ( feature , reason )
177
+ unsupported [ feature . to_sym ] = reason
209
178
end
210
179
end
211
180
end
0 commit comments