In my Indieweb Syndication code I was looking for a way to DRY-up two similar methods. I knew what I wanted to do, but wasn't sure how to do it. I wanted to have some kind of template method (you'll have to excuse me if my terminology is wrong) with placeholders for the parts where code diverged. I.e. I was thinking something along the lines of liquid or erb templates/placeholders for web pages but for actual code generation.

Here is a simplified example of what I started with:

def get(item)
    succeeded = false
    failed = false

    until succeeded | failed
        response = open("http://somewhere.com/?get=#{item}")
        response_json = JSON.parse(response.string)
        if (response.status[0] == "200") & response_json["posts"].any?
            #Same code here for get and add
            succeeded = true
        elsif (response.status[0] == "200") & response_json["posts"].empty?
            #Same code here for get and add
            failed = true
        end
    end
    succeeded
end

def add(item)
    succeeded = false
    failed = false

    until succeeded | failed
        response = open("http://somewhere.com/?add=#{item}")
        response_json = JSON.parse(response.string)
        if (response.status[0] == "200") & (response_json["result_code"] == "done")
            #Same code here for get and add
            succeeded = true
        elsif (response.status[0] == "200") & (response_json["result_code"] == "item already exists")
            #Same code here for get and add
            failed = true
        end
    end
    succeeded
end

You can see the two methods share an overall structure and also pieces of code within that structure. It would have been easy to write methods for the shared pieces of code within the conditionals, but that would have still left me with duplicated structures. After lots of reading around (it's odd how all useful Ruby posts seem to have been written around 2006-2008) I came up with this idea:

{ 
    "get" => {
        "lambda_1" => lambda { |response_json| response_json["posts"].any? },
        "lambda_2" => lambda { |response_json| response_json["posts"].empty? },
    },
    "add" => {
        "lambda_1" =>  lambda { |response_json| (response_json["result_code"] == "done") },
        "lambda_2" => lambda { |response_json| (response_json["result_code"] == "item already exists") },
    }
}.each do |method, lambdas|
    define_method(method) do |item|

        succeeded = false
        failed = false

        until succeeded | failed
            response = open("http://somewhere.com/?#{method}=#{item}")
            response_json = JSON.parse(response.string)
            if (response.status[0] == "200") & lambdas["lambda_1"].call(response_json)
                #Same things here
                succeeded = true
            elsif (response.status[0] == "200") & lambdas["lambda_2"].call(response_json)
                #Same things here
                failed = true
            end
        end
        succeeded
    end
end

This uses a hash to hold the code differences for each method, via the use of a lambda. This hash is then looped through and define_method used to build the methods, with the appropriate lambda "replacing" the "placeholders".

This is perhaps a bit messy, but I don't mind it as it is just for me. I could tidy up the visual appearance of it somewhat just by not attaching the .each do on the hash definition directly and instead using a variable.

There are pros and cons of this approach. The lambdas are a bit verbose. I.e I have to have lamba { |response_json| response_json["posts"].any? } whereas if this was just a text placeholder it would only be response_json["posts"].any?. Also, the placeholders aren't true placeholders because you have to remember to call the required variable in the lambda. But, on the plus side there is no use of eval and all code is proper code; it probably would have been possible to use liquid/erb to achieve the same end result, but I'm pretty sure I would have not been able to escape using eval or having to define certain parts of code as text.

You almost certainly don't want to write code like I do, but if you are interested here is the actual before and after code.