DEV Community

Beth Haubert
Beth Haubert

Posted on • Edited on

💥each_with_object💥

I've been wrestling with a slightly nasty refactor story recently. The following is my first iteration of a method that loops through a hash of @nameservers and for each one assigns the host to an instance variable in the class, then loops through each IP in the info hash and adds the output from a private method in the @results hash.
That's a lot of looping. 💩

def check
    @results = {}
    @nameservers.each do |host, info|
      @host = host
      info[:ips].each do |nameserver_ip|
        @results[nameserver_ip] = ip_check(nameserver_ip)
      end
    end

    @results
+end

@nameservers is a hash that looks something like this:

{
            "Google" => {
        :country => "USA",
            :ips => [
            [0] "8.8.8.8",
            [1] "8.8.4.4"
        ]
    },
           "OpenDNS" => {
        :country => "USA",
            :ips => [
            [0] "208.67.222.222",
            [1] "208.67.220.220"
        ]
    },
         "Fortalnet" => {
        :country => "Brazil",
            :ips => [
            [0] "189.90.16.20",
            [1] "189.90.16.21"
        ]
    },
    "Probe Networks" => {
        :country => "Germany",
            :ips => [
            [0] "82.96.64.2",
            [1] "82.96.65.2"
        ]
    }
}

What's the best way to simplify that method? Well, another dev turned me on to the each_with_object method. Instead of defining and returning the @results hash explicitly, it can be incorporated into one of the loops. How it works?

            def check
                @nameservers.each_with_object({}) do |(host, info), results|
                    info[:ips].each do |nameserver_ip|
                        results[nameserver_ip] = ip_check(host, nameserver_ip)
                    end
                end
            end

When you call each_with_object on @nameservers I'm passing an empty hash as the argument. (It doesn't need to be an empty hash. It can be a pre-existing hash or even an array). Inside the block you pass the object or objects to loop over, in this case host and info, and then the variable name you want to assign the object you passed in as an argument. So my hash will be returned as results.
The second loop in my method remained almost exactly the same, except that I decided to pass host through to my private ip_check method as well so that the state wasn't leaking into the rest of the object.

And voila. Just what I needed.

Top comments (0)