coverghoul

coverghoul

JSON API with Ash but no DB storage needed

Hey Ash Community,

I found ash and the json api and immediately thought of robust documenting APIs like fastapi in python, or spring in java. I’ve been playing with it, and for resources I’m backing into postgres this makes total sense/works. I’m trying to sent up a “calculation only” api (a bunch of recursive math calls), that I don’t need to store into the DB, this is what I came up with:

The domain –


defmodule App.Math do
  @moduledoc """
  This establishes our ash domain so that we track what is in scope
  """
  use Ash.Domain, extensions: [AshJsonApi.Domain]

  resources do
    resource App.Math.Answer
  end


  json_api do
    routes do
      # in the domain `base_route` acts like a scope
      base_route "/math/fibonacci", App.Math.Answer do
        post :calculate_fibonacci
      end
    end
  end
end

The resource –

defmodule App.Math.Answer do
  @moduledoc """
  This will showcase an API endpoint that does not store into the database,
  but instead does a calculation and returns to the user this value
  """
  use Ash.Resource,
    domain: App.Math,
    extensions: [AshJsonApi.Resource]

  attributes do
    attribute :problem, :string
    attribute :answer, :integer
  end

  resource do
    require_primary_key? false
  end

  defp calc_fibonacci(0) do
    [0 | 0]
  end
  defp calc_fibonacci(1) do
    [1 | 0]
  end
  defp calc_fibonacci(depth) when is_integer(depth) and depth >= 0 do
    [head | tail] = calc_fibonacci(depth - 1)
    [head + tail | head]
  end

  def calculate_fibonacci(depth) when is_integer(depth) and depth >= 0 do
    hd(calc_fibonacci(depth))
  end

  json_api do
    type "answer"
  end

  actions do
    action :calculate_fibonacci, :struct do
      constraints instance_of: __MODULE__
      argument :depth, :integer do
        constraints  [min: 0]
        allow_nil? false
      end

      run fn input, _context ->
        {:ok, %{problem: "fibonacci", answer: calculate_fibonacci(input.arguments.depth)}}
      end
    end
  end


end

When I post to this with -

{
  "data": {
    "attributes": {
      "depth": 3
    }
  }
}

I get -

[error] ** (ArgumentError) Could not determine a resource from the provided input: %{answer: 2, problem: "fibonacci"}

I could be approaching this the wrong way (and would welcome that feedback/a steer in the right direction), but was hopeful to have something that takes advantage of the swagger/redoc generation ash-json has. Thanks for reading this.

Paul

Marked As Solved

zachdaniel

zachdaniel

Creator of Ash

Ah, right. So in this case since you’re not using something that has a primary key (i.e non-storage backed as you say), your best bet is not to use routes like post and get, as those are meant to following specific patterns described here https://jsonapi.org

Spec compliant JSON:API requires ids. However, we provide a facility for doing generic routes that don’t have to strictly follow those formats, which looks like this:

  defmodule Bar do
    use Ash.Resource, domain: nil, extensions: AshJsonApi.Resource

    json_api do
      type "bar"

      routes do
        post :say_hello
      end
    end

    resource do
      require_primary_key?(false)
    end

    attributes do
      attribute(:message, :string, allow_nil?: false)
    end

    actions do
      action :say_hello, :struct do
        constraints(instance_of: __MODULE__)
        argument(:name, :string, allow_nil?: false)

        run(fn input, _ ->
          {:ok, "Hello, #{input.arguments.name}!"}
        end)
      end
    end
  end

What you can additionally do to simplify is something like this:

defmodule MathAnswer do
  destruct [:question, :answer]

  use Ash.Type.NewType, subtype_of: :struct, 
    constraints: [
      instance_of: __MODULE__,
      fields: [
        question: [type: :string, allow_nil?: false],
        answer: [type: :integer, allow_nil?: false]
      ]
    ]
end

Then you can simplify the resource to require no state/attributes.

defmodule App.Math.Answer do
  @moduledoc """
  This will showcase an API endpoint that does not store into the database,
  but instead does a calculation and returns to the user this value
  """
  use Ash.Resource,
    domain: App.Math,
    extensions: [AshJsonApi.Resource]

  actions do
    action :calculate_fibonacci, MathAnswerdo
      argument :depth, :integer do
        constraints  [min: 0]
        allow_nil? false
      end

      run fn input, _context ->
        {:ok, %MathAnswer{problem: "fibonacci", answer: calculate_fibonacci(input.arguments.depth)}}
      end
    end
  end

  defp calc_fibonacci(0) do
    [0 | 0]
  end
  defp calc_fibonacci(1) do
    [1 | 0]
  end
  defp calc_fibonacci(depth) when is_integer(depth) and depth >= 0 do
    [head | tail] = calc_fibonacci(depth - 1)
    [head + tail | head]
  end

  def calculate_fibonacci(depth) when is_integer(depth) and depth >= 0 do
    hd(calc_fibonacci(depth))
  end
end

And you can then use route/3 to define the route:

        route :post, "/", :calculate_fibonacci

Also Liked

coverghoul

coverghoul

Thanks for the reply, due to my new born haven’t had the time to try implementing. Hopefully he settles down tonight and I’ll be able to let you know how it goes. Thanks you again for all the help.

Last Post!

Bumbus

Bumbus

Hmm, damn I forgot to add the domain in `AshJsonApiRouter` :grimacing:

Where Next?

Popular in Questions Top

Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement