some Ruby things I learned today ðŸ’
If you've been following my posts, I mentioned that following the #100DaysOfCode challenge I wanted to spend more time doing practice problems on codewars, leetcode, HackerRank, etc. This is because I want to spend more time developing my problem-solving skills and to become more familiar of the "ruby-way" of programming.
Here are some things I learned about today that feel worth noting:
Inject / Reduce
These methods can be used to basically combine a bunch of things down to one thing (or at least that's how I've come to understand it so far). Usually, we can do this by iterating through the data structure and applying some sort of aggregation technique, but the reduce
method lets us do it in one line!
According to ruby-doc, the inject
and reduce
methods are aliases and they combine all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator.
Example of inject and reduce
[1,2,3,4].inject( :+ )
=> 10
# A method that generates the full name of people given their first name
# followed by some variable number of middle names, and finally their last name.
def full_name(name, *other_names)
other_names.reduce(name) { |n, o| n + " " o)
end
full_name('John', 'Jacob', 'Jingleheimer', 'Schmidt')
=> "John Jacob Jingleheimer Schmidt"
Note: A colon :
before a sequence of characters is a Symbol
literal. The symbol you pass to reduce
or inject
will be interpreted as a name of a method to call on each element.
Defining Methods
Positional arguments, Optional parameters, and Keyword arguments
There are several ways to setup the way arguments are passed to methods in Ruby. The first way to do so is to simply use positional arguments. For example, here is a class Coffee
with positional arguments to initialize the coffee object's size, flavor, and roast.
class Coffee
attr_accessor :size, :roast, :flavor, :ice
def initialize(size, roast, flavor, ice)
@size = size
@flavor = flavor
@roast = roast
@ice = ice
end
end
cuppa = Coffee.new('small', 'dark', 'vanilla', true)
This is a straightforward way of setting up our class, but there is one major drawback to using positional arguments which is that the arguments are order specific. Let's pretend that instead of the three arguments we have to initialize the object, we had 20. If we wanted to apply a default value to one of the arguments, we would now need to re-order the arguments in the initialize method call and in all the objects that have been instantiated previously. This becomes really cumbersome if you have a lot of arguments because you now have to ensure that the argument order is corrected. Technically, the method invocation will still work if you don't place the argument with a default value at the end (for example, you can define a method like def initialize(size, roast='Medium', flavor)
, but the convention is to place arguments with default values at the end because it's less confusing.
Here is an example of adding a default value to our argumentroast
in our Coffee
class.
class Coffee
attr_accessor :size, :roast, :flavor, :ice
def initialize(size, flavor, ice, roast='Medium')
@size = size
@flavor = flavor
@roast = roast
@ice = ice
end
end
cuppa = Coffee.new('small', 'dark', 'vanilla', true)
# The values are now incorrect with no indication of the error
cuppa.size # small
cuppa.flavor # dark
cuppa.ice # vanilla
cuppa.roast # true
Luckily, we can refactor our class to use keyword arguments to make future changes a lot easier! With keyword arguments, we explicitly state every argument with a keyword (just as the naming implies). This will make things easier such that the object instantiation will no longer rely on order-specific argument calls. Then, if we need to add a default parameter later on we don't need to change every invocation of our Coffee class.
class Coffee
attr_accessor :size, :roast, :flavor, :ice
def initialize(size:, roast: 'Medium', flavor, ice: 'false')
@size = size
@flavor = flavor
@roast = roast
@ice = ice
end
end
cuppa = Coffee.new(
size: 'large',
roast: 'dark',
flavor: 'hazelnut',
)
cuppa.size # large
cuppa.roast # dark
cuppa.flavor # hazelnut
cuppa.ice # false
Awesome, now we can see with the method invocation exactly what arguments the class is expecting!
Previous post: It's been a minute... hi! 💖
Next post: What I learned building my first rails application.