Code review request - parsing output from a query building tool

I’m looking for a review of my code to make it better. I know it’s not great as is, but I’m not sure what all could be improved by more idiomatic Elixir usage. You can view a gist of the code here, or the full text below. The goal of the code is to parse output from this query builder tool.

defmodule Mix.Tasks.Parser do
  use Mix.Task
  require IEx

  @shortdoc "A test of the parser."

  def run(_args) do
    Mix.shell.info "Greetings from the parser"

    # The following tree represents a query that looks like:
    # (users.enrollment_status == "E" || users.dob == "2017-11-06" || (users.is_member == true && (users.gender == "M" && users.site_id IN ("234siteid"))))
    tree = Poison.decode!(~s(
      {  
         "usedFields":[  
            "users.enrollment_status",
            "users.dob",
            "users.is_member",
            "users.gender",
            "users.site_id"
         ],
         "rules":[  
            {  
               "id":"9bab8999-cdef-4012-b456-715f92375987",
               "field":"users.enrollment_status",
               "type":"select",
               "input":"select",
               "operator":"select_equals",
               "values":[  
                  {  
                     "type":"select",
                     "value":"E"
                  }
               ]
            },
            {  
               "id":"aabaa8ab-89ab-4cde-b012-315f92377265",
               "field":"users.dob",
               "type":"date",
               "input":"date",
               "operator":"equal",
               "values":[  
                  {  
                     "type":"date",
                     "value":"2017-11-06"
                  }
               ]
            },
            {  
               "rules":[  
                  {  
                     "id":"9a9b899a-0123-4456-b89a-b15f9237a0de",
                     "field":"users.is_member",
                     "type":"boolean",
                     "input":"boolean",
                     "operator":"equal",
                     "values":[  
                        {  
                           "type":"boolean",
                           "value":true
                        }
                     ]
                  },
                  {  
                     "rules":[  
                        {  
                           "id":"aa98baaa-89ab-4cde-b012-315f9238650b",
                           "field":"users.gender",
                           "type":"select",
                           "input":"select",
                           "operator":"select_equals",
                           "values":[  
                              {  
                                 "type":"select",
                                 "value":"M"
                              }
                           ]
                        },
                        {  
                           "id":"a9bb8a8b-4567-489a-bcde-f15f92389859",
                           "field":"users.site_id",
                           "type":"select",
                           "input":"select",
                           "operator":"select_any_in",
                           "values":[  
                              {  
                                 "type":"multiselect",
                                 "value":[  
                                    "234siteid"
                                 ]
                              }
                           ]
                        }
                     ],
                     "condition":"AND"
                  }
               ],
               "condition":"AND"
            }
         ],
         "condition":"OR"
      }
    ))

    parsed_output = parse(tree)
    IO.puts parsed_output
  end

  def parse(node) do
    res = Enum.map(Map.get(node, "rules"), &(parse_node/1)) 
      |> Enum.join(" " <> Map.get(node, "condition") <> " ")

    "(#{res})"
  end

  # so every node is a set of tuples -- 
  def parse_node(node) do
    if (is_top_level_node(node)), do: parse(node), else: sql_from_rule(node)
  end

  def is_top_level_node(node) do
    cond do
      is_map(node) ->
        Map.keys(node) |> Enum.member?("condition")
      true ->  # default condition
        false
    end
  end

  def sql_from_rule(rule) do
    # IEx.pry
    field = Map.get(rule, "field")
    # is there a better way here?
    value = Map.get(rule, "values") |> List.first |> Map.get("value")
    # there will be a lot of logic around picking the operator, and 
    # any tips there would be appreciated
    "#{field} = #{value}"
  end
end
1 Like