Skip to content

Instantly share code, notes, and snippets.

@7hoenix
Last active October 11, 2015 17:20
Show Gist options
  • Save 7hoenix/5ea1235c898f5d369b91 to your computer and use it in GitHub Desktop.
Save 7hoenix/5ea1235c898f5d369b91 to your computer and use it in GitHub Desktop.
Metaprogramming_in_ruby_with_send
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