Procs in Ruby for Convenience
Message from 2022
This post is pretty old! Opinions and technical information in it are almost certainly oudated. Commands and configurations will probably not work. Consider the age of the content before putting any of it into practice.
This was pretty raunchy (especially when the call to
log() was repeated every other line in a 50-line script). So when I wrote a similar script, I put most of the redundancy into a
l = Proc.new do |m| log(team, service_id, m, Time.now()) end register b, team.ip, u, p l.call "A registered u} / p}" session b, team.ip, u, p do |d| do_something d end l.call "A did something"
How does this work?
Ruby blocks are one of the most visible and distinctive pieces of syntax in Ruby. Their close friend, the
Proc object, is how you separate the logic of a block from syntax and store it in a variable (or turn it into a method, but that’s something else).
Procs are similar to closures as seen in other languages, in that they bind to variables in the scope where they’re defined. Above, we note that
service_id aren’t defined within the block’s scope, so Ruby instead looks to the enclosing scope for their values. Since it is able to find them there, those values get attached to the block, so that wherever the block goes, those values for
service_id will follow. Since
m is specified as an argument to the block (the
|m| syntax), Ruby doesn’t go looking for it.
Since the block wasn’t going to be useful just sitting there, I converted it into a
Proc object and stashed it in
l. At this point, the 1-ary (takes one argument)
l contains the logic from the block, and the values from the variables above, all encapsulated neatly in a one-letter variable. Since it’s just a normal object, we can call its methods:
l.arity # => 1 l['lol'] # => does logging stuff l.call 'lol' # => does logging stuff
Either calling method (either
call) is smaller than the whole shebang from the first script above, so that’s a big win for DRY.