Define stats and result permissions with scopes

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.
This commit is contained in:
Javi Martín
2019-11-08 22:11:25 +01:00
parent 9e27027f56
commit f8e6e98d3a
6 changed files with 71 additions and 18 deletions

View File

@@ -7,20 +7,16 @@ module Abilities
can [:read, :map, :summary, :share], Proposal can [:read, :map, :summary, :share], Proposal
can :read, Comment can :read, Comment
can :read, Poll can :read, Poll
can :results, Poll do |poll| can :results, Poll, id: Poll.expired.results_enabled.ids
poll.expired? && poll.results_enabled? can :stats, Poll, id: Poll.expired.stats_enabled.ids
end
can :stats, Poll do |poll|
poll.expired? && poll.stats_enabled?
end
can :read, Poll::Question can :read, Poll::Question
can :read, User can :read, User
can [:read, :welcome], Budget can [:read, :welcome], Budget
can [:read], Budget can [:read], Budget
can [:read], Budget::Group can [:read], Budget::Group
can [:read, :print, :json_data], Budget::Investment can [:read, :print, :json_data], Budget::Investment
can(:read_results, Budget) { |budget| budget.results_enabled? && budget.finished? } can :read_results, Budget, id: Budget.finished.results_enabled.ids
can(:read_stats, Budget) { |budget| budget.stats_enabled? && budget.valuating_or_later? } can :read_stats, Budget, id: Budget.valuating_or_later.stats_enabled.ids
can :read_executions, Budget, phase: "finished" can :read_executions, Budget, phase: "finished"
can :new, DirectMessage can :new, DirectMessage
can [:read, :debate, :draft_publication, :allegations, :result_publication, can [:read, :debate, :draft_publication, :allegations, :result_publication,

View File

@@ -47,6 +47,7 @@ class Budget < ApplicationRecord
scope :reviewing, -> { where(phase: "reviewing") } scope :reviewing, -> { where(phase: "reviewing") }
scope :selecting, -> { where(phase: "selecting") } scope :selecting, -> { where(phase: "selecting") }
scope :valuating, -> { where(phase: "valuating") } scope :valuating, -> { where(phase: "valuating") }
scope :valuating_or_later, -> { where(phase: Budget::Phase.kind_or_later("valuating")) }
scope :publishing_prices, -> { where(phase: "publishing_prices") } scope :publishing_prices, -> { where(phase: "publishing_prices") }
scope :balloting, -> { where(phase: "balloting") } scope :balloting, -> { where(phase: "balloting") }
scope :reviewing_ballots, -> { where(phase: "reviewing_ballots") } scope :reviewing_ballots, -> { where(phase: "reviewing_ballots") }

View File

@@ -32,6 +32,10 @@ class Budget
define_singleton_method(phase) { find_by(kind: phase) } define_singleton_method(phase) { find_by(kind: phase) }
end end
def self.kind_or_later(phase)
PHASE_KINDS[PHASE_KINDS.index(phase)..-1]
end
def next_enabled_phase def next_enabled_phase
next_phase&.enabled? ? next_phase : next_phase&.next_enabled_phase next_phase&.enabled? ? next_phase : next_phase&.next_enabled_phase
end end
@@ -92,7 +96,7 @@ class Budget
end end
def in_phase_or_later?(phase) def in_phase_or_later?(phase)
PHASE_KINDS.index(kind) >= PHASE_KINDS.index(phase) self.class.kind_or_later(phase).include?(kind)
end end
end end
end end

View File

@@ -4,6 +4,10 @@ module Reportable
included do included do
has_one :report, as: :process, inverse_of: :process, dependent: :destroy has_one :report, as: :process, inverse_of: :process, dependent: :destroy
accepts_nested_attributes_for :report accepts_nested_attributes_for :report
Report::KINDS.each do |kind|
scope "#{kind}_enabled", -> { joins(:report).where("reports.#{kind}": true) }
end
end end
def report def report

View File

@@ -7,6 +7,33 @@ describe Budget do
it_behaves_like "reportable" it_behaves_like "reportable"
it_behaves_like "globalizable", :budget it_behaves_like "globalizable", :budget
describe "scopes" do
describe ".open" do
it "returns all budgets that are not in the finished phase" do
(Budget::Phase::PHASE_KINDS - ["finished"]).each do |phase|
budget = create(:budget, phase: phase)
expect(Budget.open).to include(budget)
end
end
end
describe ".valuating_or_later" do
it "returns budgets valuating or later" do
valuating = create(:budget, :valuating)
finished = create(:budget, :finished)
expect(Budget.valuating_or_later).to match_array([valuating, finished])
end
it "does not return budgets which haven't reached valuation" do
create(:budget, :drafting)
create(:budget, :selecting)
expect(Budget.valuating_or_later).to be_empty
end
end
end
describe "name" do describe "name" do
before do before do
budget.update(name_en: "object name") budget.update(name_en: "object name")
@@ -172,15 +199,6 @@ describe Budget do
end end
end end
describe "#open" do
it "returns all budgets that are not in the finished phase" do
(Budget::Phase::PHASE_KINDS - ["finished"]).each do |phase|
budget = create(:budget, phase: phase)
expect(Budget.open).to include(budget)
end
end
end
describe "heading_price" do describe "heading_price" do
it "returns the heading price if the heading provided is part of the budget" do it "returns the heading price if the heading provided is part of the budget" do
heading = create(:budget_heading, price: 100, budget: budget) heading = create(:budget_heading, price: 100, budget: budget)

View File

@@ -1,6 +1,36 @@
shared_examples "reportable" do shared_examples "reportable" do
let(:reportable) { create(model_name(described_class)) } let(:reportable) { create(model_name(described_class)) }
describe "scopes" do
describe ".results_enabled" do
it "includes records with results enabled" do
reportable.update!(results_enabled: true)
expect(described_class.results_enabled).to eq [reportable]
end
it "does not include records without results enabled" do
reportable.update!(results_enabled: false)
expect(described_class.results_enabled).to be_empty
end
end
describe ".stats_enabled" do
it "includes records with stats enabled" do
reportable.update!(stats_enabled: true)
expect(described_class.stats_enabled).to eq [reportable]
end
it "does not include records without stats enabled" do
reportable.update!(stats_enabled: false)
expect(described_class.stats_enabled).to be_empty
end
end
end
describe "#results_enabled" do describe "#results_enabled" do
it "can write and read the attribute" do it "can write and read the attribute" do
reportable.results_enabled = true reportable.results_enabled = true