【问题标题】:How to run GraphQL queries in Elixir/Phoenix controllers without removing the generated CRUD web pages?如何在 Elixir/Phoenix 控制器中运行 GraphQL 查询而不删除生成的 CRUD 网页?
【发布时间】:2018-08-06 10:46:12
【问题描述】:

我在 Elixir 中创建了一个 Phoenix 应用程序。我使用生成工具来创建所有的 crud 功能和 crud 页面。 它目前使用:

def index(conn, _params) do
  data = Repo.all(Object)
  render(conn, "index.html", data: data)
end

如何用 GraphQL 实现替换它,因为我目前能够通过指定的 url 传递 GraphQL 查询,例如。从表中获取所有记录。该文档讨论了使用 absinthe_phoenix 插件并将其添加到您的管道中。这最终只是替换了我拥有的当前网页并要求一个 url,所有当前页面都是 Phoenix 在运行 scaffholding 命令以生成 crud 和数据库模式时创建的页面。

我需要保留所有这些杂乱无章的页面,但让它们运行 GrapQL 查询。所以在显示数据库中所有记录的页面上,我需要它而不是运行

data = Repo.all(Object)

它应该运行

{
  objects{
    field1,
    field2
  }
}

获取所有数据。如何在控制器中运行 GraphQL 查询?

这是我需要在我的 GraphQL 架构中运行的查询

query do
    @doc """
    Returns all the records from a database
    """
    field :objects, list_of(:object) do
        resolve &Billingplus.ObjectResolver.all/2
    end
end

【问题讨论】:

  • 为什么不创建一个全局 GraphQL 路由来处理所有查询和突变(使用 absinthe_plug),而不是使用 absinthe_phoenix 为每个控制器添加 GraphQL 功能?
  • 看看我已经拥有的,我目前可以使用特定的 url 来发送 GraphQL 查询。但我怎样才能让我的控制器使用这是我的问题。还是让它直接与 Ecto.Repo 对话更好?
  • 没错,您的 REST 控制器不应使用 graphql 查询,而应仅使用 Ecto 从数据库中获取数据。
  • 有趣,这背后的原因是什么?那是因为这会使它过于复杂而没有真正的好处吗?
  • 没错。 GraphQL 和 REST 是不同的范式,将两者混用没有任何好处。

标签: elixir graphql phoenix-framework absinthe


【解决方案1】:

您需要在 GQL 架构中创建引用,并为不同的事物提供解析器函数。你没有把它放在你的控制器中。我将向您展示从 DB 到 Absinthe 实现的“事物”的前后示例。

REST 和 GQL 是不同的范例/接口。

您的项目中是否有任何可用的 GQL?展示您所做的工作,以便我们提供建议。

突变示例:

mutation do
    @desc "Create an OAuth2 client"
    field :create_oauth2_client, :oauth2_client do
      arg(:app_id, non_null(:uuid4))
      arg(:client_id, non_null(:string))
      arg(:client_secret, non_null(:string))
      arg(:oauth2_provider_id, non_null(:uuid4))

      resolve(&Resolvers.OAuth2Client.create_oauth2_client/3)
    end
end

查询示例:

query do
    @desc "Get an OAuth2 client"
    field :oauth2_client, :oauth2_client do
      arg(:id, non_null(:uuid4))
      resolve(&Resolvers.OAuth2Client.get_oauth2_client/3)
    end
end

示例架构对象:

defmodule ApiWeb.Schema.OAuth2Client do
  use Absinthe.Schema.Notation

  alias Api.Auth.Apps
  alias Api.Auth.OAuth2Providers
  alias Api.Util

  @desc "An OAuth2 client"
  object :oauth2_client do
    field(:id, :uuid4)
    field(:client_id, :string)
    field(:client_secret, :string)

    field(:app, :app,
      resolve: fn oauth2_client, _, _ ->
        Apps.get_app(oauth2_client.app_id)
        |> Util.handle_not_found_error_and_wrap("App not found.")
      end
    )

    field(:oauth2_provider, :oauth2_provider,
      resolve: fn oauth2_client, _, _ ->
        OAuth2Providers.get_oauth2_provider(oauth2_client.oauth2_provider_id)
        |> Util.handle_not_found_error_and_wrap("OAuth2Provider not found.")
      end
    )
  end
end

示例解析器:

defmodule ApiWeb.Resolvers.OAuth2Client do
  alias Api.Auth.OAuth2Clients

  #
  # QUERIES
  #
  def get_oauth2_client(_parent, %{id: id}, _resolution) do
    OAuth2Clients.get_oauth2_client(id)
  end

  def get_oauth2_clients_by_app(_parent, %{app_id: app_id}, _resolution) do
    OAuth2Clients.get_oauth2_clients_by_app(app_id)
  end

  #
  # MUTATIONS
  #
  def create_oauth2_client(_parent, params, _resolution) do
    OAuth2Clients.create_oauth2_client(%{
      app_id: params.app_id,
      oauth2_provider_id: params.oauth2_provider_id,
      client_id: params.client_id,
      client_secret: params.client_secret
    })
  end
end

示例上下文:

defmodule Api.Auth.OAuth2Clients do
  @moduledoc """
  The OAuth2Clients context.
  """

  import Ecto.Query, warn: false

  alias Api.Repo
  alias Api.Auth.OAuth2Clients.OAuth2Client
  alias Api.Util

  @doc """
  Returns the list of OAuth2Clients.

  ## Examples

      iex> list_oauth2_clients()
      {:ok, [%OAuth2Client{}, ...]}

  """
  def list_oauth2_clients do
    Repo.all(OAuth2Client)
    |> Util.handle_not_found_error_and_wrap("No OAuth2Clients found.")
  end

  @doc """
  Gets a single OAuth2Client.

  Returns `{:error, %NotFoundError{}}` if the OAuth2Client does not exist.

  ## Examples

      iex> get_oauth2_client(123)
      {:ok, %OAuth2Client{}}

      iex> get_oauth2_client(456)
      {:error, %NotFoundError{}}

  """
  def get_oauth2_client(id) do
    Repo.get(OAuth2Client, id)
    |> Util.handle_not_found_error_and_wrap("OAuth2Client not found.")
  end

  @doc """
  Gets a single OAuth2Client by `client_id` and `provider_id`.

  Returns `{:error, %NotFoundError{}}` if the OAuth2Client does not exist.

  ## Examples

      iex> get_oauth2_client_by_client_and_provider_id(123)
      {:ok, %OAuth2Client{}}

      iex> get_oauth2_client_by_client_and_provider_id(456)
      {:error, %NotFoundError{}}

  """
  def get_oauth2_client_by_client_and_provider_id(client_id, provider_id) do
    from(o in OAuth2Client,
      where: o.client_id == ^client_id and o.oauth2_provider_id == ^provider_id
    )
    |> Repo.one()
    |> Util.handle_not_found_error_and_wrap("OAuth2Client not found.")
  end

  @doc """
  Gets a list of OAuth2Client by App.

  Returns `{:error, %NotFoundError{}}` if there are no OAuth2Client to return.

  ## Examples

      iex> get_oauth2_clients_by_app(123)
      {:ok, [%OAuth2Client{}, ...]}

      iex> get_oauth2_clients_by_app(456)
      {:error, %NotFoundError{}}

  """
  def get_oauth2_clients_by_app(app_id) do
    from(o in OAuth2Client,
      where: o.app_id == ^app_id
    )
    |> Repo.all()
    |> Util.handle_not_found_error_and_wrap("App not found.")
  end

  @doc """
  Gets an OAuth2Client by `App` and `Provider`

  Returns `{:error, %NotFoundError{}}` if there is no `OAuth2Client` to return.

  ## Examples

      iex> get_oauth2_clients_by_app_and_provider(123)
      {:ok, [%OAuth2Client{}, ...]}

      iex> get_oauth2_clients_by_app_and_provider(456)
      {:error, %NotFoundError{}}

  """
  def get_oauth2_client_by_app_and_provider(app_id, provider_id) do
    from(o in OAuth2Client,
      where: o.app_id == ^app_id and o.oauth2_provider_id == ^provider_id
    )
    |> Repo.one()
    |> Util.handle_not_found_error_and_wrap("App not found.")
  end

  @doc """
  Creates an OAuth2Client.

  ## Examples

      iex> create_oauth2_client(%{field: value})
      {:ok, %OAuth2Client{}}

      iex> create_oauth2_client(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_oauth2_client(attrs) do
    %OAuth2Client{}
    |> OAuth2Client.create(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates an OAuth2Client.

  ## Examples

      iex> update_oauth2_client(oauth2_client, %{field: new_value})
      {:ok, %OAuth2Client{}}

      iex> update_oauth2_client(oauth2_client, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_oauth2_client(%OAuth2Client{} = oauth2_client, attrs) do
    oauth2_client
    |> OAuth2Client.update(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a OAuth2Client.

  ## Examples

      iex> delete_oauth2_client(oauth2_client)
      {:ok, %OAuth2Client{}}

      iex> delete_oauth2_client(oauth2_client)
      {:error, %Ecto.Changeset{}}

  """
  def delete_oauth2_client(%OAuth2Client{} = oauth2_client) do
    Repo.delete(oauth2_client)
  end
end

示例架构/模型:

defmodule Api.Auth.OAuth2Clients.OAuth2Client do
  use Ecto.Schema
  import Ecto.Changeset

  alias Api.Auth.Apps.App
  alias Api.Auth.OAuth2Providers.OAuth2Provider

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "oauth2_clients" do
    field(:client_id, :string)
    field(:client_secret, :string)

    belongs_to(:app, App)
    belongs_to(:oauth2_provider, OAuth2Provider)

    timestamps()
  end

  def create(app, attrs) do
    app
    |> cast(attrs, [:client_id, :client_secret, :oauth2_provider_id, :app_id])
    |> validate_required([:client_id, :client_secret, :oauth2_provider_id, :app_id])
    |> foreign_key_constraint(:app_id)
    |> foreign_key_constraint(:oauth2_provider_id)
  end

  def update(app, attrs) do
    app
    |> cast(attrs, [:client_id, :client_secret, :oauth2_provider_id, :app_id])
    |> foreign_key_constraint(:app_id)
    |> foreign_key_constraint(:oauth2_provider_id)
  end
end

迁移示例:

defmodule Api.Repo.Migrations.CreateOAuth2Clients do
  use Ecto.Migration

  def change do
    create table(:oauth2_clients, primary_key: false) do
      add(:id, :binary_id, primary_key: true)
      add(:client_id, :string, null: false)
      add(:client_secret, :string, null: false)
      add(:oauth2_provider_id, references(:oauth2_providers, type: :binary_id), null: false)
      add(:app_id, references(:apps, type: :binary_id), null: false)
      timestamps()
    end
  end

  def up do
    create(
      constraint(:owners, :user_or_organization,
        check:
          "((organization_id is not null and user_id is null) or (organization_id is null and user_id is not null))"
      )
    )
  end

  def down do
    drop(constraint(:owners, :user_or_organization))
  end
end

这就是你感到困惑的地方。您无需在路由器中引用控制器,而是指定 GQL 端点并在那里查询您的后端。

defmodule ApiWeb.Router do
  use ApiWeb, :router

  alias ApiWeb.OAuthController

  pipeline :api do
    plug(Plug.Parsers,
      parsers: [:json, Absinthe.Plug.Parser],
      pass: ["*/*"],
      json_decoder: Jason
    )

    plug(:accepts, ["json"])
  end

  scope "/" do
    pipe_through(:api)

    get("/oauth2", OAuthController, :callback)

    post("/graphql", Absinthe.Plug, schema: ApiWeb.Schema)

    forward("/graphiql", Absinthe.Plug.GraphiQL,
      schema: ApiWeb.Schema,
      json_codec: Jason
    )
  end
end

在 Phoenix 项目中实施 Absinthe 有很多不同的组件。我花了一些时间来理解它 - 主要是因为对象定义有点奇怪,你可以制作伪虚拟字段。在查询中实现解析结构引用的能力起初也有点令人困惑。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-05
    • 1970-01-01
    • 1970-01-01
    • 2022-10-04
    • 2014-05-08
    相关资源
    最近更新 更多