First, you need to understand how ABC is calculated. Nested conditionals do not affect ABC. RuboCop's warning output shows the calculation results:
Assignment Branch Condition size for call is too high. [<5, 28, 4> 28.72/17]
The <5, 28, 4>
is your <Assignments, Branches, Conditionals>
, as described in this article.
The overall score is calculated like this: sqrt(5^2 + 28^2 + 4^2) = 28.72
The default maximum score for the cop is 17.
I've annotated your code below with the ABC scores for each line. Take note that every time you reference context
, you add a B-point. This is because context
is not a local variable to call
, so the ABC metric assumes it's a method call every time.
def call
user = Learner.find_by(id: context.user_id) # <1, 3, 0>
if user.web_push_subscription # <0, 1, 1>
context.subscription = user.web_push_subscription # <1, 3, 0>
else # <0, 0, 1>
# this constructor call is the most expensive part
subscription = WebPushSubscription.new( # <1, 1, 0>
endpoint: context.push_params[:endpoint], # <0, 3, 0>
auth_key: context.push_params[:keys][:auth], # <0, 4, 0>
p256dh_key: context.push_params[:keys][:p256dh], # <0, 4, 0>
learner: user # <0, 0, 0>
)
if subscription.save # <0, 1, 1>
context.subscription = subscription # <1, 2, 0>
else # <0, 0, 1>
context.error = subscription.errors.full_messages # <1, 4, 0>
context.fail! # <0, 2, 0>
end
end
end
if you set the cop option: CountRepeatedAttributes: false (which I recommend), it will bring your score down to 19.1
.
In lieu of that, you can bring your score down by extracting the creation of WebPushSubscription
into its own method like so:
def call
user = Learner.find_by(id: context.user_id)
if user.web_push_subscription
context.subscription = user.web_push_subscription
else
create_subscription(user)
end
end
private
def create_subscription(user)
push_params = context.push_params
subscription = WebPushSubscription.new(
endpoint: push_params[:endpoint],
auth_key: push_params.dig(:key, :auth),
p256dh_key: push_params.dig(:key, :p256dh),
learner: user
)
if subscription.save
context.subscription = subscription
else
context.error = subscription.errors.full_messages
context.fail!
end
end
This will split the score up between the two methods. Note some additional ABC-saving strategies in create_subscription
, like assigning push_params
to a variable, and using dig
for the nested hash accessors. The final score for create_subscription
is between 12 and 16 depending on the cop options you use, and call
is between 6 and 8.
Generally, all that's required to bring down ABC score is to refactor to smaller methods.