Last active
October 11, 2015 17:20
-
-
Save 7hoenix/5ea1235c898f5d369b91 to your computer and use it in GitHub Desktop.
Metaprogramming_in_ruby_with_send
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Metaprogramming => programs that write programs. | |
Why does it matter? Rails uses it a ton behind the scenes. | |
How does it work? Many patterns but the one we are going to focus on today is #send. | |
Let's say you want to write a method that will contact your mother via text (because she | |
keeps complaining that you never talk to her anymore since you started Turing). | |
So you write the following method: | |
class Contacter | |
def self.contact_via_text(recipient_phone_number, sent_phone_number, message) | |
#This implementation comes from the Twilio API and won't actually work unless you hook up Twilio | |
client.messages.create(from: sent_phone_number, to: recipient_phone_number, message) | |
end | |
end | |
moms_number = (303) 555-8888 | |
my_number = (303) 555-7777 | |
message = "Hi mom" | |
Contacter.contact_via_text(moms_number, message) | |
And that works for a while but eventually mom says she wants something more personal. | |
So you do the only thing you can... you set up your program to send her email in addition to text. | |
class Contacter | |
attr_reader :client, :mailer | |
def initialize | |
@client = Twilio::REST::Client.new(ENV["twilio_sid"], ENV["twilio_token"]) | |
@mailer = MyMailer.new | |
end | |
def contact_via_text(recipient_phone_number, sent_phone_number, message) | |
client.send_message(from: sent_phone_number, to: recipient_phone_number, message) | |
end | |
def contact_via_email(recipient_email_address, sent_email_address, message) | |
mailer.send_message(from: recipient_email_address, to: sent_email_address, message: message) | |
end | |
end | |
Now you have options when you choose to contact her. | |
moms_number = (303) 555-8888 | |
my_number = (303) 555-7777 | |
moms_email = "mom@example.com" | |
my_email = "justin@example.com" | |
message = "Hi mom" | |
contacter = Contacter.new | |
contacter.contact_via_text(moms_number, my_number, message) | |
contacter.contact_via_email(moms_email, my_email, message) | |
Well what do you notice about these two methods? | |
They are pretty much the same in many ways which means that they are excellent examples for refactoring because they are violating the DRY principle ("Don't Repeat Yourself"). | |
So let's do some refactoring with the powerful #send method. | |
But before that let's examine how it works in an IRB session. | |
irb | |
a = [1, 2, 3] | |
a.length => 3 | |
a.send("length") => 3 | |
a.send(:length) => 3 | |
what if we get a little clever? | |
substring = "len" | |
a.send(substring + "gth") => 3 | |
a.send(#{:len} + "gth") => 3 | |
a.send("l" + "en" + "gth") => 3 | |
Whoa... cool stuff. | |
So what we really want to do is refactor the contact_via methods into a single method. | |
class Contacter | |
attr_reader :client, :mailer | |
def initialize | |
@client = Twilio::REST::Client.new(ENV["twilio_sid"], ENV["twilio_token"]) | |
@mailer = MyMailer.new | |
end | |
def contact(communication_type, recipient_info, from_info, message) | |
communication_type.send_message(from: recipient_email_address, to: sent_email_address, message: message) | |
end | |
end | |
contacter = Contacter.new | |
contacter.contact(:email, moms_email, my_email, message) | |
contacter.contact(:phone_number, moms_number, my_number, message) | |
This makes sense but its not really meta (and it doesn't work I don't think). | |
Or else we could build a router: | |
class Router | |
def self.contact(communication_type, recipient_info, sender_info, message) | |
Contacter.send("contact_via_#{communication_type}")(recipient_info, sender_info, message) | |
end | |
end | |
Now instead of touching the Contacter class at all we instead only touch the router class and that handles where it goes. | |
Router.contact(:email, moms_email, my_email, message) | |
Notice how its the same amount of code (give or take) when we have two examples... And the implementation is much cleaner. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment