All posts

Migrating behavior from instance based to class based

I recently had to build a new Ruby class in an application I work on to group like behavior. I originally chose to implement the behavior within the class using instance methods because I wanted some initial state to work with. This was the original implementation:

class Recommendation
  attr_accessor :recommendations

  def initialize
    @recommendations = []
  end

  def recommendations(options = {})
    options[:first] ? options[:first] : options[:first] = 10

    query = <<-GRAPHQL
      query {
        recommendations(#{options}) {
          page_info {
            end_cursor
            has_next_page
          }
          edges {
            node {
              id
              name
            }
          }
        }
      }
    GRAPHQL

    response = Api.post("/recommendations/graphql", query: query).data
    recs = response.data.recommendations
    recommendations << recs.edges.map(&:node)

    if recs.page_info.has_next_page
      recommendations({after: recs.page_info.end_cursor})
    end

    recommendations.flatten
  end
end

In the example above the class is initialized with an empty list of recommendations and when the #recommendations method is called it updates that state with all of the recommendations from the server by calling the method recursively until the server does not have any more records.

The issue here is this really is not an object that needs to be instantiated multipled times with a lot of different public interfaces, it is more like a helper method with a very specific function, return all the recommendations. In this case a class method makes more sense.

The part of this I had to think about was how to handle the recommendations state since I would no longer me initializing a class with shared state. Because this class only has one method I was able to just set an array object at the top of the new class method to hold the recommendations, which would be the new state. If other methods needed this data though I would have to figure out a new option, likely passing data as parameters between methods or returning the state from method calls. These would be more functional approaches to the problem.

Here is the output of the conversion to class based behavior:

class Recommendation
  def self.recommendations(options = {})
    recommendations = []
    options[:first] ? options[:first] : options[:first] = 10

    query = <<-GRAPHQL
      query {
        recommendations(#{options}) {
          page_info {
            end_cursor
            has_next_page
          }
          edges {
            node {
              id
              name
            }
          }
        }
      }
    GRAPHQL

    response = Api.post("/recommendations/graphql", query: query).data
    recs = response.data.recommendations
    recommendations << recs.edges.map(&:node)

    if recs.page_info.has_next_page
      recommendations({after: recs.page_info.end_cursor})
    end

    recommendations.flatten
  end
end

More Ruby posts for you enjoyment:

Postscript:

I built two products to help fund continued efforts for this website, it would mean a lot if you would check them out:
1. 2500+ Words for the Undermotivated Software Engineer - A short essay I wrote on the titled topic ($3).
2. Build 3 Chrome Extensions in Under 2 Hours - A course I built to teach chrome extension development ($9-20).