Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
9c24ed0
Pass voting_method into absinthe layer.
jinjagit Mar 31, 2021
06cc2f3
Add voting_method to association to vote model changeset.
jinjagit Mar 31, 2021
a662d76
Drop and create new votes unique index.
jinjagit Mar 31, 2021
57f3051
Rename votes unique index and related changeset constraint.
jinjagit Mar 31, 2021
ab662fd
Upsert voting_method when create vote via GraphQl
jinjagit Mar 31, 2021
a33002f
Rename 'voting_method' field to 'name'.
jinjagit Mar 31, 2021
88b2caa
Linting.
jinjagit Mar 31, 2021
6b5dd5d
Use new field 'name' in absinthe schema and voting_method factory.
jinjagit Mar 31, 2021
2551082
Add Repo.preload([:voting_method]) to core functions.
jinjagit Mar 31, 2021
0b521bf
Create get_voting_method!/2 and related test.
jinjagit Mar 31, 2021
8e78f9b
Add voting_method object to absinthe schema.
jinjagit Mar 31, 2021
c09b5ba
Use Ecto.Multi to refactor nested case statements.
jinjagit Mar 31, 2021
c35e517
Linting.
jinjagit Mar 31, 2021
c6c4d02
Use correct model names in comments.
jinjagit Mar 31, 2021
b9e4eb8
Add :name to :voting_method required fields in changeset.
jinjagit Mar 31, 2021
7e2f1fa
Combine 3 error matches into one.
jinjagit Apr 1, 2021
3c4b890
Merge branch 'master' into use-voting-method-for-voting.
jinjagit Apr 1, 2021
dfe51b4
Add voting_method to create_vote test in voting_test.exs.
jinjagit Apr 1, 2021
006f427
Add upsert voting method to absinthe create vote with participant id
jinjagit Apr 1, 2021
294119d
Add voting_method field to absinthe create_vote tests.
jinjagit Apr 1, 2021
d010f1f
Add voting_method field to voting result and use to calculate result.
jinjagit Apr 2, 2021
c8d67e0
Update test of list_votes_by_proposal/3
jinjagit Apr 2, 2021
eddb3b9
Use voting_method param in get_vote!/4
jinjagit Apr 2, 2021
5b78e5a
Use assoc with VotingMethod to enable upsert of similar Result.
jinjagit Apr 2, 2021
41d4998
Use voting_method in test involving create_result/1
jinjagit Apr 2, 2021
00a7b2f
Add get_voting_method_by_name/2 & related test.
jinjagit Apr 2, 2021
99f0dcf
Re-enable deleteVote at absinthe layer.
jinjagit Apr 2, 2021
af02508
Return associated voting_method from queries in voitng_results.ex.
jinjagit Apr 2, 2021
da3bdfc
Use voting_method.id in schema resolver function call.
jinjagit Apr 2, 2021
1195634
Use voting_method in absinthe Results schema and related functions.
jinjagit Apr 2, 2021
04c7383
Preload voting_method assoc when returning vote from create_vote/1.
jinjagit Apr 2, 2021
7ecf4ee
Create list_results_for_proposal_url and use in delegations_ex.
jinjagit Apr 2, 2021
e581f0e
Set default voting_method.name at db level & use in create_vote.
jinjagit Apr 5, 2021
41e6eba
Remove resolver setting of "default" value for voting_method.name
jinjagit Apr 5, 2021
94cc387
Associate Delegation with VotingMethod
jinjagit Apr 5, 2021
8e2bf24
Add voting_method_name param to get_delegation!/4 (now .../5).
jinjagit Apr 5, 2021
73dfafa
Add tests of absinthe create_vote with & without voting_method
jinjagit Apr 5, 2021
9489d95
Add absinthe tests of voting result query.
jinjagit Apr 5, 2021
aa7d5aa
Improve description of absinthe result query in schema.ex
jinjagit Apr 5, 2021
e1cf227
Return error when createDelegation contains wrong mix of params.
jinjagit Apr 6, 2021
86b3b4f
Upsert voting_method & update votingResult on createDelegation.
jinjagit Apr 6, 2021
afa0179
Associate new delegation with voting_method.id.
jinjagit Apr 6, 2021
ebdbecf
Replace accidental deletion.
jinjagit Apr 6, 2021
8ebfe80
Use voting_method_id to correctly calculate voting weight.
jinjagit Apr 6, 2021
d32bcb9
Use voting_method in check_vote_conflict/3.
jinjagit Apr 6, 2021
1d1a1e4
Create and use new delegations unique index when upserting.
jinjagit Apr 6, 2021
d1571dd
Update multiple tests to use voting_method.
jinjagit Apr 7, 2021
88aab32
Force preload voting method when create delegation using participant …
jinjagit Apr 7, 2021
6971454
Add error handling for create delegation with participant ids.
jinjagit Apr 7, 2021
a34ec58
Use voting_method in various tests.
jinjagit Apr 7, 2021
ce90bd7
Refactor create_delegation/1 clauses to remove duplication.
jinjagit Apr 7, 2021
1d95b34
Improve doc comments for create_delegation/1
jinjagit Apr 7, 2021
6389505
Fix typos in comments.
jinjagit Apr 7, 2021
bccca21
Use voting_method when deleting a delegation.
jinjagit Apr 8, 2021
23617de
Use voting_method in absinthe delete delegations test.
jinjagit Apr 8, 2021
7d6d91c
Improve comments and code layout.
jinjagit Apr 8, 2021
cff7abc
Remove unused vars.
jinjagit Apr 8, 2021
c4f0177
Use voting method in absinthe Votes query.
jinjagit Apr 8, 2021
94a5fcc
Add absinthe tests of Votes query.
jinjagit Apr 8, 2021
935e074
Fix incorrect test file module name.
jinjagit Apr 8, 2021
2650657
Use voting_method in absinthe voting results subscription.
jinjagit Apr 8, 2021
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
149 changes: 125 additions & 24 deletions lib/liquid_voting/delegations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule LiquidVoting.Delegations do
import Ecto.Query, warn: false

alias __MODULE__.Delegation
alias LiquidVoting.{Repo, Voting}
alias LiquidVoting.{Repo, Voting, VotingMethods}
alias Voting.Vote
alias Ecto.Multi

Expand All @@ -33,7 +33,10 @@ defmodule LiquidVoting.Delegations do

## Examples

iex> get_delegation!(123, "a6158b19-6bf6-4457-9d13-ef8b141611b4")
iex> get_delegation!(
"c9c2fa04-a35b-427b-80b4-894043264d25",
"a6158b19-6bf6-4457-9d13-ef8b141611b4"
)
%Delegation{}

iex> get_delegation!(456, "a6158b19-6bf6-4457-9d13-ef8b141611b4")
Expand All @@ -47,30 +50,49 @@ defmodule LiquidVoting.Delegations do
end

@doc """
Gets a single delegation by delegator email, delegate email, proposal_url and organization id
Gets a single delegation by delegator email, delegate email, voting_method_name, proposal_url and organization id

Raises `Ecto.NoResultsError` if the Delegation does not exist.

## Examples

iex> get_delegation!("delegator@email.com", "delegate@email.com", "https://aproposal.com", "a6158b19-6bf6-4457-9d13-ef8b141611b4")
iex> get_delegation!(
"delegator@email.com",
"delegate@email.com",
"method_A",
"https://aproposal.com",
"a6158b19-6bf6-4457-9d13-ef8b141611b4")
%Delegation{}

iex> get_delegation!("participant-without-delegation@email.com", "some@body.com", "https://aproposal.com", "a6158b19-6bf6-4457-9d13-ef8b141611b4")
iex> get_delegation!(
"participant-without-delegation@email.com",
"some@body.com",
"method_A",
"https://aproposal.com",
"a6158b19-6bf6-4457-9d13-ef8b141611b4")
** (Ecto.NoResultsError)

"""
def get_delegation!(delegator_email, delegate_email, proposal_url, organization_id) do
def get_delegation!(
delegator_email,
delegate_email,
voting_method_name,
proposal_url,
organization_id
) do
delegator = Voting.get_participant_by_email!(delegator_email, organization_id)
delegate = Voting.get_participant_by_email!(delegate_email, organization_id)
voting_method = VotingMethods.get_voting_method_by_name!(voting_method_name, organization_id)

Repo.get_by!(
Delegation,
delegator_id: delegator.id,
delegate_id: delegate.id,
voting_method_id: voting_method.id,
proposal_url: proposal_url,
organization_id: organization_id
)
|> Repo.preload([:voting_method])
end

@doc """
Expand All @@ -80,10 +102,17 @@ defmodule LiquidVoting.Delegations do

## Examples

iex> get_delegation!("delegator@email.com", "delegate@email.com", "a6158b19-6bf6-4457-9d13-ef8b141611b4")
iex> get_delegation!(
"delegator@email.com",
"delegate@email.com",
"a6158b19-6bf6-4457-9d13-ef8b141611b4")
%Delegation{}

iex> get_delegation!("participant-without-delegation@email.com", "some@body.com", "a6158b19-6bf6-4457-9d13-ef8b141611b4")
iex> get_delegation!(
"participant-without-delegation@email.com",
"some@body.com",
"a6158b19-6bf6-4457-9d13-ef8b141611b4"
)
** (Ecto.NoResultsError)

"""
Expand Down Expand Up @@ -111,9 +140,29 @@ defmodule LiquidVoting.Delegations do
If created using participant emails, new participant(s) will be created if
they do not already exist.

If a proposal delegation (has a proposal_url), a new associated voting_method will be
created if it does not already exist.

## Examples

iex> create_delegation(%{field: value})
global delegation:

iex> create_delegation(%{
delegator_email: "ana@g.com",
delegate_email: "bob@g.com",
organization_id: "a6158b19-6bf6-4457-9d13-ef8b141611b4"
})
{:ok, %Delegation{}}

proposal specific delegation:

iex> create_delegation(%{
delegator_email: "ana@g.com",
delegate_email: "bob@g.com",
voting_method: "our_voting_method",
proposal_url: "https://aproposal.com",
organization_id: "a6158b19-6bf6-4457-9d13-ef8b141611b4"
})
{:ok, %Delegation{}}

iex> create_delegation(%{field: bad_value})
Expand All @@ -127,6 +176,8 @@ defmodule LiquidVoting.Delegations do
delegate_attrs = %{email: args.delegate_email, organization_id: args.organization_id}
delegation_attrs = Map.take(args, [:organization_id, :proposal_url])

voting_method_id = upsert_voting_method_and_get_id(args)

Multi.new()
|> Multi.run(:upsert_delegator, fn _repo, _changes ->
Voting.upsert_participant(delegator_attrs)
Expand All @@ -138,12 +189,17 @@ defmodule LiquidVoting.Delegations do
delegation_attrs
|> Map.put(:delegator_id, changes.upsert_delegator.id)
|> Map.put(:delegate_id, changes.upsert_delegate.id)
|> Map.put(:voting_method_id, voting_method_id)
|> upsert_delegation()
end)
|> Repo.transaction()
|> case do
{:ok, resources} ->
{:ok, resources.upsert_delegation}
delegation =
resources.upsert_delegation
|> Repo.preload([:voting_method])

{:ok, delegation}

{:error, :upsert_delegation, value, _} ->
{:error, value}
Expand All @@ -154,7 +210,34 @@ defmodule LiquidVoting.Delegations do
end

def create_delegation(%{delegator_id: _, delegate_id: _} = attrs) do
voting_method_id = upsert_voting_method_and_get_id(attrs)
attrs = Map.put(attrs, :voting_method_id, voting_method_id)

upsert_delegation(attrs)
|> case do
{:ok, delegation} ->
delegation = Repo.preload(delegation, [:voting_method], force: true)
{:ok, delegation}

{:error, changeset} ->
{:error, changeset}
end
end

# If a proposal_url is specified, upserts a voting_method and returns the voting_method_id.
# If a proposal_url is NOT specified, simply returns voting_method_id = nil.
defp upsert_voting_method_and_get_id(attrs) do
if Map.get(attrs, :proposal_url) do
{:ok, voting_method} =
VotingMethods.upsert_voting_method(%{
name: Map.get(attrs, :voting_method),
organization_id: attrs.organization_id
})

voting_method.id
else
nil
end
end

@doc """
Expand Down Expand Up @@ -188,16 +271,22 @@ defmodule LiquidVoting.Delegations do
attrs
) do
proposal_url = Map.get(attrs, :proposal_url)
voting_method_id = Map.get(attrs, :voting_method_id)

with {:ok} <- check_vote_conflict(delegator_id, proposal_url, organization_id) do
with {:ok} <-
check_vote_conflict(delegator_id, voting_method_id, proposal_url, organization_id) do
Delegation
|> where(delegator_id: ^delegator_id)
|> Repo.all()
|> resolve_conflicts(delegate_id, proposal_url, organization_id)
|> case do
{:ok, delegations} ->
delegations
|> find_similar_delegation_or_return_new_struct(proposal_url, organization_id)
|> find_similar_delegation_or_return_new_struct(
proposal_url,
voting_method_id,
organization_id
)
|> Delegation.changeset(attrs)
|> Repo.insert_or_update()

Expand All @@ -213,12 +302,15 @@ defmodule LiquidVoting.Delegations do
#
# Or returns {:ok} if delegation creation is for a proposal delegation & no conflicting vote is found.
# Returns an error, if a conflicting vote is found.
defp check_vote_conflict(_delegator_id, _proposal_url = nil, _organization_id) do
{:ok}
end

defp check_vote_conflict(delegator_id, proposal_url, organization_id) do
case Voting.get_vote_by_participant_id(delegator_id, proposal_url, organization_id) do
defp check_vote_conflict(_, _proposal_url = nil, _, _), do: {:ok}

defp check_vote_conflict(delegator_id, voting_method_id, proposal_url, organization_id) do
case Voting.get_vote_by_participant_id(
delegator_id,
voting_method_id,
proposal_url,
organization_id
) do
%Vote{} ->
{
:error,
Expand All @@ -244,7 +336,8 @@ defmodule LiquidVoting.Delegations do
defp resolve_conflicts(delegations, delegate_id, _proposal_url = nil, organization_id) do
delegations
|> Stream.filter(fn d ->
d.delegate_id == delegate_id and d.proposal_url != nil and
d.delegate_id == delegate_id and
d.proposal_url != nil and
d.organization_id == organization_id
end)
|> Enum.each(fn d ->
Expand All @@ -257,7 +350,8 @@ defmodule LiquidVoting.Delegations do
defp resolve_conflicts(delegations, delegate_id, _proposal_url, organization_id) do
delegations
|> Enum.filter(fn d ->
d.proposal_url == nil and d.delegate_id == delegate_id and
d.proposal_url == nil and
d.delegate_id == delegate_id and
d.organization_id == organization_id
end)
|> case do
Expand All @@ -274,14 +368,21 @@ defmodule LiquidVoting.Delegations do
end

# Looks for a delegator's existing delegation of matching type (global or for
# same proposal).Returns a matching delegation type, if found, or returns an
# empty Delegation struct.
# same proposal and voting_method). Returns a matching delegation type, if found,
# or returns an empty Delegation struct.
#
# Used by upsert_delegation/1 (above.)
defp find_similar_delegation_or_return_new_struct(delegations, proposal_url, organization_id) do
defp find_similar_delegation_or_return_new_struct(
delegations,
proposal_url,
voting_method_id,
organization_id
) do
delegations
|> Enum.filter(fn d ->
d.proposal_url == proposal_url and d.organization_id == organization_id
d.proposal_url == proposal_url and
d.voting_method_id == voting_method_id and
d.organization_id == organization_id
end)
|> case do
# Delegation (of same type) not found, so we build one
Expand Down
8 changes: 5 additions & 3 deletions lib/liquid_voting/delegations/delegation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule LiquidVoting.Delegations.Delegation do
import Ecto.Changeset

alias LiquidVoting.Voting.Participant
alias LiquidVoting.VotingMethods.VotingMethod

@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
Expand All @@ -13,23 +14,24 @@ defmodule LiquidVoting.Delegations.Delegation do

belongs_to :delegator, Participant
belongs_to :delegate, Participant
belongs_to :voting_method, VotingMethod

timestamps()
end

@doc false
def changeset(delegation, attrs) do
required_fields = [:delegator_id, :delegate_id, :organization_id]
all_fields = [:proposal_url | required_fields]
all_fields = [:proposal_url, :voting_method_id | required_fields]

delegation
|> cast(attrs, all_fields)
|> assoc_constraint(:delegator)
|> assoc_constraint(:delegate)
|> validate_required(required_fields)
|> validate_participants_different
|> unique_constraint(:org_delegator_delegate_proposal,
name: :uniq_index_org_delegator_delegate_proposal
|> unique_constraint(:org_delegator_delegate_proposal_method,
name: :uniq_index_org_delegator_delegate_proposal_voting_method
)
end

Expand Down
Loading