feat: Add property: private? for attributes, relationships, aggregates, and calculations (#140)

This commit is contained in:
Kyle Nguyen 2020-11-03 04:33:14 +08:00 committed by GitHub
parent 30dab24554
commit c732099240
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 436 additions and 37 deletions

View file

@ -162,6 +162,7 @@ defmodule Ash.Resource do
:persistent_term.get({resource, :primary_key}, [])
end
@doc "Returns all relationships of a resource"
@spec relationships(Ash.resource()) :: list(Ash.relationship())
def relationships(resource) do
Extension.get_entities(resource, [:relationships])
@ -196,6 +197,37 @@ defmodule Ash.Resource do
|> Enum.find(&(&1.name == relationship_name))
end
@doc "Returns all public relationships of a resource"
@spec public_relationships(Ash.resource()) :: list(Ash.relationship())
def public_relationships(resource) do
resource
|> relationships()
|> Enum.reject(& &1.private?)
end
@doc "Get a public relationship by name or path"
def public_relationship(resource, [name | rest]) do
case public_relationship(resource, name) do
nil ->
nil
relationship ->
public_relationship(relationship.destination, rest)
end
end
def public_relationship(resource, relationship_name) when is_binary(relationship_name) do
resource
|> relationships()
|> Enum.find(&(to_string(&1.name) == relationship_name && !&1.private?))
end
def public_relationship(resource, relationship_name) do
resource
|> relationships()
|> Enum.find(&(&1.name == relationship_name && !&1.private?))
end
@doc "Get the multitenancy strategy for a resource"
@spec multitenancy_strategy(Ash.resource()) :: :context | :attribute | nil
def multitenancy_strategy(resource) do
@ -235,11 +267,14 @@ defmodule Ash.Resource do
Ash.Dsl.Extension.get_opt(resource, [:multitenancy], :template, nil)
end
@doc "Returns all calculations of a resource"
@spec calculations(Ash.resource()) :: list(Ash.calculation())
def calculations(resource) do
Extension.get_entities(resource, [:calculations])
end
@doc "Get a calculation by name"
@spec calculation(Ash.resource(), atom | String.t()) :: Ash.calculation() | nil
def calculation(resource, name) when is_binary(name) do
resource
|> calculations()
@ -252,11 +287,36 @@ defmodule Ash.Resource do
|> Enum.find(&(&1.name == name))
end
@doc "Returns all public calculations of a resource"
@spec public_calculations(Ash.resource()) :: list(Ash.calculation())
def public_calculations(resource) do
resource
|> Extension.get_entities([:calculations])
|> Enum.reject(& &1.private?)
end
@doc "Get a public calculation by name"
@spec public_calculation(Ash.resource(), atom | String.t()) :: Ash.calculation() | nil
def public_calculation(resource, name) when is_binary(name) do
resource
|> calculations()
|> Enum.find(&(to_string(&1.name) == name && !&1.private?))
end
def public_calculation(resource, name) do
resource
|> calculations()
|> Enum.find(&(&1.name == name && !&1.private?))
end
@doc "Returns all aggregates of a resource"
@spec aggregates(Ash.resource()) :: list(Ash.aggregate())
def aggregates(resource) do
Extension.get_entities(resource, [:aggregates])
end
@doc "Get an aggregate by name"
@spec aggregate(Ash.resource(), atom | String.t()) :: Ash.aggregate() | nil
def aggregate(resource, name) when is_binary(name) do
resource
|> aggregates()
@ -269,6 +329,28 @@ defmodule Ash.Resource do
|> Enum.find(&(&1.name == name))
end
@doc "Returns all public aggregates of a resource"
@spec public_aggregates(Ash.resource()) :: list(Ash.aggregate())
def public_aggregates(resource) do
resource
|> Extension.get_entities([:aggregates])
|> Enum.reject(& &1.private?)
end
@doc "Get an aggregate by name"
@spec public_aggregate(Ash.resource(), atom | String.t()) :: Ash.aggregate() | nil
def public_aggregate(resource, name) when is_binary(name) do
resource
|> aggregates()
|> Enum.find(&(to_string(&1.name) == name && !&1.private?))
end
def public_aggregate(resource, name) do
resource
|> aggregates()
|> Enum.find(&(&1.name == name && !&1.private?))
end
@doc "Returns the primary action of the given type"
@spec primary_action!(Ash.resource(), Ash.action_type()) :: Ash.action() | no_return
def primary_action!(resource, type) do
@ -324,6 +406,28 @@ defmodule Ash.Resource do
|> Enum.find(&(&1.name == name))
end
@doc "Returns all public attributes of a resource"
@spec public_attributes(Ash.resource()) :: [Ash.attribute()]
def public_attributes(resource) do
resource
|> attributes()
|> Enum.reject(& &1.private?)
end
@doc "Get a public attribute name from the resource"
@spec public_attribute(Ash.resource(), String.t() | atom) :: Ash.attribute() | nil
def public_attribute(resource, name) when is_binary(name) do
resource
|> attributes()
|> Enum.find(&(to_string(&1.name) == name && !&1.private?))
end
def public_attribute(resource, name) do
resource
|> attributes()
|> Enum.find(&(&1.name == name && !&1.private?))
end
@spec related(Ash.resource(), atom() | String.t() | [atom() | String.t()]) ::
Ash.resource() | nil
def related(resource, relationship) when not is_list(relationship) do

View file

@ -1,6 +1,6 @@
defmodule Ash.Resource.Aggregate do
@moduledoc "Represents a named aggregate on the resource that can be loaded"
defstruct [:name, :relationship_path, :filter, :kind, :description]
defstruct [:name, :relationship_path, :filter, :kind, :description, :private?]
@schema [
name: [
@ -26,6 +26,12 @@ defmodule Ash.Resource.Aggregate do
description: [
type: :string,
doc: "An optional description for the aggregate"
],
private?: [
type: :boolean,
default: false,
doc:
"Whether or not the aggregate will appear in any interfaces created off of this resource, e.g AshJsonApi and AshGraphql"
]
]
@ -34,7 +40,8 @@ defmodule Ash.Resource.Aggregate do
relationship_path: {:ok, list(atom())} | {:error, String.t()},
filter: Keyword.t(),
kind: :count,
description: String.t() | nil
description: String.t() | nil,
private?: boolean
}
@doc false

View file

@ -7,6 +7,7 @@ defmodule Ash.Resource.Attribute do
:allow_nil?,
:generated?,
:primary_key?,
:private?,
:writable?,
:default,
:update_default,
@ -19,9 +20,10 @@ defmodule Ash.Resource.Attribute do
constraints: Keyword.t(),
type: Ash.Type.t(),
primary_key?: boolean(),
private?: boolean(),
default: (() -> term),
update_default: (() -> term) | (Ash.record() -> term),
writable?: boolean
writable?: boolean()
}
alias Ash.OptionsHelpers
@ -62,6 +64,12 @@ defmodule Ash.Resource.Attribute do
default: true,
doc: "Whether or not the value can be written to"
],
private?: [
type: :boolean,
default: false,
doc:
"Whether or not the attribute will appear in any interfaces created off of this resource, e.g AshJsonApi and AshGraphql"
],
update_default: [
type: {:custom, Ash.OptionsHelpers, :default, []},
doc:

View file

@ -1,6 +1,6 @@
defmodule Ash.Resource.Calculation do
@moduledoc "Represents a named calculation on a resource"
defstruct [:name, :calculation, :arguments, :description]
defstruct [:name, :calculation, :arguments, :description, :private?]
@schema [
name: [
@ -16,6 +16,12 @@ defmodule Ash.Resource.Calculation do
description: [
type: :string,
doc: "An optional description for the calculation"
],
private?: [
type: :boolean,
default: false,
doc:
"Whether or not the calculation will appear in any interfaces created off of this resource, e.g AshJsonApi and AshGraphql"
]
]
@ -23,7 +29,8 @@ defmodule Ash.Resource.Calculation do
name: atom(),
calculation: {:ok, {atom(), any()}} | {:error, String.t()},
arguments: list(any()),
description: String.t() | nil
description: String.t() | nil,
private?: boolean
}
defmodule Argument do

View file

@ -8,6 +8,7 @@ defmodule Ash.Resource.Relationships.BelongsTo do
:define_field?,
:field_type,
:destination_field,
:private?,
:source_field,
:source,
:required?,
@ -29,6 +30,7 @@ defmodule Ash.Resource.Relationships.BelongsTo do
define_field?: boolean,
field_type: Ash.Type.t(),
destination_field: atom,
private?: boolean,
source_field: atom | nil,
description: String.t()
}

View file

@ -4,6 +4,7 @@ defmodule Ash.Resource.Relationships.HasMany do
:name,
:destination,
:destination_field,
:private?,
:source_field,
:source,
:writable?,
@ -21,6 +22,7 @@ defmodule Ash.Resource.Relationships.HasMany do
type: Ash.Type.t(),
destination: Ash.resource(),
destination_field: atom,
private?: boolean,
source_field: atom,
description: String.t()
}

View file

@ -6,6 +6,7 @@ defmodule Ash.Resource.Relationships.HasOne do
:source,
:destination,
:destination_field,
:private?,
:source_field,
:allow_orphans?,
:writable?,
@ -23,6 +24,7 @@ defmodule Ash.Resource.Relationships.HasOne do
type: Ash.Type.t(),
destination: Ash.resource(),
destination_field: atom,
private?: boolean,
source_field: atom,
allow_orphans?: boolean,
description: String.t()

View file

@ -12,6 +12,7 @@ defmodule Ash.Resource.Relationships.ManyToMany do
:join_relationship,
:join_attributes,
:writable?,
:private?,
:description,
cardinality: :many,
type: :many_to_many
@ -22,6 +23,7 @@ defmodule Ash.Resource.Relationships.ManyToMany do
cardinality: :many,
source: Ash.resource(),
writable?: boolean,
private?: boolean,
name: atom,
through: Ash.resource(),
destination: Ash.resource(),

View file

@ -28,6 +28,12 @@ defmodule Ash.Resource.Relationships.SharedOptions do
description: [
type: :string,
doc: "An optional description for the relationship"
],
private?: [
type: :boolean,
default: false,
doc:
"Whether or not the relationship will appear in any interfaces created off of this resource, e.g AshJsonApi and AshGraphql"
]
]

View file

@ -24,6 +24,7 @@ defmodule Ash.Resource.Transformers.BelongsToAttribute do
name: relationship.source_field,
type: relationship.field_type,
writable?: false,
private?: true,
primary_key?: relationship.primary_key?
) do
{:ok, attribute} ->

View file

@ -26,7 +26,8 @@ defmodule Ash.Resource.Transformers.CreateJoinRelationship do
name: relationship.join_relationship,
destination: relationship.through,
destination_field: relationship.source_field_on_join_table,
source_field: relationship.source_field
source_field: relationship.source_field,
private?: true
)
{:ok, Transformer.add_entity(dsl_state, [:relationships], relationship)}

View file

@ -2,6 +2,8 @@ defmodule Ash.Test.Resource.AggregatesTest do
@moduledoc false
use ExUnit.Case, async: true
alias Ash.Resource.Aggregate
defmodule Comment do
@moduledoc false
use Ash.Resource
@ -32,6 +34,7 @@ defmodule Ash.Test.Resource.AggregatesTest do
defposts do
aggregates do
count :count_of_comments, :comments
count :another_count_but_private, :comments, private?: true
end
relationships do
@ -40,11 +43,26 @@ defmodule Ash.Test.Resource.AggregatesTest do
end
assert [
%Ash.Resource.Aggregate{
%Aggregate{
name: :count_of_comments,
relationship_path: [:comments]
relationship_path: [:comments],
private?: false
},
%Aggregate{
name: :another_count_but_private,
relationship_path: [:comments],
private?: true
}
] = Ash.Resource.aggregates(Post)
assert [%Aggregate{name: :count_of_comments}] = Ash.Resource.public_aggregates(Post)
assert %Aggregate{name: :another_count_but_private} =
Ash.Resource.aggregate(Post, :another_count_but_private)
assert nil == Ash.Resource.public_aggregate(Post, :another_count_but_private)
assert nil == Ash.Resource.aggregate(Post, :totally_legit_aggregate)
end
test "Aggregate descriptions are allowed" do

View file

@ -2,6 +2,8 @@ defmodule Ash.Test.Resource.AttributesTest do
@moduledoc false
use ExUnit.Case, async: true
alias Ash.Resource.Attribute
defmacrop defposts(do: body) do
quote do
defmodule Post do
@ -22,11 +24,28 @@ defmodule Ash.Test.Resource.AttributesTest do
defposts do
attributes do
attribute :foo, :string
attribute :bar, :boolean, private?: true
end
end
assert [_, %Ash.Resource.Attribute{name: :foo, type: Ash.Type.String, primary_key?: false}] =
Ash.Resource.attributes(Post)
assert [
_,
%Attribute{name: :foo, type: Ash.Type.String, primary_key?: false},
%Attribute{
name: :bar,
type: Ash.Type.Boolean,
primary_key?: false,
private?: true
}
] = Ash.Resource.attributes(Post)
assert [_, %Attribute{name: :foo}] = Ash.Resource.public_attributes(Post)
assert %Attribute{name: :bar} = Ash.Resource.attribute(Post, :bar)
assert nil == Ash.Resource.attribute(Post, :totally_valid_attributes)
assert nil == Ash.Resource.public_attribute(Post, :bar)
end
end
@ -72,5 +91,19 @@ defmodule Ash.Test.Resource.AttributesTest do
end
)
end
test "raises if you pass an invalid value for `private?`" do
assert_raise(
Ash.Error.Dsl.DslError,
"[Ash.Resource.Dsl.Attribute]\n attributes -> attribute -> foo:\n expected :private? to be an boolean, got: \"an_invalid_value\"",
fn ->
defposts do
attributes do
attribute :foo, :string, private?: "an_invalid_value"
end
end
end
)
end
end
end

View file

@ -2,6 +2,8 @@ defmodule Ash.Test.Resource.CalculationsTest do
@moduledoc false
use ExUnit.Case, async: true
alias Ash.Resource.Calculation
defmacrop defposts(do: body) do
quote do
defmodule Post do
@ -25,16 +27,31 @@ defmodule Ash.Test.Resource.CalculationsTest do
defposts do
calculations do
calculate :name_and_contents, concat([:name, :context])
calculate :another_cal_but_private, concat([:name, :context]), private?: true
end
end
assert [
%Ash.Resource.Calculation{
%Calculation{
name: :name_and_contents,
calculation:
{Ash.Resource.Calculation.Concat, [keys: [:name, :context], separator: ""]}
calculation: {Calculation.Concat, [keys: [:name, :context], separator: ""]},
private?: false
},
%Calculation{
name: :another_cal_but_private,
calculation: {Calculation.Concat, [keys: [:name, :context], separator: ""]},
private?: true
}
] = Ash.Resource.calculations(Post)
assert [%Calculation{name: :name_and_contents}] = Ash.Resource.public_calculations(Post)
assert %Calculation{name: :another_cal_but_private} =
Ash.Resource.calculation(Post, :another_cal_but_private)
assert nil == Ash.Resource.public_calculation(Post, :another_cal_but_private)
assert nil == Ash.Resource.calculation(Post, :totally_legit_calculation)
end
test "Calculation descriptions are allowed" do

View file

@ -2,6 +2,8 @@ defmodule Ash.Test.Resource.Relationships.BelongsToTest do
@moduledoc false
use ExUnit.Case, async: true
alias Ash.Resource.Relationships.BelongsTo
defmacrop defposts(do: body) do
quote do
defmodule Post do
@ -29,7 +31,26 @@ defmodule Ash.Test.Resource.Relationships.BelongsToTest do
%Ash.Resource.Attribute{
name: :foobar_id,
primary_key?: false,
type: Ash.Type.UUID
type: Ash.Type.UUID,
private?: true
},
_
] = Ash.Resource.attributes(Post)
end
test "it creates an attribute that honors private?" do
defposts do
relationships do
belongs_to(:foobar, FooBar, private?: true)
end
end
assert [
%Ash.Resource.Attribute{
name: :foobar_id,
primary_key?: false,
type: Ash.Type.UUID,
private?: true
},
_
] = Ash.Resource.attributes(Post)
@ -38,23 +59,45 @@ defmodule Ash.Test.Resource.Relationships.BelongsToTest do
test "it creates a relationship" do
defposts do
relationships do
belongs_to(:foobar, FooBar)
belongs_to(:foo, Foo)
belongs_to(:bar, Bar, source_field: :bazz, private?: true)
end
end
assert [
%Ash.Resource.Relationships.BelongsTo{
%BelongsTo{
cardinality: :one,
define_field?: true,
destination: FooBar,
destination: Foo,
destination_field: :id,
field_type: :uuid,
name: :foobar,
name: :foo,
primary_key?: false,
source_field: :foobar_id,
type: :belongs_to
source_field: :foo_id,
type: :belongs_to,
private?: false
},
%BelongsTo{
cardinality: :one,
define_field?: true,
destination: Bar,
destination_field: :id,
field_type: :uuid,
name: :bar,
primary_key?: false,
source_field: :bazz,
type: :belongs_to,
private?: true
}
] = Ash.Resource.relationships(Post)
assert [%BelongsTo{name: :foo}] = Ash.Resource.public_relationships(Post)
assert %BelongsTo{name: :foo} = Ash.Resource.public_relationship(Post, :foo)
assert nil == Ash.Resource.relationship(Post, :definitely_legit_relationship)
assert nil == Ash.Resource.public_relationship(Post, :bar)
end
end
@ -128,6 +171,20 @@ defmodule Ash.Test.Resource.Relationships.BelongsToTest do
end
)
end
test "fails if `private?` is not a boolean" do
assert_raise(
Ash.Error.Dsl.DslError,
"[Ash.Resource.Dsl.BelongsTo]\n relationships -> belongs_to -> foobar:\n expected :private? to be an boolean, got: \"blah\"",
fn ->
defposts do
relationships do
belongs_to(:foobar, Foobar, private?: "blah")
end
end
end
)
end
end
test "fails if `define_field?` is not a boolean" do

View file

@ -2,6 +2,8 @@ defmodule Ash.Test.Resource.Relationshihps.HasManyTest do
@moduledoc false
use ExUnit.Case, async: true
alias Ash.Resource.Relationships.HasMany
defmacrop defposts(do: body) do
quote do
defmodule Post do
@ -21,20 +23,39 @@ defmodule Ash.Test.Resource.Relationshihps.HasManyTest do
test "it creates a relationship" do
defposts do
relationships do
has_many :foobar, FooBar, destination_field: :post_id
has_many :foo, Foo, destination_field: :post_id
has_many :bar, Bar, destination_field: :bazz, private?: true
end
end
assert [
%Ash.Resource.Relationships.HasMany{
%HasMany{
cardinality: :many,
destination: FooBar,
destination: Foo,
destination_field: :post_id,
name: :foobar,
name: :foo,
source_field: :id,
type: :has_many
type: :has_many,
private?: false
},
%HasMany{
cardinality: :many,
destination: Bar,
destination_field: :bazz,
name: :bar,
source_field: :id,
type: :has_many,
private?: true
}
] = Ash.Resource.relationships(Post)
assert [%HasMany{name: :foo}] = Ash.Resource.public_relationships(Post)
assert %HasMany{name: :foo} = Ash.Resource.public_relationship(Post, :foo)
assert nil == Ash.Resource.relationship(Post, :definitely_legit_relationship)
assert nil == Ash.Resource.public_relationship(Post, :bar)
end
end
@ -94,5 +115,19 @@ defmodule Ash.Test.Resource.Relationshihps.HasManyTest do
end
)
end
test "fails if private? is not an boolean" do
assert_raise(
Ash.Error.Dsl.DslError,
"[Ash.Resource.Dsl.HasMany]\n relationships -> has_many -> foobar:\n expected :private? to be an boolean, got: \"foo\"",
fn ->
defposts do
relationships do
has_many :foobar, FooBar, private?: "foo", destination_field: :post_id
end
end
end
)
end
end
end

View file

@ -2,6 +2,8 @@ defmodule Ash.Test.Resource.Relationshihps.HasOneTest do
@moduledoc false
use ExUnit.Case, async: true
alias Ash.Resource.Relationships.HasOne
defmacrop defposts(do: body) do
quote do
defmodule Post do
@ -21,20 +23,39 @@ defmodule Ash.Test.Resource.Relationshihps.HasOneTest do
test "it creates a relationship" do
defposts do
relationships do
has_one :foobar, FooBar, destination_field: :post_id
has_one :foo, Foo, destination_field: :post_id
has_one :bar, Bar, destination_field: :post_id, private?: true
end
end
assert [
%Ash.Resource.Relationships.HasOne{
%HasOne{
cardinality: :one,
destination: FooBar,
destination: Foo,
destination_field: :post_id,
name: :foobar,
name: :foo,
source_field: :id,
type: :has_one
type: :has_one,
private?: false
},
%HasOne{
cardinality: :one,
destination: Bar,
destination_field: :post_id,
name: :bar,
source_field: :id,
type: :has_one,
private?: true
}
] = Ash.Resource.relationships(Post)
assert [%HasOne{name: :foo}] = Ash.Resource.public_relationships(Post)
assert %HasOne{name: :foo} = Ash.Resource.public_relationship(Post, :foo)
assert nil == Ash.Resource.relationship(Post, :definitely_legit_relationship)
assert nil == Ash.Resource.public_relationship(Post, :bar)
end
end
@ -94,5 +115,19 @@ defmodule Ash.Test.Resource.Relationshihps.HasOneTest do
end
)
end
test "fails if private? is not an boolean" do
assert_raise(
Ash.Error.Dsl.DslError,
"[Ash.Resource.Dsl.HasOne]\n relationships -> has_one -> foobar:\n expected :private? to be an boolean, got: \"foo\"",
fn ->
defposts do
relationships do
has_one :foobar, FooBar, private?: "foo", destination_field: :post_id
end
end
end
)
end
end
end

View file

@ -2,6 +2,10 @@ defmodule Ash.Test.Resource.Relationships.ManyToManyTest do
@moduledoc false
use ExUnit.Case, async: true
alias __MODULE__
alias Ash.Resource.Relationships.HasMany
alias Ash.Resource.Relationships.ManyToMany
defmacrop defposts(do: body) do
quote do
defmodule Post do
@ -25,32 +29,72 @@ defmodule Ash.Test.Resource.Relationships.ManyToManyTest do
through: SomeResource,
source_field_on_join_table: :post_id,
destination_field_on_join_table: :related_post_id
many_to_many :unrelated_posts, Post,
through: Tabloid,
source_field_on_join_table: :post_id,
destination_field_on_join_table: :unrelated_post_id,
private?: true
end
end
assert [
%Ash.Resource.Relationships.HasMany{
%HasMany{
cardinality: :many,
destination: Tabloid,
destination_field: :post_id,
name: :unrelated_posts_join_assoc,
source: ManyToManyTest.Post,
source_field: :id,
type: :has_many,
private?: true
},
%HasMany{
cardinality: :many,
destination: SomeResource,
destination_field: :post_id,
name: :related_posts_join_assoc,
source: Ash.Test.Resource.Relationships.ManyToManyTest.Post,
source: ManyToManyTest.Post,
source_field: :id,
type: :has_many
type: :has_many,
private?: true
},
%Ash.Resource.Relationships.ManyToMany{
%ManyToMany{
cardinality: :many,
destination: Ash.Test.Resource.Relationships.ManyToManyTest.Post,
destination: ManyToManyTest.Post,
destination_field: :id,
destination_field_on_join_table: :related_post_id,
name: :related_posts,
source: Ash.Test.Resource.Relationships.ManyToManyTest.Post,
source: ManyToManyTest.Post,
source_field: :id,
source_field_on_join_table: :post_id,
through: SomeResource,
type: :many_to_many
type: :many_to_many,
private?: false
},
%ManyToMany{
cardinality: :many,
destination: ManyToManyTest.Post,
destination_field: :id,
destination_field_on_join_table: :unrelated_post_id,
name: :unrelated_posts,
source: ManyToManyTest.Post,
source_field: :id,
source_field_on_join_table: :post_id,
through: Tabloid,
type: :many_to_many,
private?: true
}
] = Ash.Resource.relationships(Post)
assert [%ManyToMany{name: :related_posts}] = Ash.Resource.public_relationships(Post)
assert %ManyToMany{name: :related_posts} =
Ash.Resource.public_relationship(Post, :related_posts)
assert nil == Ash.Resource.relationship(Post, :definitely_legit_relationship)
assert nil == Ash.Resource.public_relationship(Post, :unrelated_posts)
end
end
@ -152,5 +196,23 @@ defmodule Ash.Test.Resource.Relationships.ManyToManyTest do
end
)
end
test "fails if private? is not an boolean" do
assert_raise(
Ash.Error.Dsl.DslError,
"[Ash.Resource.Dsl.ManyToMany]\n relationships -> many_to_many -> foobars:\n expected :private? to be an boolean, got: \"an_invalid_field\"",
fn ->
defposts do
relationships do
many_to_many :foobars, Foobar,
through: FooBars,
source_field_on_join_table: :source_post_id,
destination_field_on_join_table: :destination_post_id,
private?: "an_invalid_field"
end
end
end
)
end
end
end