So I'm trying to take the right path here and understand how to solve this cop, this looks like a small piece of code IMO, why is it complaining?
moving the nested if-else doesn't change anything, any suggestions on how to solve this cop?
class WebPush::Register
include Interactor
# rubocop:disable Metrics/AbcSize
def call
user = Learner.find_by(id: context.user_id)
# return if existing
if user.web_push_subscription
context.subscription = user.web_push_subscription
else
subscription = WebPushSubscription.new(
endpoint: context.push_params[:endpoint],
auth_key: context.push_params[:keys][:auth],
p256dh_key: context.push_params[:keys][:p256dh],
learner: user
)
if subscription.save
context.subscription = subscription
else
context.error = subscription.errors.full_messages
context.fail!
end
end
end
# rubocop:enable Metrics/AbcSize
end
First, you need to understand how ABC is calculated. Nested conditionals do not affect ABC. RuboCop's warning output shows you 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. 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.
ABC stands for Assignment, Branch, Condition . ABC = sqrt(A^2 + B^2 + C^2)
Let's count them.
def call
user = Learner.find_by(id: context.user_id) // assignment, branches
# return if existing
if user.web_push_subscription // branch, condition
context.subscription = user.web_push_subscription // assignment, branches
else // condition
subscription = WebPushSubscription.new( // assignment, branch
endpoint: context.push_params[:endpoint], // branches
auth_key: context.push_params[:keys][:auth], // branches
p256dh_key: context.push_params[:keys][:p256dh], // branches
learner: user
)
if subscription.save // branch, condition
context.subscription = subscription // assignment
else // condition
context.error = subscription.errors.full_messages // assignment, branches
context.fail! // branch
end
end
end
This gives 18.2. By default, 17 is the maximum Rubcop considers satisfactory.
We can handle this with a few simple extract methods.
private def user
@user ||= Learner.find_by(id: context.user_id)
end
private def create_web_push_subscription_from_context
subscription = WebPushSubscription.new(
endpoint: context.push_params[:endpoint],
auth_key: context.push_params[:keys][:auth],
p256dh_key: context.push_params[:keys][:p256dh],
learner: user
)
if subscription.save
context.subscription = subscription
else
context.error = subscription.errors.full_messages
context.fail!
end
end
def call
if user.web_push_subscription
context.subscription = user.web_push_subscription
else
create_web_push_subscription_from_context
end
end
After this refactoring the code is simpler, each method does one thing. That's what the ABC metric encourages.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.