Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow nesting association based conditions #37

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/permit/permissions/condition_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ defmodule Permit.Permissions.ConditionParser do
{:ok, module} ->
val_fn = binding_fn(value, Keyword.get(ops, :bindings))

condition_type = if Keyword.keyword?(value), do: :association, else: :operator

%ParsedCondition{
condition: {key, val_fn},
condition_type: {:operator, module},
condition_type: {condition_type, module},
semantics: module.semantics(val_fn, ops),
not: Keyword.get(ops, :not, false)
}
Expand Down
33 changes: 33 additions & 0 deletions lib/permit/permissions/parsed_condition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ defmodule Permit.Permissions.ParsedCondition do
|> semantics.(subject, record)
end

def satisfied?(
%ParsedCondition{
condition: {key, val_fn},
condition_type: {:association, _},
semantics: _semantics
},
record,
subject
)
when is_struct(record) do
conditions = val_fn.(subject, record)
check_conditions(record, [{key, conditions}])
end

def satisfied?(%ParsedCondition{condition: condition}, module, _subject)
when is_atom(module),
do: !!condition
Expand Down Expand Up @@ -88,4 +102,23 @@ defmodule Permit.Permissions.ParsedCondition do
subject
),
do: !!function.(subject, record)

defp check_conditions(record, conditions) when is_map(record) and is_list(conditions) do
Enum.all?(conditions, fn {assoc, assoc_conditions} ->
check_association(record, assoc, assoc_conditions)
end)
end

defp check_association(record, assoc, assoc_conditions) do
case Map.get(record, assoc) do
sub_assoc when is_map(sub_assoc) ->
check_conditions(sub_assoc, assoc_conditions)

sub_assoc when is_list(sub_assoc) ->
Enum.all?(sub_assoc, &check_conditions(&1, assoc_conditions))

sub_assoc ->
sub_assoc == assoc_conditions
end
end
end
124 changes: 122 additions & 2 deletions test/permit/permissions/condition_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
defmodule Permit.Permissions.ConditionTest.Types do
defmodule TestObject do
@moduledoc false
defstruct [:name, :key, :field_1, :field_2]
defstruct [:name, :key, :field_1, :field_2, :category]
end

defmodule Movie do
@moduledoc false
defstruct [:name, :category, :actors]
end

defmodule Category do
@moduledoc false
defstruct [:name, :type]
end

defmodule Type do
@moduledoc false
defstruct [:name, :description]
end

defmodule Description do
@moduledoc false
defstruct [:content]
end

defmodule Actor do
@moduledoc false
defstruct [:name, :age]
end
end

Expand All @@ -10,9 +35,104 @@ defmodule Permit.Permissions.ConditionTest do

alias Permit.Permissions.ParsedCondition
alias Permit.Permissions.ConditionParser
alias Permit.Permissions.ConditionTest.Types.TestObject

alias Permit.Permissions.ConditionTest.Types.{
TestObject,
Category,
Type,
Description,
Movie,
Actor
}

describe "satisfied?/3" do
test "should satisfy nested has-many associations" do
condition = {:actors, {:==, [name: nil]}}

test_object = %Movie{actors: [%Actor{name: nil}, %Actor{name: nil}]}

assert ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)

condition = {:actors, {:==, [age: 123]}}

test_object = %Movie{actors: [%Actor{age: 123}]}

assert ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)

condition = {:actors, {:==, [age: 666]}}

test_object = %Movie{actors: [%Actor{age: 666}, %Actor{age: 666}]}

assert ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)

condition = {:actors, {:==, [age: 123, name: "test"]}}

test_object = %Movie{
actors: [%Actor{age: 123, name: "test"}, %Actor{age: 123, name: "test"}]
}

assert ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)
end

test "should not satisfy nested has-many associations" do
condition = {:actors, {:==, [age: 123]}}

test_object = %Movie{actors: [%Actor{age: 666}]}

refute ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)

condition = {:actors, {:==, [age: 666]}}

test_object = %Movie{actors: [%Actor{age: 123}, %Actor{age: 666}]}

refute ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)

condition = {:actors, {:==, [age: 123, name: "test"]}}

test_object = %Movie{
actors: [%Actor{age: 123, name: "test"}, %Actor{age: 123, name: "test_666"}]
}

refute ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)
end

test "should satisfy nested association conditions" do
condition = {
:category,
{:==, [name: "test_category", type: [name: "private"]]}
}

test_object = %Movie{
category: %Category{name: "test_category", type: %Type{name: "private"}}
}

assert ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)

condition = {
:category,
{:==,
[name: "test_category", type: [name: "private", description: [content: "test_content"]]]}
}

test_object = %Movie{
category: %Category{
name: "test_category",
type: %Type{name: "private", description: %Description{content: "test_content"}}
}
}

assert ConditionParser.build(condition)
|> ParsedCondition.satisfied?(test_object, nil)
end

test "should satisfy const condition" do
assert ConditionParser.build(true)
|> ParsedCondition.satisfied?(nil, nil)
Expand Down
Loading