A recent project at Turing involved creating an Enigma machine of sorts that encrypted and decrypted messages by rotating each character to a new one based on a randomly generated key and a date, with different rotators based on the location of the character in the message.
Once I had the functionality working, I realized I had a method in both my decrypt and encrypt classes that did the exact same thing, but in reverse in the case of decrypt. It looked like this:
#In the Encrypt class def rotate_message(message) indices_and_rotators = which_rotator(message) new_indices =  indices_and_rotators.length.times do new_indices << (indices_and_rotators[i] + (indices_and_rotators[i] % 85)) end end def rotate_encrypted_message(message) indices_and_rotators = which_rotator(message) new_indices =  indices_and_rotators.length.times do new_indices << (indices_and_rotators[i] - (indices_and_rotators[i] % 85)) end end
indices_and_rotators variable contains a 2D array of the initial index of the character, and the number of characters by which it was to be rotated. As you can see, the only difference in these two methods is that I’m adding the index and rotator together in the case of Encrypt, and subtracting them in the case of Decrypt. In the spirit of DRYing up my code, I wanted to do something about this.
I’ll walk through how it works, but first, spoiler alert — here’s what I ended up with:
#this is in the class from which both Encrypt and Decrypt inherit def rotate_indices(message, &block) indices_and_rotators = which_rotator(message) @new_indices =  indices_and_rotators.length.times do |i| @new_indices << block.call(indicies_and_rotators[i], indices_and_rotators[i] % 85) end ensure_valid_rotator end #in Encrypt def rotate_message(message) rotate_indices(message) do |initial, rotation| initial + rotation end end #in Decrypt def rotate_message(message) rotate_indices(message) do |initial, rotation| initial - rotation end end
In the actual code, each of these methods is in a different Class and different files, but I’ve brought them together just to simplify viewing. Here’s how this works:
First, do all the setup (calling the
which_rotator method, defined previously, and setting up the new_indices variable as an empty array) in the rotate_indices method so you don’t have to do it twice. Note that the new_indices variable now needs to be an instance variable so that it’s accessible across methods.
rotate_indices method, which both Encrypt and Decrypt have access to, call the block as an argument. Do this by calling &block as an argument in the method. You can call it whatever you want, as long as it starts with the ampersand, and you use the same name below when you call the block (line 6 in my example). This block argument does have to be the last one you pass your method.
This turns your block into a Proc that you can call elsewhere, and still give it access to your local variables.
Once you’ve created your Proc, you can call it in the different
rotate_message methods. initial references
indices_and_rotators[i] and rotation references
indices_and_rotators[i]. Then you can pass each the separate method that they need. (Yes,
— are methods, you can write them the way you can because of Ruby’s syntactic sugar).
This is another brief example. The accumulate method is basically a rewriting of the built in map method.
class Array def accumulate(&block) result =  self.each do |item| result << block.call(item) end result end end #In use: [1, 2, 3].accumulate do |number| number * number end # => [1, 4, 9]