Cyclomatic complexity for some_method is too high - ruby

I have following method in controller. When i run lint i get the error
Cyclomatic complexity for some_method is too high
I checked online about it and seems like it is the way i wrote the method. How could i rewrite this method so i dont get the lint error?
def customer_order
if params[:customer_id].present? && !params[:order_id].present?
render_error :not_found, 'No info found for given customer id' \
unless #info.customer_id == params[:customer_id]
elsif params[:order_id].present? && !params[:customer_id].present?
render_error :not_found, 'No info found for given order id' \
unless #info.order_id == params[:order_id]
elsif params[:customer_id].present? && params[:order_id].present?
render_error :not_found, 'No info found for given customer id and order id’ \
unless #info.customer_id == params[:customer_id] &&
#info.order_id == params[:order_id]
end
end

Yikes! It is too high!
This linting message essentially means you have so many if statements that it's hard for a developer to keep straight.
I would suggest refactoring the contents of each if statement into it's own method.
def customer_order
if params[:customer_id].present? && !params[:order_id].present?
no_order_error
elsif params[:order_id].present? && !params[:customer_id].present?
no_info_error
...
end
def no_order_error
return if #info.customer_id == params[:customer_id]
render_error :not_found, 'No info found for given customer id'
end
def no_info_error
...
end

One approach would be to make each of the conditionals their own method. As an example:
def customer_without_order(params)
params[:customer_id].present? && !params[:order_id].present?
end
You may find that the conditionals that determine when you render an error warrant this treatment as well.

You could reduce some of the redundancy like so.
def customer_order
msg = case [params[:customer_id].present?, params[:order_id].present?]
when [true, false]
#info.customer_id == params[:customer_id] ?
nil : 'No info found for given customer id'
when [false, true]
#info.order_id == params[:order_id] ?
nil : 'No info found for given order id'
when [true, true]
#info.customer_id == params[:customer_id] && #info.order_id == params[:order_id] ?
nil : 'No info found for given customer id and order id’
else
nil
end
render_error(:not_found, msg) if msg
end

Related

Code not actually asserting in RSpec?

I'm new to Ruby and in various open source software I've noticed a number of "statements" in some RSpec descriptions that appear not to accomplish what they intended, like they wanted to make an assertion, but didn't. Are these coding errors or is there some RSpec or Ruby magic I'm missing? (Likelihood of weirdly overloaded operators?)
The examples, with #??? added to the suspect lines:
(rubinius/spec/ruby/core/array/permutation_spec.rb)
it "returns no permutations when the given length has no permutations" do
#numbers.permutation(9).entries.size == 0 #???
#numbers.permutation(9) { |n| #yielded << n }
#yielded.should == []
end
(discourse/spec/models/topic_link_spec.rb)
it 'works' do
# ensure other_topic has a post
post
url = "http://#{test_uri.host}/t/#{other_topic.slug}/#{other_topic.id}"
topic.posts.create(user: user, raw: 'initial post')
linked_post = topic.posts.create(user: user, raw: "Link to another topic: #{url}")
TopicLink.extract_from(linked_post)
link = topic.topic_links.first
expect(link).to be_present
expect(link).to be_internal
expect(link.url).to eq(url)
expect(link.domain).to eq(test_uri.host)
link.link_topic_id == other_topic.id #???
expect(link).not_to be_reflection
...
(chef/spec/unit/chef_fs/parallelizer.rb)
context "With :ordered => false (unordered output)" do
it "An empty input produces an empty output" do
parallelize([], :ordered => false) do
sleep 10
end.to_a == [] #???
expect(elapsed_time).to be < 0.1
end
(bosh/spec/external/aws_bootstrap_spec.rb)
it "configures ELBs" do
load_balancer = elb.load_balancers.detect { |lb| lb.name == "cfrouter" }
expect(load_balancer).not_to be_nil
expect(load_balancer.subnets.sort {|s1, s2| s1.id <=> s2.id }).to eq([cf_elb1_subnet, cf_elb2_subnet].sort {|s1, s2| s1.id <=> s2.id })
expect(load_balancer.security_groups.map(&:name)).to eq(["web"])
config = Bosh::AwsCliPlugin::AwsConfig.new(aws_configuration_template)
hosted_zone = route53.hosted_zones.detect { |zone| zone.name == "#{config.vpc_generated_domain}." }
record_set = hosted_zone.resource_record_sets["\\052.#{config.vpc_generated_domain}.", 'CNAME'] # E.g. "*.midway.cf-app.com."
expect(record_set).not_to be_nil
record_set.resource_records.first[:value] == load_balancer.dns_name #???
expect(record_set.ttl).to eq(60)
end
I don't think there is any special behavior. I think you've found errors in the test code.
This doesn't work because there's no assertion, only a comparison:
#numbers.permutation(9).entries.size == 0
It would need to be written as:
#numbers.permutation(9).entries.size.should == 0
Or using the newer RSpec syntax:
expect(#numbers.permutation(9).entries.size).to eq(0)

complex validation on object update?

I have a model "WorkDetail" and db-attrs related to problem are 'name', 'status' and 'approved_status' with datatype all integer and the model class definition is as:-
class WorkDetail < ActiveRecord::Base
enum name: [:smartcheck, :install, :verify]
enum status: [:pending, :inprogress, :complete]
belongs_to :work_order
has_many :cabinets
after_save :work_order_status_update
private
def work_order_status_update
work_detail_count = self.work_order.work_details.count
status_array = self.work_order.work_details.where(status: 2).count
if status_array == work_detail_count
if work_detail_count == 0
self.work_order.update({status: "pending"})
else
self.work_order.update({status: "complete"})
end
else
self.work_order.update({status: "inprogress"})
end
end
end
Now, I would like to add custom validation to be applied for below problem :-
validation should only apply for update process.
If object's name == "smartcheck" and status == "complete" then only the approved_status boolean attribute should be updated to true (the
default is false on migration), else should give error if not
smartchecked and the status is not complete on trying to update
approved_status attr.
Hoping the question makes sense and Thanks !!! in advance guys, Happy Coding.
1) Validation should only apply for update process
after_save :work_order_status_update, :on => :update
2) If object's name == "smartcheck" and status == "complete" then only
the approved_status boolean attribute should be updated to true (the
default is false on migration), else should give error if not
smartchecked and the status is not complete on trying to update
approved_status attr.
def test_status_and_name
if self.name == "smartcheck" and self.status == "complete"
self.update_attributes(approved_status: true)
else
errors[:base] << "smartchecked and the status is not complete on trying to update approved_status attr."
end
end

Strange behavior using CSV class [duplicate]

I'm working on the EventReporter project to help learn Ruby.
Here's what I've got so far:
require 'CSV'
puts 'Welcome to Event Reporter!'
print 'Enter command: '
command = gets.chomp
def clean(attribute, type)
if (type == 'regdate')
elsif (type == 'first_name')
elsif (type == 'last_name')
elsif (type == 'email_address')
elsif (type == 'homephone')
homephone = attribute
homephone = homephone.to_s.gsub(/\D/, '')
if (homephone.length < 10)
homephone = '0000000000'
elsif (homephone.length == 11)
if (homephone[0] == '1')
homephone[0] = ''
else
homephone = '0000000000'
end
elsif (homephone.length > 11)
homephone = '0000000000'
end
return homephone
elsif (type == 'street')
elsif (type == 'city')
elsif (type == 'state')
elsif (type == 'zipcode')
zipcode = attribute.to_s.rjust(5, "0")[0..4]
return zipcode
end
return attribute
end
queue = []
while (command != 'q') do
command = command.split
if (command[0] == 'load')
command[1] ? filename = command[1] : filename = 'event_attendees.csv'
attendees = CSV.open filename, headers: true, header_converters: :symbol
puts "Loaded #{filename}"
elsif (command[0] == 'find')
attribute = command[1]
criteria = command[2]
# REACHES HERE SECOND TIME AROUND
puts "#{command[0]} #{command[1]} #{command[2]}"
attendees.each do |attendee|
# ISNT REACHING HERE SECOND TIME AROUND
puts 'TEST'
# get cleaned attendee attribute
attendee_attribute = clean(attendee[attribute.to_sym], attribute)
# see if it matches the criteria input
if criteria.to_s.downcase.strip == attendee_attribute.to_s.downcase.strip
# if it does, add the attendee to the queue
puts 'Match!'
queue << attendee
end
end
end
print 'Enter command: '
command = gets.chomp
end
It seems that the attendees.each isn't being executed the second time through the while loop. Why is this?
~/practice/event_manager >> ruby 'lib/event_reporter.rb'
Welcome to Event Reporter!
Enter command: load
Loaded event_attendees.csv
Enter command: find zipcode 11111
find zipcode 11111
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
Enter command: find zipcode 11111
find zipcode 11111
Enter command: q
~/practice/event_manager >>
According to the docs, the CSV object behaves basically like a regular IO object. They keep track of their current position in the file which is advanced by reading through it, generally line by line. So on your first attendees.each, you read through the entire file. Subsequent calls to .each will try to read the next line, but there is not any since we are already at the end of the file hence your loop does not execute anymore.
You can fix this by rewinding the underlying IO instance to the beginning of the file, using #rewind. In your specific case, put it after iterating through the attendees.
attendees.each do |attendee|
# ...
end
attendees.rewind

Ruby CSV.each in while loop not executing second time through

I'm working on the EventReporter project to help learn Ruby.
Here's what I've got so far:
require 'CSV'
puts 'Welcome to Event Reporter!'
print 'Enter command: '
command = gets.chomp
def clean(attribute, type)
if (type == 'regdate')
elsif (type == 'first_name')
elsif (type == 'last_name')
elsif (type == 'email_address')
elsif (type == 'homephone')
homephone = attribute
homephone = homephone.to_s.gsub(/\D/, '')
if (homephone.length < 10)
homephone = '0000000000'
elsif (homephone.length == 11)
if (homephone[0] == '1')
homephone[0] = ''
else
homephone = '0000000000'
end
elsif (homephone.length > 11)
homephone = '0000000000'
end
return homephone
elsif (type == 'street')
elsif (type == 'city')
elsif (type == 'state')
elsif (type == 'zipcode')
zipcode = attribute.to_s.rjust(5, "0")[0..4]
return zipcode
end
return attribute
end
queue = []
while (command != 'q') do
command = command.split
if (command[0] == 'load')
command[1] ? filename = command[1] : filename = 'event_attendees.csv'
attendees = CSV.open filename, headers: true, header_converters: :symbol
puts "Loaded #{filename}"
elsif (command[0] == 'find')
attribute = command[1]
criteria = command[2]
# REACHES HERE SECOND TIME AROUND
puts "#{command[0]} #{command[1]} #{command[2]}"
attendees.each do |attendee|
# ISNT REACHING HERE SECOND TIME AROUND
puts 'TEST'
# get cleaned attendee attribute
attendee_attribute = clean(attendee[attribute.to_sym], attribute)
# see if it matches the criteria input
if criteria.to_s.downcase.strip == attendee_attribute.to_s.downcase.strip
# if it does, add the attendee to the queue
puts 'Match!'
queue << attendee
end
end
end
print 'Enter command: '
command = gets.chomp
end
It seems that the attendees.each isn't being executed the second time through the while loop. Why is this?
~/practice/event_manager >> ruby 'lib/event_reporter.rb'
Welcome to Event Reporter!
Enter command: load
Loaded event_attendees.csv
Enter command: find zipcode 11111
find zipcode 11111
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
TEST
Enter command: find zipcode 11111
find zipcode 11111
Enter command: q
~/practice/event_manager >>
According to the docs, the CSV object behaves basically like a regular IO object. They keep track of their current position in the file which is advanced by reading through it, generally line by line. So on your first attendees.each, you read through the entire file. Subsequent calls to .each will try to read the next line, but there is not any since we are already at the end of the file hence your loop does not execute anymore.
You can fix this by rewinding the underlying IO instance to the beginning of the file, using #rewind. In your specific case, put it after iterating through the attendees.
attendees.each do |attendee|
# ...
end
attendees.rewind

A good way to check for inclusion of several items in an array

I have an options hash and a method to update it - but the options hash could change and if it does I want my tests to fail. What's a good way to write this?
raise RuntimeError, msg unless options.keys.include?(
:external_uid,
:display_name,
:country_code
)
If options.keys doesn't include exactly those three items, an error should be raised.
solution that i almost used (thanks bjhaid):
def ensure_correct_options!(options)
msg = "Only 'external_uid', 'display_name' and 'country_code' can be "
msg += "updated. Given attributes: #{options.keys.inspect}"
raise RuntimeError, msg unless options.keys == [
:external_uid,
:display_name,
:country_code
]
end
The options probably have a value, so I would write:
unless options[:external_uid] && options[:display_name] && options[:country_code]
raise ArgumentError, ":external_uid, :display_name and :country_code required"
end
(I've replaced RuntimeError with ArgumentError because this seems to be about arguments)
If there were more than three values to test for inclusion as keys in the hash, you might do it t like this:
unless ([:external_uid, :display_name,...] - options.keys).empty? \
raise ArgumentError, ":external_uid, :display_Nam,... are required"

Resources