Skip to content

Commit dfbf8e7

Browse files
authored
Merge pull request #22883 from kbrock/supports_array
Drop supports_feature? methods
2 parents 6330e2c + 181a03e commit dfbf8e7

File tree

5 files changed

+75
-95
lines changed

5 files changed

+75
-95
lines changed

app/models/host.rb

+4-5
Original file line numberDiff line numberDiff line change
@@ -1432,11 +1432,10 @@ def self.display_name(number = 1)
14321432
end
14331433

14341434
def verbose_supports?(feature, description = nil)
1435-
supports?(feature).tap do |value|
1436-
unless value
1437-
description ||= feature.to_s.humanize(:capitalize => false)
1438-
_log.warn("Cannot #{description} because <#{unsupported_reason(feature)}>")
1439-
end
1435+
if (reason = unsupported_reason(feature))
1436+
description ||= feature.to_s.humanize(:capitalize => false)
1437+
_log.warn("Cannot #{description} because <#{reason}>")
14401438
end
1439+
!reason
14411440
end
14421441
end

app/models/mixins/supports_feature_mixin.rb

+41-72
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,17 @@
1212
#
1313
# To make a feature conditionally supported, pass a block to the +supports+ method.
1414
# 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
1718
# accessible through
1819
#
1920
# instance.unsupported_reason(:feature)
2021
#
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
2522
# 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
3026
#
3127
# To get a reason why a feature is unsupported use the +unsupported_reason+ method
3228
#
@@ -51,18 +47,16 @@
5147
module SupportsFeatureMixin
5248
extend ActiveSupport::Concern
5349

54-
COMMON_FEATURES = %i[create delete destroy refresh_ems update].freeze
55-
5650
# Whenever this mixin is included we define all features as unsupported by default.
5751
# This way we can query for every feature
5852
included do
5953
private_class_method :unsupported
6054
private_class_method :unsupported_reason_add
61-
private_class_method :define_supports_feature_methods
55+
class_attribute :supports_features, :instance_writer => false, :default => {}
6256
end
6357

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")
6660
end
6761

6862
# query instance for the reason why the feature is unsupported
@@ -74,22 +68,16 @@ def unsupported_reason(feature)
7468

7569
# query the instance if the feature is supported or not
7670
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)
8472
end
8573

8674
private
8775

8876
# used inside a +supports+ block to add a reason why the feature is not supported
8977
# just adding a reason will make the feature unsupported
90-
def unsupported_reason_add(feature, reason = nil)
78+
def unsupported_reason_add(feature, reason)
9179
feature = feature.to_sym
92-
unsupported[feature] = SupportsFeatureMixin.reason_or_default(reason)
80+
unsupported[feature] = reason
9381
end
9482

9583
def unsupported
@@ -99,24 +87,43 @@ def unsupported
9987
class_methods do
10088
# This is the DSL used a class level to define what is supported
10189
def supports(feature, &block)
102-
define_supports_feature_methods(feature, &block)
90+
self.supports_features = supports_features.merge(feature.to_sym => block || true)
10391
end
10492

10593
# supports_not does not take a block, because its never supported
10694
# and not conditionally supported
10795
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)
10997
end
11098

11199
# query the class if the feature is supported or not
112100
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)
119125
end
126+
!instance.send(:unsupported).key?(feature)
120127
end
121128

122129
# all subclasses that are considered for supporting features
@@ -166,46 +173,8 @@ def unsupported
166173
end
167174

168175
# 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
209178
end
210179
end
211180
end

app/models/vm/operations.rb

+2
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ module Vm::Operations
2626
supports :launch_vmrc_console do
2727
begin
2828
validate_remote_console_vmrc_support
29+
nil
2930
rescue => err
3031
_('VM VMRC Console error: %{error}') % {:error => err}
3132
end
3233
end
3334

3435
supports :launch_native_console do
3536
validate_native_console_support
37+
nil
3638
rescue StandardError => err
3739
_('VM NATIVE Console error: %{error}') % {:error => err}
3840
end

spec/models/mixins/supports_feature_mixin_spec.rb

+27-17
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,6 @@ def initialize(values = {})
3333

3434
let(:test_inst) { test_class.new }
3535

36-
describe "#supports_feature?" do
37-
it "defines supports on the instance" do
38-
expect(test_inst.supports_std_accept?).to be_truthy
39-
expect(test_inst.supports_module_accept?).to be_truthy
40-
expect(test_inst.supports_std_denial?).to be_falsey
41-
end
42-
end
43-
44-
describe ".supports_feature?" do
45-
it "defines supports on the class" do
46-
expect(test_class.supports_std_accept?).to be_truthy
47-
expect(test_class.supports_module_accept?).to be_truthy
48-
expect(test_class.supports_std_denial?).to be_falsey
49-
end
50-
end
51-
5236
describe ".supports?" do
5337
it "handles base supports" do
5438
expect(test_class.supports?(:std_accept)).to be_truthy
@@ -231,11 +215,37 @@ def initialize(values = {})
231215
end
232216

233217
it "gives reason when implicit dynamic attrs" do
234-
test_class.supports(:implicit_feature) { "dynamically unsupported" unless attr1 }
218+
test_class.supports(:implicit_feature) { "dynamically unsupported" unless attr1 }
235219
test_inst = test_class.new(:attr1 => false)
236220

237221
expect(test_inst.unsupported_reason(:implicit_feature)).to eq("dynamically unsupported")
238222
end
223+
224+
it "gives reason when chained to a denail with a default reason" do
225+
test_class.supports_not :denial_no_reason
226+
test_class.supports(:denial_chained) { unsupported_reason(:denial_no_reason) }
227+
228+
expect(test_inst.unsupported_reason(:denial_chained)).to eq("Feature not available/supported")
229+
end
230+
231+
it "gives reason when chained to a denail with a default reason (checking supported)" do
232+
test_class.supports_not :denial_no_reason
233+
test_class.supports(:denial_chained) { unsupported_reason(:denial_no_reason) unless supports?(:denial_no_reason) }
234+
235+
expect(test_inst.unsupported_reason(:denial_chained)).to eq("Feature not available/supported")
236+
end
237+
238+
it "gives no reason when chained to an attribute with success" do
239+
test_class.supports(:std_chained) { unsupported_reason(:std_accept) }
240+
241+
expect(test_inst.unsupported_reason(:std_chained)).to eq(nil)
242+
end
243+
244+
it "gives no reason when chained to an attribute with success (checking supported)" do
245+
test_class.supports(:std_chained) { unsupported_reason(:std_accept) unless supports?(:std_accept) }
246+
247+
expect(test_inst.unsupported_reason(:std_chained)).to eq(nil)
248+
end
239249
end
240250

241251
describe ".subclasses_supporting" do

spec/support/supports_helper.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def stub_supports_not(model, feature = :update, reason = nil)
2525

2626
stub_supports(model, feature, :supported => false)
2727

28-
reason ||= SupportsFeatureMixin.reason_or_default(reason)
28+
reason ||= SupportsFeatureMixin.default_supports_reason
2929
receive_reason = receive(:unsupported_reason).with(feature).and_return(reason)
3030
allow(model).to(receive_reason)
3131
allow_any_instance_of(model).to(receive_reason)

0 commit comments

Comments
 (0)