Search code examples
rubyasynchronousrubymotionsingle-responsibility-principle

RubyMotion async programming with BubbleWrap


I am confused with how to write decent code when using a lot of asynchronous code.

In the following code snippet I log in to get the authentication cookie and use that cookie for the next request to get a list of projects name (as an example):

def self.populateProjectsTable(projects_controller)
  payload = {email: "email", password: "pass"}
  HTTP.post("http://example.com/login", {payload: payload}) do |response|
    authCookie = response.headers['Set-Cookie']
    HTTP.get("http://example.com/projects.json", {cookie: authCookie}) do |response|
      projects = JSON.parse(response.body.to_str)
      projects_controller.projects = projects
      projects_controller.reloadData
    end
  end
end

While this will work the code feels dirty. Not really following the single responsibility principle. I would like to extract this in a few methods:

def self.populateProjectsTable(projects_controller)
  @taskList = TaskList.new
  @taskList.doLogin
  projects = @taskList.getProjects
  projects_controller.projects = projects
  projects_controller.reloadData
end

def doLogin
  payload = {email: "email", password: "pass"}
  HTTP.post("http://example.com/login", {payload: payload}) do |response|
    @authCookie = response.headers['Set-Cookie']
  end
end

def getProjects
  HTTP.get("http://example.com/projects.json", {cookie: @authCookie}) do |response|
    projects = JSON.parse(response.body.to_str)
  end
end

This obviously does not work. The getProjects method is called before doLogin is finished and the projects are only known in the scope of the block, not giving back the data to the populateProjectsTable method.

How does one program such applications without the nesting shown in the first example?


Solution

  • You're not going to totally get away from the nesting. Taking Alan's answer and massaging it a bit, this is what I've come up with. It involves passing a block through a couple of methods.

    def self.populateProjectsTable(projects_controller)
      @taskList = TaskList.new
      @taskList.loginAndGetProjects do |projects|
        projects_controller.projects = projects
        projects_controller.reloadData
      end
    end
    
    def loginAndGetProjects(&block)
      payload = {email: "email", password: "pass"}
      HTTP.post("http://example.com/login", {payload: payload}) do |response|
        @authCookie = response.headers['Set-Cookie']
        getProjects(&block)
      end
    end
    
    def getProjects(&block)
      HTTP.get("http://example.com/projects.json", {cookie: @authCookie}) do |response|
        projects = JSON.parse(response.body.to_str)
        block.call(projects)
      end
    end