Table of Contents
- Common Refactoring approaches
- Other refactoring approaches
- Hash
- Array
- Hash.each_with_object
- Evaluate the number of different letters.
- Check if all elements are 'a'.
- Check if there is any 'a'.
- Check if there is no 'a'.
- Check if there is only one 'a'.
- Dinamically call a method
- Small Tips
- Safe navigation operator
- References
Common Refactoring approaches
Extract Method
You move some code from an old method into a new method. This will allow you to have smaller methods with descriptive names.
Before
def report
sold_items = %w[apples lemons grapes]
puts "*** Sales Report for #{Time.now.strftime("%d/%m/%Y")} ***"
sold_items.each { |i| puts i }
puts '*** End Of The Report ***'
endAfter
def report
sold_items = %w[apples lemons grapes]
puts "*** Sales Report for #{current_date} ***"
print_items(sold_items)
puts '*** End Of The Report ***'
end
def current_date
Time.now.strftime("%d/%m/%Y")
end
def print_items(items)
items.each { |i| puts i }
end
Refactoring Conditionals
You can also refactor complicated conditionals into methods to make them more readable.
Before
def check_working_hour
if Time.now.hour >= 9 && Time.now.hour <= 17
'You should be working.'
end
endAfter
def check_working_hour
if working_hour?
'You should be working.'
end
end
def working_hour?
Time.now.hour >= 3 && Time.now.hour <= 17
endInline Method
When a method body is more obvious than the method itself, jsut directory write the content of the method instead of creating an unecessary method.
Before
def result(score)
if check(score)
"Pass"
end
end
def check(score)
score > 70
endAfter
def new_result(score)
if score > 70
"Pass"
end
endExtract variable
You move some code into a new variable for better readability.
Before
def yesterday
(Time.now - 86400).strftime("%d/%m/%Y")
endAfter
def new_yesterday
one_day = 86400
(Time.now - one_day).strftime("%d/%m/%Y")
endOther refactoring approaches
Use default values
You can set a default value for an argument to make your code simpler.
Before
module TwoFer
def self.two_fer(name = '')
if name.empty?
'One for you, one for me.'
else
"One for #{name}, one for me."
end
end
endAfter
module TwoFer
def self.two_fer(name = 'you')
"One for #{name}, one for me."
end
endscan() to get the first letter of each word
With String#scan and a regex, you can get the first letter of each word. For instance, scan with the regex /\b\w will catch word boundaries (space, - and more) followed by a word character.
Before
def abbreviate(phrase)
abbreviation_array = phrase.split(/[\s|-]/).map { |word| word[0] }
abbreviation_array.flatten.join('').upcase
endAfter
def abbreviate(phrase)
phrase.scan(/\b\w/).join('').upcase
endUse attr_reader to access instance variables
Why I always use attr_reader to access instance variables - ivo’s awfully random tech blog
Before
class Car
def initialize(name)
@name = name
end
def name
@name
end
endAfter
class Car
attr_reader :name
def initialize(name)
@name = name
end
endlines or each_line to split multiline strings
Using String#lines/each_line is more intention revealing.
Before
def rows(nums)
nums.split(/\n/)
endAfter
def rows(nums)
nums.lines
endchars over split
Using String#chars is more intention revealing.
Before
"test".split('')After
"test".charseach_with_object
Just read this article! Incredibly easy to understand how each_with_object works and what it is good for .
Usage
The most useful and I think the most popular usage of each_with_object is putting hash or array as an argument.
You don’t need to declare array or hash before your loops.
# Hash
%w[string1 string2 string3].each_with_object({}){
|item, hash| hash[item] = item.upcase
}
# Array
(1..10).each_with_object([]) do |item, array|
array << item ** 2
end
# Hash.each_with_object
{key: "value", key2: "value2"}.each_with_object({}) do |(key, value), hash|
hash[value] = key
end
#=> {"value"=>:key, "value2"=>:key2}Instance method in a class method
Before
class Foo
def self.bar
'BAR'
end
def bar
'BAR'
end
endAfter
This class method is called a concenience method.
class Foo
def self.bar
Foo.new.bar
end
def bar
'BAR'
end
endcount all? any? none? and one?
Example
# Evaluate the number of different letters.
'aaa'.chars.zip('ass'.chars).count { |a, b| a != b }
# Check if all elements are 'a'.
'aaas'.chars.all? { |letter| letter == 'a' } #=> false
# Check if there is any 'a'.
'aaas'.chars.any? { |letter| letter == 'a' } #=> true
# Check if there is no 'a'.
'aaas'.chars.none? { |letter| letter == 'a' } #=> false
# Check if there is only one 'a'.
'aaas'.chars.one? { |letter| letter == 'a' } #=> falsescan to make an array
Before
'word'.chars.select { |letter| /\w/ === letter}After
'word'.scan(/[a-z]/)sum for cleaner caliculations
Before
POINTS_AND_LETTERS = {'A'=> 1, 'B'=> 2, 'C'=> 3}
def score
result = 0
'ABC'.strip.upcase.chars.each do |letter|
result += POINTS_AND_LETTERS[letter]
end
result
end
p score #=> 6After
POINTS_AND_LETTERS = {'A'=> 1, 'B'=> 2, 'C'=> 3}
def score
result = 'ABC'.strip.upcase.chars.sum do |letter|
POINTS_AND_LETTERS[letter]
end
end
p scoreDynamically call and define methods
You can dinamically define methods using Module#define_method and call methods with Object#send
define_method
class Klass
define_method :my_method do |arg|
arg * 2
end
end
Klass.new.my_method(2) #=> 4send
class Klass
def p_name(name)
name
end
end
# Dinamically call a method
Klass.new.send(:p_name, "test") #=> testPractical Example
Before
class Klass
def ken
"Ken"
end
def jack
"Jack"
end
endAfter
class Klass
def self.define_name(name)
define_method(name) do
name.to_s
end
end
define_name :ken
define_name :jack
end
Klass.new.ken #=> ken
klass.new.jack #=> jackTap
Before
user = User.new
user.username = "kartik"
user.save!After
user = User.new.tap do |u|
u.username = "kartik"
u.save!
endSmall Tips
super
The super method calls the parent class method.
class A
def test
p 'A'
end
end
class B < A
def test
super
p 'B'
end
end
B.new.test #=> A BSafe navigation operator
If you want to be safe and not risk a nil error, you would write something like the following:
if account && account.owner && account.owner.address
....
endwith the Safe navigation operator (&.) you can write it like the code below.
account&.owner&.address