IMHO selecting in how many headings it's possible to support investments
isn't necessary when there's only one option to choose from. It's
obvious that if there's only one heading, it will be impossible to
select investments from more than one heading.
We're not adding the rule because it would apply the current line length
rule of 110 characters per line. We still haven't decided whether we'll
keep that rule or make lines shorter so they're easier to read,
particularly when vertically splitting the editor window.
So, for now, I'm applying the rule to lines which are about 90
characters long.
Since we're going to add an action to remove supports, having a separate
controller makes things easier.
Note there was a strange piece of code which assumed users were not
verified if they couldn't vote investments. Now the code is also
strange, since it assumes users are not verified if they can't create
votes. We might need to revisit these conditions if our logic changes in
the future.
In the form of creating a new investment was hiding the name of the
group if it had only one heading, but could be confusing to users if
there are, for example, five different groups of one heading.
The solution:
- If the budget has one group and one heading, the heading selector is
hidden.
- If the budget has one group and more than one heading, the group name
is hidden.
- If the budget has more than one group, the group name appears
regardless of the number of headings.
Note we're making the validation rule dynamic so it's affected by the
way we stub the constant in the tests to emulate data created in old
applications.
Co-Authored-By: Javi Martín <javim@elretirao.net>
Previously the draft mode was a phase of the PB, but that had some
limitations.
Now the phase drafting disappears and therefore the PB can have the
status published or not published (in draft mode).
That will give more flexibility in order to navigate through the
different phases and see how it looks for administrators before
publishing the PB and everybody can see.
By default, the PB is always created in draft mode, so it gives you
the flexibility to adjust and modify anything before publishing it.
This way we can simplify the code and don't have to rely on `.try`
statements which are confusing and so we don't allow them in the
`Rails/SafeNavigation` Rubocop rule.
Ruby 2.6 introduces `Enumerable#filter` as an alias to
`Enumerable#select`, and so our Filterable.filter method will not work
with Ruby 2.6.
So we're renaming the method to `filter_by`, which is similar to
`find_by`. We could also change the `filter` method so if a block is
given it delegates to `Enumerable#filter`, the same way ActiveRecord
handles the `select` method, but IMHO this is easier to follow.
We were setting it to 0, and so screen reader users might be confused by
it.
The easiest way to reuse the code and using it for both this attribute
and the width of the progress bar is to move this method to the voting
style, just like the other methods used in this view.
Note the progressbar ARIA role might not be right, since this isn't a
task which is "progressing", but an indicator of the amount spent and
amount available, which is exactly what the <meter> HTML5 tag was
designed for.
We might use a <meter> tag in the future. For now, I'm leaving it as it
is because I'm not certain about how well <meter> is supported in
accessibility tools, and because it's definitely not supported in
Internet Explorer 11, which we haven't officially dropped support for.
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.
Since we're going to introduce a new voting style which will not be
based on money, we're extracting the logic specific to the current
voting style to a new class.
This way adding new voting styles will be easier.
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.
One method was calling `reason_for_not_being_ballotable_by` passing just
one parameter instead of two.
The other method was calling the method `amount_spent`, which does not
exist in the Budget class.
So both methods would make the application crash if they were called.
Luckily, they aren't, so the application doesn't crash.
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
This method is deprecated in Rails 5.1 because its behavior will be
different in `before` and `after` callbacks.
We're replacing the deprecated `attribute_changed?` and `attribute_was`
methods with `saved_change_to_attribute?` and
`attribute_before_last_save` during `after_save` callbacks.
https://github.com/rails/rails/pull/32835/
This method is deprecated in Rails 5.1 because its behavior will be
different in `before` and `after` callbacks.
Here we're replacing the deprecated `attribute_changed?` and
`attribute_was` with `will_save_change_to_attribute?` and
`attribute_in_database` during `before_save` callbacks.
https://github.com/rails/rails/pull/32835/
When an investment had been assigned a user tag and a valuation tag with
the same name, it appeared twice when filtering by tag.
This is because by design, in order to provide compatibility with scopes
using "select" or "distinct", the method `tagged_with` doesn't select
unique records.
Forcing the query to return unique records solves the issue.
These filters were only returning investments with valuation open, but
we don't want to do that since the time we changed the interface in
order to allow users to apply several filters at the same time.
In this case using `joins` doesn't prevent N+1 queries to get titles for
every record, and since we cannot order translations with just SQL due
to fallbacks, we don't need it.
Automatic SQL injection checks were showing a false positive in this
scope; there was no real vulnerability here because foreign keys, table
names and locales were under our control.
When defining abilities, scopes cover more cases because they can be
used to check permissions for a record and to filter a collection. Ruby
blocks can only be used to check permissions for a record.
Note the `Budget::Phase.kind_or_later` name sounds funny, probably
because we use the word "phase" for both an an attribute in the budgets
table and an object associated with the budget, and so naming methods
for a budget phase is a bit tricky.
The new CSV report was more configurable and could work on proposals,
processes and comments. However, it had several issues.
In the public area, by default it generated a blank file.
In the admin section, the report was hard to configure and it generated
a file with less quality than the old system.
So until we improve this system, we're bringing back the old investment
CSV exporter.
This commit reverts most of commit 9d1ca3bf.
Our manual implementation had a few issues. In particular, it didn't
track changes related to associations, which became more of an issue
when we made investments translatable.
Using audited gives us more functionality while at the same time
simplifies our code. However, it adds one more external dependency to
our project.
The reason for choosing audited over paper trail is audited seems to
make it easier to handle associations.
If we validate the presence of the old value and the new value, changes
in optional fields will not be stored if either the old value or the new
value are blank.