diff --git a/app/presenters/member_presenter.rb b/app/presenters/member_presenter.rb index 807aa65f4..526d012ec 100644 --- a/app/presenters/member_presenter.rb +++ b/app/presenters/member_presenter.rb @@ -4,7 +4,10 @@ def organiser? end def event_organiser?(event) - has_role?(:organiser, event) || has_role?(:organiser, event.chapter) || has_role?(:admin) + event_types = %w[Workshop Meeting Event] + organising_events.slice(*event_types).values.any? { |ids| ids.include?(event.id) } || + (event.chapter && organising_events['Chapter']&.include?(event.chapter.id)) || + admin? end def newbie? @@ -33,6 +36,17 @@ def displayed_dietary_restrictions private + def organising_events + @organised_events ||= roles.where(name: 'organiser') + .pluck(:resource_type, :resource_id) + .group_by(&:first) + .transform_values { |pairs| pairs.map(&:last) } + end + + def admin? + @is_admin ||= has_role?(:admin) + end + def coach_pairing_details(note) [newbie?, full_name, 'Coach', 'N/A', note, skill_list.to_s] end diff --git a/spec/presenters/member_presenter_spec.rb b/spec/presenters/member_presenter_spec.rb index d0c607e6a..3c4429146 100644 --- a/spec/presenters/member_presenter_spec.rb +++ b/spec/presenters/member_presenter_spec.rb @@ -38,4 +38,68 @@ expect(member_presenter.attending?(event)).to be false end end + + describe '#event_organiser?' do + let(:chapter) { Fabricate(:chapter) } + let(:workshop) { Fabricate(:workshop_no_sponsor, chapter: chapter) } + + it 'returns true when user is admin' do + admin = Fabricate(:member) + admin.add_role(:admin) + presenter = MemberPresenter.new(admin) + + expect(presenter.event_organiser?(workshop)).to be true + end + + it 'returns true when user is direct organiser of the event' do + member.add_role(:organiser, workshop) + + expect(member_presenter.event_organiser?(workshop)).to be true + end + + it 'returns true when user is organiser of the event chapter' do + organiser = Fabricate(:member) + organiser.add_role(:organiser, chapter) + presenter = MemberPresenter.new(organiser) + + expect(presenter.event_organiser?(workshop)).to be true + end + + it 'returns false for a regular member' do + regular = Fabricate(:member) + presenter = MemberPresenter.new(regular) + + expect(presenter.event_organiser?(workshop)).to be false + end + + it 'returns false for organiser of a different chapter' do + other_chapter = Fabricate(:chapter) + other_workshop = Fabricate(:workshop_no_sponsor, chapter: other_chapter) + organiser = Fabricate(:member) + organiser.add_role(:organiser, other_chapter) + presenter = MemberPresenter.new(organiser) + + expect(presenter.event_organiser?(workshop)).to be false + end + + it 'handles events without a singular chapter association (e.g. Meeting) without crashing' do + meeting = Fabricate(:meeting) + meeting_presenter = MeetingPresenter.new(meeting) + admin = Fabricate(:member) + admin.add_role(:admin) + presenter = MemberPresenter.new(admin) + + expect(presenter.event_organiser?(meeting_presenter)).to be true + end + + it 'memoizes private methods across calls' do + member.add_role(:admin) + + expect(member).to receive(:has_role?).with(:admin).once.and_call_original + # First call loads and caches + member_presenter.event_organiser?(workshop) + # Second call uses cache + member_presenter.event_organiser?(workshop) + end + end end