Functional-ish Ruby

Functional-ish Ruby

Kevin Buchanan
Kevin Buchanan

August 20, 2014

In a recent Apprentice Blog of the Week, Alex Hill detailed one way that we can apply common Ruby patterns to our Clojure code. I’ve noticed a similar effect while making the opposite transition, too. Having spent a few months writing mostly Clojure and then transitioning back into writing mostly Ruby, it was interesting to see the way my experience with common patterns in Clojure influenced the way I approached writing Ruby.

Specifically, a couple of patterns I really enjoy using in Clojure are with and when macros. Usually, a macro starting with with- means that something is happening around the code you pass to it, and a macro starting with when- means that your code will be executed if a certain condition is met. For instance, we could write a macro called with-timing that times our code by setting a start time, evaluating the code, then logging the difference between the start and end times before returning our return value.

(defmacro with-timing [body]
		`(let [start# (now)
									ret# ~body]
					(logger/log (- (now) start#))
					ret#))
(defn timed-operation [x]
		(with-timing
				(calculate-some-things x)))

We might also write a macro called when-valid, which takes a record that we created from some user input, and then only evaluates our code if the record is valid, otherwise using the generic handler for invalid records.

(defmacro when-valid [record & body]
		`(if (valid? ~record)
					(render-invalid ~record)
					~@body))
(defn response-for [thing]
		(when-valid thing
														(render-created thing)))

We can implement our timing macro similarly in Ruby, by simply writing a method that takes a block to be called and timed.

def with_timing(&block)
		start_time = Time.now
		return_value = block.call
		log(Time.now - start_time)
		return_value
end

def timed_operation(x)
		with_timing do
				calculate_some_stuff(x)
		end
end

We can also reduce the duplication of a common Rails controller pattern by writing something similar to our when-valid macro in Ruby.

def when_valid(record, &block)
		if record.valid?
				block.call(record)
		else
				flash[:error] = record.errors.messages
				render :new
		end
end

def create
		when_valid(Thing.create(thing_params)) do |thing|
				redirect_to thing
		end
end

Here’s another useful when method for handling HTTP responses in Ruby that Myles Megyesi shared with me.

def when_status(response, responders)
		if responder = responders[response[:status]]
				responder.call(response)
		else
				handle_generically(response)
		end
end

def get_all_the_things
		when_status get("/things"), {
		200 => lambda do |response|
				load_things(response[:body])
		end,
		404 => lambda do
				"Whoops"
		end
		}
end

After transitioning back to writing Ruby after Clojure, I found myself naturally thinking of ways to use blocks and lambdas, among other functional-ish idioms, much more than before writing Clojure—and usually with positive results. It’s interesting to see how learning new languages expands the way you write the languages you already know. Perhaps there are patterns from certain languages you know just waiting to be applied somewhere else.