Skip to content

Commit bb4c2e4

Browse files
authored
Merge pull request #419 from impactoss/385-send-due-emails-via-all-indicators
Use combined indicators for recommendations, categories, and their due dates
2 parents 62cd3d3 + 83f4b1f commit bb4c2e4

File tree

4 files changed

+208
-24
lines changed

4 files changed

+208
-24
lines changed

app/models/category.rb

+11-14
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@ class Category < VersionedRecord
1010
has_many :recommendations, through: :recommendation_categories
1111
has_many :users, through: :user_categories
1212
has_many :measures, through: :measure_categories
13-
has_many :indicators, through: :recommendations
14-
has_many :indicators_via_measures, through: :recommendations
15-
has_many :progress_reports, through: :indicators_via_measures
16-
has_many :due_dates, -> { distinct }, through: :indicators_via_measures
17-
18-
has_many :children_due_dates, -> { distinct }, through: :categories, source: :due_dates
1913

2014
delegate :name, :email, to: :manager, prefix: true, allow_nil: true
2115

@@ -27,6 +21,17 @@ class Category < VersionedRecord
2721
scope :draft, -> { where(draft: true) }
2822
scope :published, -> { where(draft: false) }
2923

24+
def combined_indicator_ids
25+
(
26+
recommendations.flat_map(&:indicator_ids) +
27+
categories.flat_map(&:combined_indicator_ids)
28+
).uniq
29+
end
30+
31+
def due_dates
32+
DueDate.where(indicator_id: combined_indicator_ids)
33+
end
34+
3035
def disallowed_sibling_category_ids
3136
return [] if taxonomy.allow_multiple
3237

@@ -92,19 +97,11 @@ def send_due_emails
9297
due_dates.are_due.each do |due_date|
9398
DueDateMailer.category_due(due_date, self).deliver_now
9499
end
95-
96-
children_due_dates.are_due.each do |due_date|
97-
DueDateMailer.category_due(due_date, self).deliver_now
98-
end
99100
end
100101

101102
def send_overdue_emails
102103
due_dates.are_overdue.each do |due_date|
103104
DueDateMailer.category_overdue(due_date, self).deliver_now
104105
end
105-
106-
children_due_dates.are_overdue.each do |due_date|
107-
DueDateMailer.category_overdue(due_date, self).deliver_now
108-
end
109106
end
110107
end

app/models/recommendation.rb

+18-3
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,25 @@ class Recommendation < VersionedRecord
88

99
has_many :measures, through: :recommendation_measures
1010
has_many :categories, through: :recommendation_categories
11-
has_many :indicators, through: :recommendation_indicators
11+
12+
has_many :indicators_direct, through: :recommendation_indicators, source: :indicator
1213
has_many :indicators_via_measures, through: :measures, source: :indicators
13-
has_many :progress_reports, through: :indicators
14-
has_many :due_dates, through: :indicators
14+
15+
def indicator_ids
16+
(indicators_direct.pluck(:id) + indicators_via_measures.pluck(:id)).uniq
17+
end
18+
19+
def indicators
20+
Indicator.where(id: indicator_ids)
21+
end
22+
23+
def due_dates
24+
DueDate.where(indicator_id: indicator_ids)
25+
end
26+
27+
def progress_reports
28+
ProgressReport.where(indicator_id: indicator_ids)
29+
end
1530

1631
has_many :recommendation_recommendations, foreign_key: "recommendation_id"
1732
has_many :recommendations, through: :recommendation_recommendations, source: :other_recommendation

spec/models/category_spec.rb

+94-4
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@
88
it { is_expected.to have_many :recommendations }
99
it { is_expected.to have_many :users }
1010
it { is_expected.to have_many :measures }
11-
it { is_expected.to have_many :indicators }
12-
it { is_expected.to have_many :progress_reports }
13-
it { is_expected.to have_many :due_dates }
1411
it { is_expected.to have_many :categories }
15-
it { is_expected.to have_many :children_due_dates }
1612

1713
context "Sub-relation validations" do
1814
it "will not allow guest users to be assigned" do
@@ -85,4 +81,98 @@
8581
expect { sub_category.save! }.to raise_exception(/Validation failed: Parent Taxonomy does not have parent category's taxonomy as parent./)
8682
end
8783
end
84+
85+
describe "indicators" do
86+
subject { FactoryBot.create(:category) }
87+
88+
let(:indicator_traits) { [] }
89+
90+
let(:child_taxonomy) { FactoryBot.create(:taxonomy, parent_id: subject.taxonomy.id) }
91+
let(:child_category) { FactoryBot.create(:category, parent_id: subject.id, title: "Child Category", taxonomy: child_taxonomy) }
92+
93+
let(:first_recommendation) { FactoryBot.create(:recommendation, title: "First Recommendation") }
94+
let(:second_recommendation) { FactoryBot.create(:recommendation, title: "Second Recommendation") }
95+
let(:child_category_recommendation) { FactoryBot.create(:recommendation, title: "Child Category Recommendation") }
96+
97+
let(:first_direct_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "First Direct Indicator") }
98+
let(:second_direct_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "Second Direct Indicator") }
99+
let(:third_direct_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "Second Direct Indicator") }
100+
101+
let(:indicator_via_first_measure) { FactoryBot.create(:indicator, *indicator_traits, title: "Indicator via First Measure") }
102+
let(:indicator_via_second_measure) { FactoryBot.create(:indicator, *indicator_traits, title: "Indicator via Second Measure") }
103+
104+
let(:first_shared_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "First Shared Indicator") }
105+
let(:second_shared_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "Second Shared Indicator") }
106+
107+
let(:first_measure) { FactoryBot.create(:measure) }
108+
let(:second_measure) { FactoryBot.create(:measure) }
109+
110+
let(:all_unique_indicators) do
111+
[
112+
first_direct_indicator,
113+
second_direct_indicator,
114+
third_direct_indicator,
115+
indicator_via_first_measure,
116+
indicator_via_second_measure,
117+
first_shared_indicator,
118+
second_shared_indicator
119+
]
120+
end
121+
122+
before do
123+
# Create direct indicators: two for the first recommendation, one for the second, and one for the child category recommendation
124+
FactoryBot.create(:recommendation_indicator, recommendation: first_recommendation, indicator: first_direct_indicator)
125+
FactoryBot.create(:recommendation_indicator, recommendation: first_recommendation, indicator: second_direct_indicator)
126+
127+
FactoryBot.create(:recommendation_indicator, recommendation: second_recommendation, indicator: second_direct_indicator)
128+
129+
FactoryBot.create(:recommendation_indicator, recommendation: child_category_recommendation, indicator: third_direct_indicator)
130+
131+
# Create indicators via measures
132+
FactoryBot.create(:measure_indicator, measure: first_measure, indicator: indicator_via_first_measure)
133+
FactoryBot.create(:measure_indicator, measure: second_measure, indicator: indicator_via_second_measure)
134+
135+
# Associate measures with the recommendation: one for the first recommendation, two for the second, and one for the child category recommendation
136+
FactoryBot.create(:recommendation_measure, recommendation: first_recommendation, measure: first_measure)
137+
138+
FactoryBot.create(:recommendation_measure, recommendation: second_recommendation, measure: first_measure)
139+
FactoryBot.create(:recommendation_measure, recommendation: second_recommendation, measure: second_measure)
140+
141+
FactoryBot.create(:recommendation_measure, recommendation: child_category_recommendation, measure: second_measure)
142+
143+
# Create shared indicators: both linked to every recommendation
144+
FactoryBot.create(:measure_indicator, measure: first_measure, indicator: first_shared_indicator)
145+
FactoryBot.create(:recommendation_indicator, recommendation: first_recommendation, indicator: first_shared_indicator)
146+
FactoryBot.create(:recommendation_indicator, recommendation: second_recommendation, indicator: first_shared_indicator)
147+
FactoryBot.create(:recommendation_indicator, recommendation: child_category_recommendation, indicator: first_shared_indicator)
148+
149+
FactoryBot.create(:measure_indicator, measure: second_measure, indicator: second_shared_indicator)
150+
FactoryBot.create(:recommendation_indicator, recommendation: first_recommendation, indicator: second_shared_indicator)
151+
FactoryBot.create(:recommendation_indicator, recommendation: second_recommendation, indicator: second_shared_indicator)
152+
FactoryBot.create(:recommendation_indicator, recommendation: child_category_recommendation, indicator: second_shared_indicator)
153+
154+
# Associate recommendations to the category
155+
FactoryBot.create(:recommendation_category, recommendation: first_recommendation, category: subject)
156+
FactoryBot.create(:recommendation_category, recommendation: second_recommendation, category: subject)
157+
158+
# Associate child category
159+
FactoryBot.create(:recommendation_category, recommendation: child_category_recommendation, category: child_category)
160+
end
161+
162+
describe "#combined_indicator_ids" do
163+
it "contains the IDs of all distinct indicators from recommendations and the recommendations of child categories" do
164+
expect(subject.combined_indicator_ids).not_to be_empty
165+
expect(subject.combined_indicator_ids).to match_array(all_unique_indicators.map(&:id))
166+
end
167+
end
168+
169+
describe "#due_dates" do
170+
let(:indicator_traits) { [:with_12_due_dates] }
171+
172+
it "contains the due dates for all distinct indicators from recommendations and the recommendations of child categories" do
173+
expect(subject.due_dates.to_a).not_to be_empty
174+
expect(subject.due_dates.to_a).to match_array(all_unique_indicators.flat_map(&:due_dates))
175+
end
176+
end
177+
end
88178
end

spec/models/recommendation_spec.rb

+85-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@
1515

1616
it { is_expected.to have_many :categories }
1717
it { is_expected.to have_many :measures }
18-
it { is_expected.to have_many :indicators }
19-
it { is_expected.to have_many :progress_reports }
20-
it { is_expected.to have_many :due_dates }
2118
it { is_expected.to have_many :recommendations }
2219
it { is_expected.to have_many :recommendation_recommendations }
2320
it { is_expected.to have_many :recommendation_categories }
@@ -73,4 +70,89 @@
7370
end
7471
end
7572
end
73+
74+
describe "indicators" do
75+
subject { FactoryBot.create(:recommendation) }
76+
77+
let(:indicator_traits) {
78+
[]
79+
}
80+
81+
let(:first_direct_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "First Direct Indicator") }
82+
let(:second_direct_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "Second Direct Indicator") }
83+
84+
let(:indicator_via_first_measure) { FactoryBot.create(:indicator, *indicator_traits, title: "Indicator via First Measure") }
85+
let(:indicator_via_second_measure) { FactoryBot.create(:indicator, *indicator_traits, title: "Indicator via Second Measure") }
86+
87+
let(:first_shared_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "First Shared Indicator") }
88+
let(:second_shared_indicator) { FactoryBot.create(:indicator, *indicator_traits, title: "Second Shared Indicator") }
89+
90+
let(:first_measure) { FactoryBot.create(:measure) }
91+
let(:second_measure) { FactoryBot.create(:measure) }
92+
93+
let(:all_unique_indicators) {
94+
[
95+
first_direct_indicator,
96+
second_direct_indicator,
97+
indicator_via_first_measure,
98+
indicator_via_second_measure,
99+
first_shared_indicator,
100+
second_shared_indicator
101+
102+
]
103+
}
104+
105+
before do
106+
# Create direct indicators
107+
FactoryBot.create(:recommendation_indicator, recommendation: subject, indicator: first_direct_indicator)
108+
FactoryBot.create(:recommendation_indicator, recommendation: subject, indicator: second_direct_indicator)
109+
110+
# Create indicators via measures
111+
FactoryBot.create(:measure_indicator, measure: first_measure, indicator: indicator_via_first_measure)
112+
FactoryBot.create(:measure_indicator, measure: second_measure, indicator: indicator_via_second_measure)
113+
114+
# Associate measures with the recommendation
115+
FactoryBot.create(:recommendation_measure, recommendation: subject, measure: first_measure)
116+
FactoryBot.create(:recommendation_measure, recommendation: subject, measure: second_measure)
117+
118+
# Create shared indicators
119+
FactoryBot.create(:measure_indicator, measure: first_measure, indicator: first_shared_indicator)
120+
FactoryBot.create(:recommendation_indicator, recommendation: subject, indicator: first_shared_indicator)
121+
FactoryBot.create(:measure_indicator, measure: second_measure, indicator: second_shared_indicator)
122+
FactoryBot.create(:recommendation_indicator, recommendation: subject, indicator: second_shared_indicator)
123+
end
124+
125+
it "contains all distinct indicators from measures and recommendations" do
126+
expect(subject.indicators.to_a).to match_array(all_unique_indicators)
127+
end
128+
129+
describe "indicator_ids" do
130+
it "contains all distinct indicator ids from measures and recommendations" do
131+
expect(subject.indicator_ids).not_to be_empty
132+
expect(subject.indicator_ids).to match_array(all_unique_indicators.map(&:id))
133+
end
134+
end
135+
136+
context "with due_dates" do
137+
let(:indicator_traits) { [:with_12_due_dates] }
138+
139+
it "forwards the due_dates from the combined indicators" do
140+
expect(subject.due_dates.to_a).not_to be_empty
141+
expect(subject.due_dates.to_a).to match_array(subject.indicators.flat_map(&:due_dates))
142+
end
143+
end
144+
145+
context "with progress reports" do
146+
before do
147+
all_unique_indicators.each do |unique_indicator|
148+
FactoryBot.create(:progress_report, indicator: unique_indicator)
149+
end
150+
end
151+
152+
it "forwards the progress reports from the combined indicators" do
153+
expect(subject.progress_reports.to_a).not_to be_empty
154+
expect(subject.progress_reports.to_a).to match_array(subject.indicators.flat_map(&:progress_reports))
155+
end
156+
end
157+
end
76158
end

0 commit comments

Comments
 (0)