StevenMcD
StevenMcD

Reputation: 17482

Elixir: Run function after another function

I'm still quite new to Elixir and checking out some code in a repository and wondering if there is a way to cut down the following code snippet:

  def find(client) do
    Common.find(client, @resource)
    |> ResponseHandler.handle_response(@model_module)
  end

  def find(client, identifier) do
    Common.find(client, @resource, identifier)
    |> ResponseHandler.handle_response(@model_module)
  end

  def filter(client, filter) do
    Common.filter(client, @resource, filter)
    |> ResponseHandler.handle_response(@model_module)
  end

  def create(client, bank_transactions_map) do
    Common.create(client, @resource, bank_transactions_map)
    |> ResponseHandler.handle_response(@model_module)
  end

  def update(client, identifier, bank_transactions_map) do
    Common.update(client, @resource, identifier, bank_transactions_map)
    |> ResponseHandler.handle_response(@model_module)
  end

All the functions here pipe into the same function and it seems a little repetitive. Is there a way to set up a function in the module to take in an argument and run the .handle_response line without setting it in each function here?

Upvotes: 0

Views: 992

Answers (2)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

If that is the real codepiece, meaning the functions are wrappers for the functions from another module having the same names and interspersed with @resource parameter list, one might define them dynamically:

functions =
  [find: ~w|client|a,
   find: ~w|client identifier|a,
   filter: ~w|client filter|a,
   create: ~w|client bank_transactions_map|a,
   update: ~w|client identifier bank_transactions_map|a]

Enum.each(functions, fn {fun, args} ->
  args = Enum.map args, &{&1, [], Elixir}
  wrapped_args = with [h | t] <- args,
    do: [h | [ quote(do: @resource) | t ]] # inject @resource
  def unquote(fun)(unquote_splicing(args)) do
    Common 
    |> apply(unquote(fun), unquote(wrapped_args))
    |> ResponseHandler.handle_response(@model_module)
  end
end)

If the wrapping is not as pure, the initial list of functions to wrap should be extended to something like [find: [params: [...], as: :search].

The advantage of this approach is that once the function producer is written and well-tested, adding the new wrappers is as easy as updating a config file.

Upvotes: 1

Dogbert
Dogbert

Reputation: 222128

You can define a new function which takes in the function name to call along with the arguments and that function will pipe into ResponseHandler.handle_response(@model_module) at the end. The following code is untested but should work:

def find(client) do
  go(:find, [client, @resource])
end

def find(client, identifier) do
  go(:find, [client, @resource, identifier])
end

...

def go(function, args) do
  apply(Common, function, args)
  |> ResponseHandler.handle_response(@model_module)
end

You can also automatically inject @resource as the second argument if you want:

def find(client) do
  go(:find, [client])
end

def find(client, identifier) do
  go(:find, [client, identifier])
end

...

def go(function, [head | tail]) do
  apply(Common, function, [head, @resource | tail])
  |> ResponseHandler.handle_response(@model_module)
end

You can reduce this even further using macros/metaprogramming but I wouldn't recommend it if you only have 5 cases as it complicates the code. I'd go with the first solution as it's the simplest one.

Upvotes: 2

Related Questions