mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
whew
This commit is contained in:
parent
c6710f9381
commit
3c1ef49950
20 changed files with 328 additions and 77 deletions
|
@ -2,11 +2,16 @@
|
||||||
locals_without_parens = [
|
locals_without_parens = [
|
||||||
get: 1,
|
get: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
post: 1,
|
|
||||||
attribute: 2,
|
attribute: 2,
|
||||||
attribute: 3,
|
attribute: 3,
|
||||||
belongs_to: 2,
|
belongs_to: 2,
|
||||||
belongs_to: 3,
|
belongs_to: 3,
|
||||||
|
create: 1,
|
||||||
|
create: 2,
|
||||||
|
update: 1,
|
||||||
|
update: 2,
|
||||||
|
delete: 1,
|
||||||
|
delete: 2,
|
||||||
has_one: 2,
|
has_one: 2,
|
||||||
has_one: 3,
|
has_one: 3,
|
||||||
has_many: 2,
|
has_many: 2,
|
||||||
|
|
|
@ -14,3 +14,4 @@
|
||||||
* break up the `Ash` module
|
* break up the `Ash` module
|
||||||
* Wire up/formalize the error handling
|
* Wire up/formalize the error handling
|
||||||
* Ensure that errors are properly propagated up from the data_layer behaviour, and every operation is allowed to fail
|
* Ensure that errors are properly propagated up from the data_layer behaviour, and every operation is allowed to fail
|
||||||
|
* figure out the ecto schema warning
|
16
lib/ash.ex
16
lib/ash.ex
|
@ -14,6 +14,10 @@ defmodule Ash do
|
||||||
Application.get_env(:ash, :resources) || []
|
Application.get_env(:ash, :resources) || []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def primary_key(resource) do
|
||||||
|
resource.primary_key()
|
||||||
|
end
|
||||||
|
|
||||||
def relationship(resource, relationship_name) do
|
def relationship(resource, relationship_name) do
|
||||||
resource.relationship(relationship_name)
|
resource.relationship(relationship_name)
|
||||||
end
|
end
|
||||||
|
@ -55,6 +59,18 @@ defmodule Ash do
|
||||||
Ash.DataLayer.Actions.run_index_action(resource, action, params)
|
Ash.DataLayer.Actions.run_index_action(resource, action, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run_create_action(resource, action, attributes, relationships, params) do
|
||||||
|
Ash.DataLayer.Actions.run_create_action(resource, action, attributes, relationships, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_update_action(record, action, attributes, relationships, params) do
|
||||||
|
Ash.DataLayer.Actions.run_update_action(record, action, attributes, relationships, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_delete_action(record, action, params) do
|
||||||
|
Ash.DataLayer.Actions.run_delete_action(record, action, params)
|
||||||
|
end
|
||||||
|
|
||||||
# TODO: Implement a to_resource protocol, like ecto's to query logic
|
# TODO: Implement a to_resource protocol, like ecto's to query logic
|
||||||
def to_resource(%resource{}), do: resource
|
def to_resource(%resource{}), do: resource
|
||||||
def to_resource(resource) when is_atom(resource), do: resource
|
def to_resource(resource) when is_atom(resource), do: resource
|
||||||
|
|
|
@ -9,6 +9,42 @@ defmodule Ash.Data do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec create(Ash.resource(), Ash.action(), Ash.attributes(), Ash.relationships(), Ash.params()) ::
|
||||||
|
{:ok, Ash.record()} | {:errro, Ash.error()}
|
||||||
|
def create(resource, action, attributes, relationships, params) do
|
||||||
|
Ash.data_layer(resource).create(resource, action, attributes, relationships, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec update(Ash.record(), Ash.action(), Ash.attributes(), Ash.relationships(), Ash.params()) ::
|
||||||
|
{:ok, Ash.record()} | {:errro, Ash.error()}
|
||||||
|
def update(%resource{} = record, action, attributes, relationships, params) do
|
||||||
|
Ash.data_layer(resource).update(record, action, attributes, relationships, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec delete(Ash.record(), Ash.action(), Ash.params()) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
def delete(%resource{} = record, action, params) do
|
||||||
|
Ash.data_layer(resource).delete(record, action, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec append_related(Ash.record(), Ash.relationship(), Ash.resource_identifiers()) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
def append_related(%resource{} = record, relationship, resource_identifiers) do
|
||||||
|
Ash.data_layer(resource).append_related(record, relationship, resource_identifiers)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec delete_related(Ash.record(), Ash.relationship(), Ash.resource_identifiers()) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
def delete_related(%resource{} = record, relationship, resource_identifiers) do
|
||||||
|
Ash.data_layer(resource).delete_related(record, relationship, resource_identifiers)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec replace_related(Ash.record(), Ash.relationship(), Ash.resource_identifiers()) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
def replace_related(%resource{} = record, relationship, resource_identifiers) do
|
||||||
|
Ash.data_layer(resource).replace_related(record, relationship, resource_identifiers)
|
||||||
|
end
|
||||||
|
|
||||||
@spec resource_to_query(Ash.resource()) :: Ash.query()
|
@spec resource_to_query(Ash.resource()) :: Ash.query()
|
||||||
def resource_to_query(resource) do
|
def resource_to_query(resource) do
|
||||||
Ash.data_layer(resource).resource_to_query(resource)
|
Ash.data_layer(resource).resource_to_query(resource)
|
||||||
|
|
|
@ -3,6 +3,18 @@ defmodule Ash.DataLayer.Actions do
|
||||||
Ash.Data.get_by_id(resource, id)
|
Ash.Data.get_by_id(resource, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run_create_action(resource, action, attributes, relationships, params) do
|
||||||
|
Ash.Data.create(resource, action, attributes, relationships, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_update_action(record, action, attributes, relationships, params) do
|
||||||
|
Ash.Data.update(record, action, attributes, relationships, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_delete_action(record, action, params) do
|
||||||
|
Ash.Data.delete(record, action, params)
|
||||||
|
end
|
||||||
|
|
||||||
def run_index_action(resource, _action, params) do
|
def run_index_action(resource, _action, params) do
|
||||||
with {:ok, query} <- Ash.Data.resource_to_query(resource),
|
with {:ok, query} <- Ash.Data.resource_to_query(resource),
|
||||||
{:ok, paginator} <- Ash.DataLayer.Paginator.paginate(resource, query, params),
|
{:ok, paginator} <- Ash.DataLayer.Paginator.paginate(resource, query, params),
|
||||||
|
|
|
@ -15,4 +15,33 @@ defmodule Ash.DataLayer do
|
||||||
{:ok, [Ash.record()]} | {:error, Ash.error()}
|
{:ok, [Ash.record()]} | {:error, Ash.error()}
|
||||||
@callback side_load([Ash.record()], Ash.side_load_keyword(), Ash.resource()) ::
|
@callback side_load([Ash.record()], Ash.side_load_keyword(), Ash.resource()) ::
|
||||||
{:ok, [Ash.resource()]} | {:error, Ash.error()}
|
{:ok, [Ash.resource()]} | {:error, Ash.error()}
|
||||||
|
@callback create(
|
||||||
|
Ash.resource(),
|
||||||
|
Ash.action(),
|
||||||
|
Ash.attributes(),
|
||||||
|
Ash.relationships(),
|
||||||
|
Ash.params()
|
||||||
|
) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
|
||||||
|
@callback update(
|
||||||
|
Ash.record(),
|
||||||
|
Ash.action(),
|
||||||
|
Ash.attributes(),
|
||||||
|
Ash.relationships(),
|
||||||
|
Ash.params()
|
||||||
|
) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
|
||||||
|
@callback delete(Ash.record(), Ash.action(), Ash.params()) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
|
||||||
|
@callback append_related(Ash.record(), Ash.relationship(), Ash.resource_identifiers()) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
|
||||||
|
@callback delete_related(Ash.record(), Ash.relationship(), Ash.resource_identifiers()) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
|
|
||||||
|
@callback replace_related(Ash.record(), Ash.relationship(), Ash.resource_identifiers()) ::
|
||||||
|
{:ok, Ash.record()} | {:error, Ash.error()}
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,9 @@ defmodule Ash.Resource do
|
||||||
Module.register_attribute(__MODULE__, :relationships, accumulate: true)
|
Module.register_attribute(__MODULE__, :relationships, accumulate: true)
|
||||||
Module.register_attribute(__MODULE__, :mix_ins, accumulate: true)
|
Module.register_attribute(__MODULE__, :mix_ins, accumulate: true)
|
||||||
|
|
||||||
@attributes Ash.Resource.Attributes.Attribute.new(:id, :uuid)
|
if unquote(Keyword.get(opts, :primary_key?, true)) do
|
||||||
|
@attributes Ash.Resource.Attributes.Attribute.new(:id, :uuid, primary_key?: true)
|
||||||
|
end
|
||||||
|
|
||||||
# Module.put_attribute(__MODULE__, :custom_threshold_for_lib, 10)
|
# Module.put_attribute(__MODULE__, :custom_threshold_for_lib, 10)
|
||||||
import Ash.Resource
|
import Ash.Resource
|
||||||
|
@ -41,30 +43,46 @@ defmodule Ash.Resource do
|
||||||
raise "Your module (#{inspect(__MODULE__)}) must be in config, :ash, resources: [...]"
|
raise "Your module (#{inspect(__MODULE__)}) must be in config, :ash, resources: [...]"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@sanitized_actions Ash.Resource.mark_primaries(@actions)
|
||||||
|
@ash_primary_key Ash.Resource.primary_key(@attributes)
|
||||||
|
|
||||||
|
unless @ash_primary_key do
|
||||||
|
raise "Must have a primary key for a resource: #{__MODULE__}"
|
||||||
|
end
|
||||||
|
|
||||||
def type() do
|
def type() do
|
||||||
@resource_type
|
@resource_type
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationship(_name) do
|
def create(action, attributes, parameters) do
|
||||||
nil
|
data_layer().create(__MODULE__, action, attributes, parameters)
|
||||||
|
end
|
||||||
|
|
||||||
|
def relationship(name) do
|
||||||
|
# TODO: Make this happen at compile time
|
||||||
|
Enum.find(relationships(), &(&1.name == name))
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships() do
|
def relationships() do
|
||||||
@relationships
|
@relationships
|
||||||
end
|
end
|
||||||
|
|
||||||
def action(_name) do
|
def action(name) do
|
||||||
nil
|
Enum.find(actions(), &(&1.name == name))
|
||||||
end
|
end
|
||||||
|
|
||||||
def actions() do
|
def actions() do
|
||||||
@actions
|
@sanitized_actions
|
||||||
end
|
end
|
||||||
|
|
||||||
def attributes() do
|
def attributes() do
|
||||||
@attributes
|
@attributes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def primary_key() do
|
||||||
|
@ash_primary_key
|
||||||
|
end
|
||||||
|
|
||||||
def name() do
|
def name() do
|
||||||
@name
|
@name
|
||||||
end
|
end
|
||||||
|
@ -91,4 +109,46 @@ defmodule Ash.Resource do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def primary_key(attributes) do
|
||||||
|
attributes
|
||||||
|
|> Enum.filter(& &1.primary_key?)
|
||||||
|
|> Enum.map(& &1.name)
|
||||||
|
|> case do
|
||||||
|
[] ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
[single] ->
|
||||||
|
single
|
||||||
|
|
||||||
|
other ->
|
||||||
|
other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def mark_primaries(all_actions) do
|
||||||
|
all_actions
|
||||||
|
|> Enum.group_by(& &1.type)
|
||||||
|
|> Enum.flat_map(fn {type, actions} ->
|
||||||
|
case actions do
|
||||||
|
[action] ->
|
||||||
|
[%{action | primary?: true}]
|
||||||
|
|
||||||
|
actions ->
|
||||||
|
case Enum.count(actions, & &1.primary?) do
|
||||||
|
0 ->
|
||||||
|
# TODO: Format these prettier
|
||||||
|
raise "Must declare a primary action for #{type}, as there are more than one."
|
||||||
|
|
||||||
|
1 ->
|
||||||
|
actions
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
raise "Duplicate primary actions declared for #{type}, but there can only be one primary action."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
defmodule Ash.Resource.Actions.Action do
|
|
||||||
defstruct [:type, :name, :path]
|
|
||||||
|
|
||||||
def new(name, type, _opts \\ []) do
|
|
||||||
%__MODULE__{
|
|
||||||
name: name,
|
|
||||||
type: type
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -7,31 +7,48 @@ defmodule Ash.Resource.Actions do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmacro get(name \\ :get, _opts \\ []) do
|
# TODO: Originally I had it in my mind that you had to set up your own actions
|
||||||
quote do
|
# for the basic capabilities. Instead, these capabilities will just automatically exist
|
||||||
action = Ash.Resource.Actions.Action.new(unquote(name), :get)
|
# for all resources. What you can do is create actions that are a variation of one of the
|
||||||
|
# basic kinds of resource actions, with special rules. That will be hooked up later.
|
||||||
|
|
||||||
@actions action
|
# defmacro create(name \\ :create, opts \\ []) do
|
||||||
|
# quote bind_quoted: [name: name, opts: opts] do
|
||||||
|
# action = Ash.Resource.Actions.Create.new(name, primary?: opts[:primary?] || false)
|
||||||
|
|
||||||
@current_action action
|
# @actions action
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
def action(unquote(name)) do
|
# defmacro update(name \\ :update, opts \\ []) do
|
||||||
@current_action
|
# quote bind_quoted: [name: name, opts: opts] do
|
||||||
end
|
# action = Ash.Resource.Actions.Update.new(name, primary?: opts[:primary?] || false)
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro index(name \\ :index, _opts \\ []) do
|
# @actions action
|
||||||
quote do
|
# end
|
||||||
action = Ash.Resource.Actions.Action.new(unquote(name), :index)
|
# end
|
||||||
|
|
||||||
@actions action
|
# defmacro delete(name \\ :delete, opts \\ []) do
|
||||||
|
# quote bind_quoted: [name: name, opts: opts] do
|
||||||
|
# action = Ash.Resource.Actions.Delete.new(name, primary?: opts[:primary?] || false)
|
||||||
|
|
||||||
@current_action action
|
# @actions action
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
def action(unquote(name)) do
|
# defmacro get(name \\ :get, opts \\ []) do
|
||||||
@current_action
|
# quote bind_quoted: [name: name, opts: opts] do
|
||||||
end
|
# action = Ash.Resource.Actions.Get.new(name, primary?: opts[:primary?] || false)
|
||||||
end
|
|
||||||
end
|
# @actions action
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# defmacro index(name \\ :index, opts \\ []) do
|
||||||
|
# quote bind_quoted: [name: name, opts: opts] do
|
||||||
|
# action = Ash.Resource.Actions.Index.new(name, primary?: opts[:primary?] || false)
|
||||||
|
|
||||||
|
# @actions action
|
||||||
|
# end
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
|
|
11
lib/ash/resource/actions/create.ex
Normal file
11
lib/ash/resource/actions/create.ex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Ash.Resource.Actions.Create do
|
||||||
|
defstruct [:type, :name, :primary?]
|
||||||
|
|
||||||
|
def new(name, opts \\ []) do
|
||||||
|
%__MODULE__{
|
||||||
|
name: name,
|
||||||
|
type: :create,
|
||||||
|
primary?: opts[:primary?]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
11
lib/ash/resource/actions/delete.ex
Normal file
11
lib/ash/resource/actions/delete.ex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Ash.Resource.Actions.Delete do
|
||||||
|
defstruct [:type, :name, :primary?]
|
||||||
|
|
||||||
|
def new(name, opts \\ []) do
|
||||||
|
%__MODULE__{
|
||||||
|
name: name,
|
||||||
|
type: :delete,
|
||||||
|
primary?: opts[:primary?]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
11
lib/ash/resource/actions/get.ex
Normal file
11
lib/ash/resource/actions/get.ex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Ash.Resource.Actions.Get do
|
||||||
|
defstruct [:type, :name, :primary?]
|
||||||
|
|
||||||
|
def new(name, opts \\ []) do
|
||||||
|
%__MODULE__{
|
||||||
|
name: name,
|
||||||
|
type: :get,
|
||||||
|
primary?: opts[:primary?]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
11
lib/ash/resource/actions/index.ex
Normal file
11
lib/ash/resource/actions/index.ex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Ash.Resource.Actions.Index do
|
||||||
|
defstruct [:type, :name, :primary?]
|
||||||
|
|
||||||
|
def new(name, opts \\ []) do
|
||||||
|
%__MODULE__{
|
||||||
|
name: name,
|
||||||
|
type: :index,
|
||||||
|
primary?: opts[:primary?]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
11
lib/ash/resource/actions/update.ex
Normal file
11
lib/ash/resource/actions/update.ex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Ash.Resource.Actions.Update do
|
||||||
|
defstruct [:type, :name, :primary?]
|
||||||
|
|
||||||
|
def new(name, opts \\ []) do
|
||||||
|
%__MODULE__{
|
||||||
|
name: name,
|
||||||
|
type: :update,
|
||||||
|
primary?: opts[:primary?]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,7 +1,9 @@
|
||||||
defmodule Ash.Resource.Attributes.Attribute do
|
defmodule Ash.Resource.Attributes.Attribute do
|
||||||
defstruct [:name, :type, :ecto_type]
|
defstruct [:name, :type, :ecto_type, :primary_key?]
|
||||||
|
|
||||||
def new(name, type, _opts \\ []) do
|
def new(name, type, opts \\ []) do
|
||||||
|
# TODO: Remove `ecto_type` here and do that mapping in
|
||||||
|
# the database layer
|
||||||
ecto_type =
|
ecto_type =
|
||||||
if type == :uuid do
|
if type == :uuid do
|
||||||
:binary_id
|
:binary_id
|
||||||
|
@ -12,7 +14,8 @@ defmodule Ash.Resource.Attributes.Attribute do
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
ecto_type: ecto_type
|
ecto_type: ecto_type,
|
||||||
|
primary_key?: opts[:primary_key?] || false
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
||||||
:type,
|
:type,
|
||||||
:path,
|
:path,
|
||||||
:destination,
|
:destination,
|
||||||
|
:primary_key?,
|
||||||
:side_load,
|
:side_load,
|
||||||
:destination_field,
|
:destination_field,
|
||||||
:source_field
|
:source_field
|
||||||
|
@ -29,10 +30,14 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
||||||
type: :belongs_to,
|
type: :belongs_to,
|
||||||
cardinality: :one,
|
cardinality: :one,
|
||||||
path: path,
|
path: path,
|
||||||
|
primary_key?: Keyword.get(opts, :primary_key, false),
|
||||||
destination: related_resource,
|
destination: related_resource,
|
||||||
destination_field: opts[:destination_field] || "id",
|
destination_field: atomize(opts[:destination_field] || "id"),
|
||||||
source_field: opts[:source_field] || "#{name}_id",
|
source_field: atomize(opts[:source_field] || "#{name}_id"),
|
||||||
side_load: opts[:side_load]
|
side_load: opts[:side_load]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp atomize(value) when is_atom(value), do: value
|
||||||
|
defp atomize(value) when is_bitstring(value), do: String.to_atom(value)
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ defmodule Ash.Resource.Relationships.HasMany do
|
||||||
related_resource :: Ash.resource(),
|
related_resource :: Ash.resource(),
|
||||||
opts :: Keyword.t()
|
opts :: Keyword.t()
|
||||||
) :: t()
|
) :: t()
|
||||||
def new(resource_name, name, related_resource, opts \\ []) do
|
def new(resource_name, resource_type, name, related_resource, opts \\ []) do
|
||||||
path = opts[:path] || resource_name <> "/:id/" <> to_string(name)
|
path = opts[:path] || resource_name <> "/:id/" <> to_string(name)
|
||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
|
@ -30,9 +30,12 @@ defmodule Ash.Resource.Relationships.HasMany do
|
||||||
cardinality: :many,
|
cardinality: :many,
|
||||||
path: path,
|
path: path,
|
||||||
destination: related_resource,
|
destination: related_resource,
|
||||||
destination_field: opts[:destination_field] || "#{resource_name}_id",
|
destination_field: atomize(opts[:destination_field] || "#{resource_type}_id"),
|
||||||
source_field: opts[:source_field] || "id",
|
source_field: atomize(opts[:source_field] || "id"),
|
||||||
side_load: opts[:side_load]
|
side_load: opts[:side_load]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp atomize(value) when is_atom(value), do: value
|
||||||
|
defp atomize(value) when is_bitstring(value), do: String.to_atom(value)
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,9 +30,12 @@ defmodule Ash.Resource.Relationships.HasOne do
|
||||||
cardinality: :one,
|
cardinality: :one,
|
||||||
path: path,
|
path: path,
|
||||||
destination: related_resource,
|
destination: related_resource,
|
||||||
destination_field: opts[:destination_field] || "#{resource_name}_id",
|
destination_field: atomize(opts[:destination_field] || "#{resource_name}_id"),
|
||||||
source_field: opts[:source_field] || "id",
|
source_field: atomize(opts[:source_field] || "id"),
|
||||||
side_load: opts[:side_load]
|
side_load: opts[:side_load]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp atomize(value) when is_atom(value), do: value
|
||||||
|
defp atomize(value) when is_bitstring(value), do: String.to_atom(value)
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,14 +27,24 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
||||||
def new(resource_name, name, related_resource, opts \\ []) do
|
def new(resource_name, name, related_resource, opts \\ []) do
|
||||||
path = opts[:path] || resource_name <> "/:id/" <> to_string(name)
|
path = opts[:path] || resource_name <> "/:id/" <> to_string(name)
|
||||||
|
|
||||||
through = through!(opts)
|
|
||||||
|
|
||||||
source_field_on_join_table =
|
source_field_on_join_table =
|
||||||
opts[:source_field_on_join_table] || String.to_atom(resource_name <> "_id")
|
atomize(opts[:source_field_on_join_table] || String.to_atom(resource_name <> "_id"))
|
||||||
|
|
||||||
destination_field_on_join_table =
|
destination_field_on_join_table =
|
||||||
|
atomize(
|
||||||
opts[:destination_field_on_join_table] ||
|
opts[:destination_field_on_join_table] ||
|
||||||
String.to_atom(Ash.name(related_resource) <> "_id")
|
String.to_atom(Ash.name(related_resource) <> "_id")
|
||||||
|
)
|
||||||
|
|
||||||
|
source_field = atomize(opts[:source_field] || :id)
|
||||||
|
destination_field = atomize(opts[:destination_field] || :id)
|
||||||
|
|
||||||
|
through =
|
||||||
|
through!(
|
||||||
|
opts,
|
||||||
|
source_field_on_join_table,
|
||||||
|
destination_field_on_join_table
|
||||||
|
)
|
||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -44,20 +54,41 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
||||||
through: through,
|
through: through,
|
||||||
side_load: opts[:side_load],
|
side_load: opts[:side_load],
|
||||||
destination: related_resource,
|
destination: related_resource,
|
||||||
source_field: opts[:source_field] || :id,
|
source_field: source_field,
|
||||||
destination_field: opts[:destination_field] || :id,
|
destination_field: destination_field,
|
||||||
source_field_on_join_table: source_field_on_join_table,
|
source_field_on_join_table: source_field_on_join_table,
|
||||||
destination_field_on_join_table: destination_field_on_join_table
|
destination_field_on_join_table: destination_field_on_join_table
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp through!(opts) do
|
defp atomize(value) when is_atom(value), do: value
|
||||||
|
defp atomize(value) when is_bitstring(value), do: String.to_atom(value)
|
||||||
|
|
||||||
|
defp through!(opts, source_field_on_join_table, destination_field_on_join_table) do
|
||||||
case opts[:through] do
|
case opts[:through] do
|
||||||
|
through when is_atom(through) ->
|
||||||
|
unless through in Ash.resources() do
|
||||||
|
raise "Got an atom/module for `through`, but it was not a resource."
|
||||||
|
end
|
||||||
|
|
||||||
|
case Ash.primary_key(through) do
|
||||||
|
[^source_field_on_join_table, ^destination_field_on_join_table] ->
|
||||||
|
through
|
||||||
|
|
||||||
|
[^destination_field_on_join_table, ^source_field_on_join_table] ->
|
||||||
|
through
|
||||||
|
|
||||||
|
other ->
|
||||||
|
raise "The primary key of a join table must be the same as the fields that are used for joining. Needed: #{
|
||||||
|
inspect([destination_field_on_join_table, source_field_on_join_table])
|
||||||
|
} got #{other}"
|
||||||
|
end
|
||||||
|
|
||||||
through when is_bitstring(through) ->
|
through when is_bitstring(through) ->
|
||||||
through
|
through
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
raise "`:through` option must be a string representing a join table"
|
raise "`:through` option must be a string representing a join table or a module representinga resource"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,11 +18,6 @@ defmodule Ash.Resource.Relationships do
|
||||||
)
|
)
|
||||||
|
|
||||||
@relationships relationship
|
@relationships relationship
|
||||||
@current_relationship relationship
|
|
||||||
|
|
||||||
def relationship(unquote(relationship_name)) do
|
|
||||||
@current_relationship
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -36,12 +31,11 @@ defmodule Ash.Resource.Relationships do
|
||||||
unquote(config)
|
unquote(config)
|
||||||
)
|
)
|
||||||
|
|
||||||
@relationships relationship
|
@attributes Ash.Resource.Attributes.Attribute.new(relationship.source_field, :binary_id,
|
||||||
@current_relationship relationship
|
primary_key?: relationship.primary_key?
|
||||||
|
)
|
||||||
|
|
||||||
def relationship(unquote(relationship_name)) do
|
@relationships relationship
|
||||||
@current_relationship
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,17 +44,13 @@ defmodule Ash.Resource.Relationships do
|
||||||
relationship =
|
relationship =
|
||||||
Ash.Resource.Relationships.HasMany.new(
|
Ash.Resource.Relationships.HasMany.new(
|
||||||
@name,
|
@name,
|
||||||
|
@resource_type,
|
||||||
unquote(relationship_name),
|
unquote(relationship_name),
|
||||||
unquote(resource),
|
unquote(resource),
|
||||||
unquote(config)
|
unquote(config)
|
||||||
)
|
)
|
||||||
|
|
||||||
@relationships relationship
|
@relationships relationship
|
||||||
@current_relationship relationship
|
|
||||||
|
|
||||||
def relationship(unquote(relationship_name)) do
|
|
||||||
@current_relationship
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -75,11 +65,6 @@ defmodule Ash.Resource.Relationships do
|
||||||
)
|
)
|
||||||
|
|
||||||
@relationships relationship
|
@relationships relationship
|
||||||
@current_relationship relationship
|
|
||||||
|
|
||||||
def relationship(unquote(relationship_name)) do
|
|
||||||
@current_relationship
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue