refactoring Control Parameter - ruby

I'm using a tool for finding code smells in code called reek and I have a problem with one called Control Parameter
def place_ship(ship, start_position, orientation)
#row = start_position[:row]
#column = start_position[:column]
ship.length.times do
if orientation == :vertical
vertical_place_ship(row,column,ship)
else
horizontal_place_ship(row,column,ship)
end
end
end
def vertical_place_ship(row,column,ship)
self.grid[row][column].ship = ship
self.grid[row][column].status = :occupied
#row += 1
end
def horizontal_place_ship(row,column,ship)
self.grid[row][column].ship = ship
self.grid[row][column].status = :occupied
#column += 1
end
warning's content: [
55]:ControlParameter: Board#place_ship is controlled by argument 'orientation
How do I fix this?

'Orientation' is a flag value in place_ship method. Value of 'orientation' is not changing as the code executes. So no need to check it 'ship.length' times.
place_ship has only conditional logic and nothing else. This is unnecessary and the conditional logic can reside outside. You are passing in a flag, that tells the method what path to choose, conditionally. This is the Conditional Coupling smell. Generally do not pass in a conditional parameter to a method. Have 2 different methods for the 2 choices and name them aptly.
You already have aptly named vertical_place_ship and horizontal_place_ship methods. You can refactor it like this.
def <method_that_calls_place_ship>
// other code
if orientation == :vertical
vertical_place_ship(ship, start_position)
else
horizontal_place_ship(ship, start_position)
end
// more code
end
def vertical_place_ship(ship, start_position)
row = start_position[:row]
column = start_position[:column]
ship.length.times do
self.grid[row][column].ship = ship
self.grid[row][column].status = :occupied
row += 1
end
end
Similarly for horizontal_place_ship method.

Regardless of the tool feedback, looking at your code, the only line that differ between the horizontal & vertical case is whether to increase #rows or #columns. An option could be:
def place_ship(ship, start_position, orientation)
row = start_position[:row]
column = start_position[:column]
ship.length.times do
self.grid[row][column].ship = ship
self.grid[row][column].status = :occupied
orientation == :vertical ? row += 1 : column += 1
end
end
I removed the two (identical) methods, and just used the ternary operator ('?') to increase the correct variable after placing each ship part.

Related

Not displaying it's corresponding values with it's key for Hash

Ok i am not here to ask for an answer. But to be honest i am not really good in class variable. So i would appreciate you can guide me along with this piece of code.
I have read on class variable at those docs. I some what kind of understand it. But it comes to applying it for my own use. I would get confused.
class Square
##sqArray = {}
#attr_accessor :length
def initialize
if defined?(##length)
randno = "%s" % [rand(20)]
##length = randno.to_i
##sqArray = ##length
else
randno = "%s" % [rand(20)]
##length = randno.to_i
##sqArray = ##length
end
end
def Area
##area = ##length * ##length
return ##area
##sqArray[##length.to_sym] = ##area
puts ##sqArray
end
end
s1 = Square.new
puts s1.Area
Let me explain this piece of code. Basically every time i create a Square object it would go to initialize method. A random number will be generated and pass it to ##length, and ##length will be assigned to hash ##sqArray as it's key. But now the problem is when i create a new object s1. When i want to display the Area i want to test out to print the hash ##sqArray with it's length as it's key and area as it's value. But now the problem is only returning it's area only. e.g 114 only.
suppose to be e.g [ 24 => 114]
When defining the object's property (i.e. it's length), the correct approach is to use an instance variable, not a class variable. This is because (in your particular example), length is an attribute of a specific square and not something that applies to all squares. Your code should look something like this:
class Square
def initialize(length = rand(20))
#length = length
end
def area
#length * #length
end
end
s1 = Square.new
puts s1.area
Now, I am a little unclear what exactly you aim to achieve by use of that class variable ##sqArray - but for example, you could use this store a list of all defined Squares:
class Square
##squares_list = []
def self.all_known
##squares_list
end
def initialize(length = rand(20))
#length = length
##squares_list << self
end
def area
#length * #length
end
end
This would allow you to write code like:
s1 = Square.new #=> #<Square:0x0000000132dbc8 #length=9>
s2 = Square.new(20) #=> #<Square:0x000000012a1038 #length=20>
s1.area #=> 81
s2.area #=> 400
Square.all_known #=> [#<Square:0x0000000132dbc8 #length=9>, #<Square:0x000000012a1038 #length=20>]
Class variables have some odd behaviour and limited use cases however; I would generally advise that you avoid them when starting out learning Ruby. Have a read through a ruby style guide to see some common conventions regarding best practice - including variable/method naming (use snake_case not camelCase or PascalCase), whitespace, etc.

Building dynamic if statement based on user-defined input

I have a table, with increasing values each row (year in the code below).
I have a target, that specifies a "threshold". The target is user defined, it can contain a value for one or multiple columns of the table. This means you never know how many columns are specified in the target.
I want to match the first row in the table, where the values in the row are greater than the values in the target. I currently have this:
class Target < ActiveRecord::Base
def loop_sheets(sheets, year_array)
result = nil
elements = self.class.column_names[1..-3].map(&:to_sym)
to_match = elements.select{|e| self.send(e) != nil }
condition = to_match.map do |attr|
"row[:#{attr}] > #{attr}"
end.join " and "
year_array.each do |year|
sheets.each do |sheet|
row = sheet.calculation.datatable.select { |r| r[:year] == year }.first
does_match = eval(condition)
if does_match
result = {
:year => row[:year],
:sheet_name => sheet.name
}
return result
end
end
end
return result
end
end
This works perfectly, but now the algorithm is fixed to use AND matching. I want to support OR matching as well as AND matching. Also I want to avoid using eval, there has to be a more elegant way. Also I want to reduce the complexity of this code as much as possible.
How could I rewrite this code to meet these requirements? Any suggestion is appreciated.
To avoid using eval: Ruby can create code dynamically, so do that instead of adding strings together. All you have to do is take the strings away!
conditions = to_match.map do |attr|
proc {|row| row[attr.to_sym] > attr }
end
Now you have an array of runnable blocks that take the row as their argument and return the result of the condition (return keyword not required). If you're just doing and, it's as simple as:
does_match = conditions.all? {|c| c.call(row) }
which will be true only if all the conditions return a truthy value (i.e. not false or nil).
As for supporting OR logic, if you are happy to just support ORing all of the conditions (e.g. replacing "and" with "or") then this will do it:
does_match = conditions.any? {|c| c.call(row) }
but if you want to support ORing some and ANDing others, you'll need someway to group them together, which is more complex.

wrong number of arguments (1 for 0) ruby warrior intermediate error that doesn't make any sense

I'm doing level 2 of ruby warrior on intermediate and every time I run this I get this error even though it doesn't seem I should. I am very new to ruby so I'd appreciate it if someone could tell me why this is happening even though I'm passing warrior for glance and glance has 1 slot for a variable to
here's the error:
wrong number of arguments (1 for 0)
Player.rb:24:in `glance'
Player.rb:6:in `play_turn'
here's my code:
class Player
def play_turn(warrior)
#warrior = warrior
glance(warrior)
actions
end
def actions
#warrior = warrior
glance(warrior)
if rightempty
warrior.walk!(:right)
elsif forwardenemy && rightempty == false
warrior.fight!
else
warrior.walk!(warrior.direction_of_stairs)
end
end
def glance(warrior)
#wariror = warrior
forwardempty = warrior.feel.empty?(:forward)
leftempty = warrior.feel.empty?(:left)
rightempty = warrior.feel.empty?(:right)
backwardempty = warrior.feel.empty?(:backward)
forwardenemy = warrior.feel.enemy?(:forward)
leftenemy = warrior.feel.enemy?(:left)
rightenemy = warrior.feel.enemy?(:right)
backwardenemy = warrior.feel.enemy?(:backward)
forwardcaptive = warrior.feel.captive?(:forward)
leftcaptive = warrior.feel.captive?(:left)
rightcaptive = warrior.feel.captive?(:right)
backwardenemy = warrior.feel.captive?(:backward)
end
end
The issue is not with the number of arguments being passed to glance, it's what's happening within that method.
You're calling empty? with one argument e.g. :forward when it doesn't take any - hence the error "1 for 0"

ruby recursive loop with sub-block return design

I have a Ruby application that I'm developing that, for some reason, does not work as expected when using a recursive function that contains a block inside to return a value from a different class' function call (easier to see in the example code below). The odd thing is that when I created a minimal sample to try and find out what was going on, the sample works as expected. Example:
require 'json'
class Simple
attr_accessor :name, :children
def initialize(name,children=nil)
#name = name
#children = children
end
end
a = Simple.new('A')
b = Simple.new('B',[a])
c = Simple.new('C',[b])
d = Simple.new('D')
e = Simple.new('E',[d])
f = Simple.new('F')
g = Simple.new('G',[e,f])
foo = [c,e,g]
def looper(d)
holder = nil
d.each do |item|
# puts item.name
if item.name == 'D'
holder = Simple.new('Z',[])
elsif !item.children.nil?
holder = looper(item.children)
end
end
return holder
end
bar = looper(foo)
puts "Returned from looper: #{bar.name}"
In my actual code I ended up using the a class instance variable to get the response (which also works in the sample code). Example snippet of the function from above modified to the other pattern:
def looper(d)
holder = nil
d.each do |item|
# puts item.name
if item.name == 'D'
#holder = Simple.new('Z',[])
elsif !item.children.nil?
looper(item.children)
end
end
#holder
end
So my question is, is it good practice to use the instance variable? Any down sides to doing so since it works for my actual code whereas the first example pattern does not?
In your first piece of code, I'd expect to see a nil from your input, where in your second version you will get the object Simple.new('Z',[])
If that's your problem, it's because item g has children, the first one will set a value recusrively, but the second one will unset the value, so the second time through the loop, holder gets set to nil.
Edit: Actually my analysis of the results form the example above is wrong, because the last item in the top level list does contain the searched-for item. However, the analysis of the problem and difference in behaviour between the two solutions still holds in general.
You probably just want this:
def looper(d)
holder = nil
d.each do |item|
# puts item.name
if item.name == 'D'
holder = Simple.new('Z',[])
break
elsif !item.children.nil?
holder = looper(item.children)
break if holder
end
end
return holder
end
The break statement prevents you assigning again to holder . . .
Alternatively use Ruby internals like #first or #any? to express your search.
Assigning to instance variables as per your second example to inter-communicate between recursions is just fine, they are effectively shared state between all depths and iterations while the recursion runs. So it doesn't break recursion or Ruby per se, although you have to watch out for other kinds of unwanted interaction: For example you cannot assume an instance variable was set in a particular place during recursion, compared to current depth or loop item . . .

Using Strings as Variable/Object Names in Ruby

I am dealing with fractals. You start with a rectangle, and that shape is decreased by a given decay rate. I have it set up to do the first 10 iterations of the given scenario, and each scenario looks like this:
y_1 = dec_y(y_1)
y_2 = dec_y(y_2)
a_y = [y_1, y_2]
rect_1 = TkcRectangle.new(canvas, [0,0], a_y)
where dec_y is defined as the following:
def dec_y(y)
to_ret = y / $rate
return to_ret
end
I want to turn the first snippet into a function/method (not exactly sure what the Ruby term is...), so that each iteration will just be a single line referencing a method, which makes the problem more extensible. But, I need each TkcRectangle to have a different name. The way I want to set it up, each TkcRectangle will have the same name. But, if I can set the name of the object to a string passed as an argument, then I should not have a problem.
How do I define the name of an object with a given string?
Edit : Code has not been tested, but will give you the idea.
Instead of naming each element, you can use an array and use the index instead
rectangles_array = Array.new
for each loop
rectangles_array << create_rectangle_object(y_1, y_2, canvas)
end for each loop
def dec_y(y)
to_ret = y / $rate
return to_ret
end
def create_rectangle_object(y_1, y_2, canvas)
return TkcRectangle.new(canvas, [0,0], [dec_y(y_1), dec_y(y_2)])
end
If you really want to name it read about structs.. Something like
MyRectangleStruct = Struct.new(:obj_name, :x1, :y1, :x2, :y2)
puts MyRectangleStruct.new(:obj_name => 'First_rec', .....)
define_method(method_name, &block)
with method_name being any string and &block being a block of ruby code; usually it looks something like this:
define_method(method_name) do
your code goes here
end

Resources