Commit Graph

30 Commits

Author SHA1 Message Date
Javi Martín
fb9156f9b8 Use with_lock instead of lock!
That way the record is only locked while necessary.
2024-06-26 15:41:44 +02:00
Javi Martín
a1439d0790 Apply Layout/LineLength rubocop rule
Note we're excluding a few files:

* Configuration files that weren't generated by us
* Migration files that weren't generated by us
* The Gemfile, since it includes an important comment that must be on
  the same line as the gem declaration
* The Budget::Stats class, since the heading statistics are a mess and
  having shorter lines would require a lot of refactoring
2023-08-30 14:46:35 +02:00
Javi Martín
dd28163be7 Show admin heading stats for the current budget
To get the heading where a user voted, we were relying on the
`balloted_heading_id` field.

Our guess is this was done so the total number of users is the same as
the sum of users who voted on a heading. That is, if 2000 people voted
just on the "All city" heading, 1000 voted just on the "North district"
heading, and 500 people voted on both, instead of showing "3500 people
voted in total, 2500 voted in all city, 1500 voted in north district",
we show something like "3500 people voted in total, 2250 voted in all
city, and 1250 voted in north district".

However, this approach has some disadvantages.

The first disadvantage is, the stats aren't correct. In the case above,
2500 voted on the "All city heading", so the statistics for this heading
don't show reality.

The second one is we weren't considering the last heading where users
voted inside the budget being displayed, but the last heading where
users voted, period. That means that, if all the people above voted on a
later budget, the stats for the budget above would become "3500 people
voted in total, 0 voted in all city, and 0 voted in north district".
That also means we were including headings from previous budgets in the
statistics for more recent budgets when people hadn't voted on the
recent ones.

So we're removing the `balloted_heading_id` since its data is lost once
people vote on a new budget. And, in order to show the right stats and
simplify the code, we're no longer trying to add votes just to one
heading when users vote on several headings.

Co-Authored-By: Julian Nicolas Herrero <microweb10@gmail.com>
2023-02-20 14:21:03 +01:00
Javi Martín
160964fcdc Make method to check a line can be added generic
In the Knapsack voting style, we can't add an investment if its cost is
greater than the money we've got left, but in other voting styles money
might not be the issue.

So we're introducing the term "resources" and adapting the code
accordingly.
2020-07-30 16:20:00 +02:00
Javi Martín
a9900e3f27 Remove duplication calculating insufficient funds
We were using the same logic twice.

I've moved the logic to the Ballot model, which to me is a more natural
place to calculate whether there's enough money left than the Investment
model. After all, the remaining money is in the ballot, and not in the
investment.
2020-07-28 13:06:31 +02:00
Javi Martín
d2d517059d Fix race condition with ballot lines
With two concurrent requests, it's possible to create two ballot lines
when only one of them should be created.

The reason is the code validating the line is not thread safe:

```
if ballot.amount_available(investment.heading) < investment.price.to_i
  errors.add(:money, "insufficient funds")
end
```

If the second request executes this code after the first request has
executed it but before the first request has saved the record to the
database, both records will pass this validation and both will be saved
to the database.

So we need to introduce a lock. Now when the second request tries to
lock the ballot, it finds it's already locked by the first request, and
will wait for the transaction of the first request to finish before
checking whether there are sufficient funds.

Note we need to disable transactions during the test; otherwise the
second thread will wait for the first one to finish.

Also note that we need to update a couple of tests because records are
reloaded when they're locked.

In one case, reloading the ballot causes `ballot.user` to be `nil`,
since the user is hidden. So we hide the user after creating all its
associated records (which is the scenario that would take place in real
life).

In the other case, reloading the ballot causes `ballot.user` to reload
as well. So we need to reload the user object used in the test too so it
gets the updates done on `ballot.user`.

I haven't been able to reproduce this behavior in a system test. The
following test works with Rails 5.0, but it stopped working when we
moved to system tests in commit 9427f014. After that commit, for reasons
I haven't been able to debug (reintroducing truncation with
DatabaseClaner didn't seem to affect this test, and neither did
increasing the number of threads in Puma), the two AJAX requests
executed here are no longer simultaneous; the second request waits for
the first one to finish.

scenario "Race conditions with simultaneous requests", :js do
  allow_any_instance_of(Budget::Ballot::Line).to receive(:check_sufficient_funds) do |object|
    allow(object).to receive(:check_sufficient_funds).and_call_original
    object.check_sufficient_funds
    sleep 0.3
  end

  ["First", "Second"].each do |title|
    create(:budget_investment, :selected,
      heading: california,
      price:   california.price,
      title:   title
    )
  end

  login_as(user)
  visit budget_investments_path(budget, heading_id: california.id)

  within(".budget-investment", text: "First") { click_link "Vote" }
  within(".budget-investment", text: "Second") { click_link "Vote" }

  expect(page).to have_link "Remove vote"
  expect(Budget::Ballot::Line.count).to eq 1
end
2020-07-12 22:11:40 +02:00
Javi Martín
d0d681a44b Add and apply EmptyLineAfterGuardClause rule
We were inconsistent on this one. I consider it particularly useful when
a method starts with a `return` statement.

In other cases, we probably shouldn't have a guard rule in the middle of
a method in any case, but that's a different refactoring.
2019-10-24 17:56:03 +02:00
Javi Martín
9fe8c47528 Apply Rails/SafeNavigation rubocop rule 2019-09-10 21:43:39 +02:00
Bertocq
63631f29a8 Add ballot_lines_count counter_cache to ballot -> ballot line relationship 2019-05-16 22:01:42 +02:00
Juanjo Bazán
7ab602175a makes models inherit from ApplicationRecord 2019-04-17 17:40:56 +02:00
rgarcia
aeb84108bc Verify poll ballots 2019-04-10 18:29:01 +02:00
rgarcia
1eaa5cc77b stores the heading where the user has voted 2019-04-10 18:15:43 +02:00
Bertocq
69f4e1c683 Fix all rubocop Metrics/LineLength issues (140+) 2017-07-10 13:22:49 +02:00
Bertocq
2888c20489 Fix all Style/RedundantSelf rubocop issues 2017-07-05 11:55:52 +02:00
Bertocq
02524b164a Rubocop autocorrections (indentations, revers unless to if, extra spaces) 2017-06-08 12:14:35 +02:00
rgarcia
42f62e5c36 stores reclassified votes 2017-05-17 15:13:28 +02:00
Juanjo Bazán
a4bfe9c50c adds ballot_lines counter to investments 2017-04-28 13:19:43 +02:00
kikito
f5dcb51d37 Adds valid_heading validation to budget line. Renames validation methods. 2017-01-04 13:56:42 +01:00
kikito
f125941bbf Tweaks unselected error in ballot/line 2016-12-23 15:22:19 +01:00
kikito
430507ec1e Adds budget_lines.selected 2016-12-22 20:24:59 +01:00
kikito
19e19e5c43 Fixes error when trying to denormalize a model whose dependent field is nil 2016-12-16 18:00:18 +01:00
kikito
355e286608 De-normalizes references in budgetballotline automatically 2016-12-15 18:17:52 +01:00
rgarcia
1c8f4d5ff0 can remove investment projects 2016-09-10 16:22:41 +02:00
rgarcia
f21fda0898 fixes pending specs 2016-07-27 16:39:25 +02:00
rgarcia
3c55d66591 fixes conflicts with budget 2016-07-27 11:41:46 +02:00
Juanjo Bazán
56a358a638 adds funds and feasibility validations to lines 2016-06-11 15:01:51 +02:00
Juanjo Bazán
ab008ed4e9 adds denormalization to budget::ballot::lines 2016-06-11 14:57:07 +02:00
kikito
c6c3e22a40 Adds methods for investments in models 2016-06-03 18:44:34 +02:00
Juanjo Bazán
57642075a7 adds ballot model spec 2016-05-24 13:04:12 +02:00
kikito
9d195609f9 Adds models 2016-05-20 12:41:48 +02:00