diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b398d5420..a3c89b3bf 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -19,6 +19,12 @@ class ApplicationController < ActionController::Base rescue_from ActionController::RoutingError, with: :render_not_found rescue_from ActiveRecord::RecordNotFound, with: :render_not_found + rescue_from ActiveRecord::RecordInvalid do |exception| + Rails.logger.error("Validation failed: #{exception.message}") + redirect_back(fallback_location: root_path, + alert: 'Unable to complete action. Please check your input and try again.') + end + rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized rescue_from Pundit::AuthorizationNotPerformedError, with: :user_not_authorized diff --git a/app/controllers/concerns/admin/sponsor_concerns.rb b/app/controllers/concerns/admin/sponsor_concerns.rb index 7105c1be0..05d939486 100644 --- a/app/controllers/concerns/admin/sponsor_concerns.rb +++ b/app/controllers/concerns/admin/sponsor_concerns.rb @@ -27,14 +27,14 @@ def destroy_sponsor def host set_sponsor @workshop_sponsor = WorkshopSponsor.find_or_create_by(workshop: @workshop, sponsor: @sponsor) - @workshop_sponsor.update_attribute(:host, true) + @workshop_sponsor.set_as_host! flash[:notice] = 'Host set successfully' redirect_back fallback_location: root_path end def destroy_host - @workshop.workshop_sponsors.find_by(host: true).update_attribute(:host, false) + @workshop.workshop_sponsors.find_by(host: true)&.remove_as_host! redirect_back fallback_location: root_path end diff --git a/app/controllers/concerns/invitation_controller_concerns.rb b/app/controllers/concerns/invitation_controller_concerns.rb index 62c8cfc58..650d37b8b 100644 --- a/app/controllers/concerns/invitation_controller_concerns.rb +++ b/app/controllers/concerns/invitation_controller_concerns.rb @@ -35,26 +35,25 @@ def accept def reject @workshop = WorkshopPresenter.decorate(@invitation.workshop) if @invitation.workshop.date_and_time - 3.5.hours >= Time.zone.now - if @invitation.attending.eql? false redirect_back(fallback_location: invitation_path(@invitation), notice: t('messages.not_attending_already')) else - @invitation.update_attribute(:attending, false) - - next_spot = WaitingList.next_spot(@invitation.workshop, @invitation.role) - - if next_spot.present? - invitation = next_spot.invitation - next_spot.destroy - invitation.update(attending: true, rsvp_time: Time.zone.now, automated_rsvp: true) - @workshop.send_attending_email(invitation, true) + begin + WorkshopInvitation.transaction do + @invitation.decline! + promote_from_waitlist(@invitation.workshop, @invitation.role) + end + redirect_back( + fallback_location: invitation_path(@invitation), + notice: t('messages.rejected_invitation', name: @invitation.member.name) + ) + rescue ActiveRecord::RecordInvalid + redirect_back( + fallback_location: invitation_path(@invitation), + alert: 'Unable to process cancellation. Please try again.' + ) end - - redirect_back( - fallback_location: invitation_path(@invitation), - notice: t('messages.rejected_invitation', name: @invitation.member.name) - ) end else redirect_back( @@ -66,6 +65,16 @@ def reject private + def promote_from_waitlist(workshop, role) + next_spot = WaitingList.next_spot(workshop, role) + return unless next_spot + + invitation = next_spot.invitation + next_spot.destroy! + invitation.accept!(rsvp_time: Time.zone.now, automated_rsvp: true) + WorkshopPresenter.decorate(workshop).send_attending_email(invitation, true) + end + def attending_or_waitlisted?(workshop, user) workshop.attendee?(user) || workshop.waitlisted?(user) end diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb index fb4964b26..da4aea7fe 100644 --- a/app/controllers/invitations_controller.rb +++ b/app/controllers/invitations_controller.rb @@ -27,12 +27,12 @@ def attend return redirect_back fallback_location: root_path, notice: t('messages.already_rsvped') if @invitation.attending? if @invitation.student_spaces? || @invitation.coach_spaces? - @invitation.update_attribute(:attending, true) + @invitation.accept! notice = t('messages.invitations.spot_confirmed', event: @invitation.event.name) unless event.confirmation_required || event.surveys_required - @invitation.update_attribute(:verified, true) + @invitation.verify! EventInvitationMailer.attending(@invitation.event, @invitation.member, @invitation).deliver_now end notice = t('messages.invitations.spot_not_confirmed') if event.surveys_required @@ -54,7 +54,7 @@ def reject ) end - @invitation.update_attribute(:attending, false) + @invitation.decline! redirect_back( fallback_location: root_path, notice: t('messages.rejected_invitation', name: @invitation.member.name) @@ -79,7 +79,7 @@ def rsvp_meeting def cancel_meeting @invitation = MeetingInvitation.find_by(token: params[:token]) - @invitation.update_attribute(:attending, false) + @invitation.decline! redirect_back fallback_location: root_path, notice: t('messages.invitations.meeting.cancel') end diff --git a/app/models/invitation.rb b/app/models/invitation.rb index 46d554406..84e1b2825 100644 --- a/app/models/invitation.rb +++ b/app/models/invitation.rb @@ -24,4 +24,16 @@ def coach_spaces? def to_param token end + + def accept!(verified: false) + update!(attending: true, verified: verified) + end + + def verify! + update!(verified: true) + end + + def decline! + update!(attending: false) + end end diff --git a/app/models/meeting_invitation.rb b/app/models/meeting_invitation.rb index e5f5aef8e..71d83e96a 100644 --- a/app/models/meeting_invitation.rb +++ b/app/models/meeting_invitation.rb @@ -11,4 +11,12 @@ class MeetingInvitation < ApplicationRecord scope :attended, -> { where(attended: true) } alias event meeting + + def accept! + update!(attending: true) + end + + def decline! + update!(attending: false) + end end diff --git a/app/models/workshop_invitation.rb b/app/models/workshop_invitation.rb index d61b48109..9264d1fd7 100644 --- a/app/models/workshop_invitation.rb +++ b/app/models/workshop_invitation.rb @@ -47,4 +47,12 @@ def student_attending? def not_attending? attending == false end + + def accept!(rsvp_time: Time.zone.now, automated_rsvp: false) + update!(attending: true, rsvp_time: rsvp_time, automated_rsvp: automated_rsvp) + end + + def decline! + update!(attending: false) + end end diff --git a/app/models/workshop_sponsor.rb b/app/models/workshop_sponsor.rb index 49a49b6a4..633fead77 100644 --- a/app/models/workshop_sponsor.rb +++ b/app/models/workshop_sponsor.rb @@ -3,4 +3,12 @@ class WorkshopSponsor < ApplicationRecord belongs_to :workshop validates :sponsor_id, uniqueness: { scope: :workshop_id, message: :already_sponsoring } + + def set_as_host! + update!(host: true) + end + + def remove_as_host! + update!(host: false) + end end diff --git a/spec/controllers/admin/invitations_controller_spec.rb b/spec/controllers/admin/invitations_controller_spec.rb index efd1c88d5..bb65411e1 100644 --- a/spec/controllers/admin/invitations_controller_spec.rb +++ b/spec/controllers/admin/invitations_controller_spec.rb @@ -3,43 +3,43 @@ let(:workshop) { invitation.workshop } let(:admin) { Fabricate(:chapter_organiser) } - describe "PUT #update" do + describe 'PUT #update' do before do admin.add_role(:organiser, workshop.chapter) login admin - request.env["HTTP_REFERER"] = "/admin/member/3" + request.env['HTTP_REFERER'] = '/admin/member/3' expect(invitation.attending).to be_nil end - it "Successfuly updates an invitation" do - put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: "true" } - + it 'Successfuly updates an invitation' do + put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: 'true' } + expect(invitation.reload.attending).to be true - expect(flash[:notice]).to match("You have added") + expect(flash[:notice]).to match('You have added') end # While similar to the previous test, this specifically tests that organisers # have the ability to manually add a student to the workshop that has not # selected a tutorial. This is helpful for when a student shows up for a # workshop they have not have a spot — this happens from time to time. - it "Successfuly adds a user as attenting, even without a tutorial" do - invitation.update_attribute(:tutorial, nil) + it 'Successfuly adds a user as attenting, even without a tutorial' do + invitation.update_column(:tutorial, nil) expect(invitation.automated_rsvp).to be_nil - put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: "true" } + put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: 'true' } invitation.reload expect(invitation.attending).to be true expect(invitation.automated_rsvp).to be true - expect(flash[:notice]).to match("You have added") + expect(flash[:notice]).to match('You have added') end - it "Records the organiser ID that overrides an invitations" do - put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: "true" } + it 'Records the organiser ID that overrides an invitations' do + put :update, params: { id: invitation.token, workshop_id: workshop.id, attending: 'true' } invitation.reload - + expect(invitation.last_overridden_by_id).to be admin.id end end diff --git a/spec/features/accepting_invitation_spec.rb b/spec/features/accepting_invitation_spec.rb index 18a8cf406..142987e1f 100644 --- a/spec/features/accepting_invitation_spec.rb +++ b/spec/features/accepting_invitation_spec.rb @@ -1,18 +1,18 @@ RSpec.feature 'Accepting a workshop invitation', type: :feature do - context '#workshop' do + describe '#workshop' do let(:member) { Fabricate(:member) } let(:invitation) { Fabricate(:workshop_invitation, member: member, tutorial: tutorial.title) } let(:invitation_route) { invitation_path(invitation) } let(:accept_invitation_route) { accept_invitation_path(invitation) } let(:reject_invitation_route) { reject_invitation_path(invitation) } - let(:set_no_available_slots) { invitation.workshop.host.update_attribute(:seats, 0) } + let(:set_no_available_slots) { invitation.workshop.host.update_column(:seats, 0) } let!(:tutorial) { Fabricate(:tutorial) } it_behaves_like 'invitation route' context 'amend invitation details' do context 'a student' do - scenario 'cannot accept an invitation without a tutorial' do + scenario 'cannot accept an invitation without a tutorial' do invitation.update(attending: nil, tutorial: nil) visit invitation_route @@ -22,7 +22,7 @@ end scenario 'with an accepted invitation can edit the tutorial' do - invitation.update_attribute(:attending, true) + invitation.accept! visit invitation_route select tutorial.title, from: :workshop_invitation_tutorial @@ -66,7 +66,7 @@ click_on 'Update note' expect(page).to have_field('workshop_invitation_note', with: note) - expect(page).to have_content("Invitation details successfully updated.") + expect(page).to have_content('Invitation details successfully updated.') end end end diff --git a/spec/features/coach_accepting_invitation_spec.rb b/spec/features/coach_accepting_invitation_spec.rb index 3d6093ae7..e710ea35a 100644 --- a/spec/features/coach_accepting_invitation_spec.rb +++ b/spec/features/coach_accepting_invitation_spec.rb @@ -1,14 +1,14 @@ RSpec.feature 'a Coach can', type: :feature do - context '#workshop' do + describe '#workshop' do let(:member) { Fabricate(:member) } let(:invitation) { Fabricate(:coach_workshop_invitation, member: member) } let(:invitation_route) { invitation_path(invitation) } let(:reject_invitation_route) { reject_invitation_path(invitation) } let(:accept_invitation_route) { accept_invitation_path(invitation) } - let(:set_no_available_slots) { invitation.workshop.host.update_attribute(:seats, 0) } + let(:set_no_available_slots) { invitation.workshop.host.update_column(:seats, 0) } - before(:each) do + before do login(member) end diff --git a/spec/models/invitation_spec.rb b/spec/models/invitation_spec.rb index 600603840..0ec2a71a6 100644 --- a/spec/models/invitation_spec.rb +++ b/spec/models/invitation_spec.rb @@ -12,7 +12,7 @@ it { is_expected.to validate_inclusion_of(:role).in_array(%w[Student Coach]) } end - context '#student_spaces?' do + describe '#student_spaces?' do it 'checks if there are any available spaces for students at the event' do student_invitation = Fabricate(:invitation) @@ -20,11 +20,63 @@ end end - context '#coach_spaces?' do + describe '#coach_spaces?' do it 'checks if there are any available spaces for coaches at the event' do coach_invitation = Fabricate(:coach_invitation) expect(coach_invitation.coach_spaces?).to eq(true) end end + + describe '#accept!' do + let(:invitation) { Fabricate(:invitation, attending: false) } + + it 'sets attending to true' do + invitation.accept! + expect(invitation.reload.attending).to be true + end + + it 'does not verify by default' do + invitation.accept! + expect(invitation.reload.verified).to be false + end + + it 'allows immediate verification' do + invitation.accept!(verified: true) + expect(invitation.reload.verified).to be true + end + + it 'raises RecordInvalid on validation failure' do + allow(invitation).to receive(:valid?).and_return(false) + expect { invitation.accept! }.to raise_error(ActiveRecord::RecordInvalid) + end + end + + describe '#verify!' do + let(:invitation) { Fabricate(:invitation, verified: false) } + + it 'sets verified to true' do + invitation.verify! + expect(invitation.reload.verified).to be true + end + + it 'raises RecordInvalid on validation failure' do + allow(invitation).to receive(:valid?).and_return(false) + expect { invitation.verify! }.to raise_error(ActiveRecord::RecordInvalid) + end + end + + describe '#decline!' do + let(:invitation) { Fabricate(:invitation, attending: true) } + + it 'sets attending to false' do + invitation.decline! + expect(invitation.reload.attending).to be false + end + + it 'raises RecordInvalid on validation failure' do + allow(invitation).to receive(:valid?).and_return(false) + expect { invitation.decline! }.to raise_error(ActiveRecord::RecordInvalid) + end + end end diff --git a/spec/models/meeting_invitation_spec.rb b/spec/models/meeting_invitation_spec.rb index 7e4561765..c93b66eb0 100644 --- a/spec/models/meeting_invitation_spec.rb +++ b/spec/models/meeting_invitation_spec.rb @@ -11,4 +11,32 @@ it { is_expected.to validate_presence_of(:member) } it { is_expected.to validate_uniqueness_of(:member_id).scoped_to(:meeting_id) } end + + describe '#accept!' do + let(:invitation) { Fabricate(:meeting_invitation, attending: false) } + + it 'sets attending to true' do + invitation.accept! + expect(invitation.reload.attending).to be true + end + + it 'raises RecordInvalid on validation failure' do + allow(invitation).to receive(:valid?).and_return(false) + expect { invitation.accept! }.to raise_error(ActiveRecord::RecordInvalid) + end + end + + describe '#decline!' do + let(:invitation) { Fabricate(:meeting_invitation, attending: true) } + + it 'sets attending to false' do + invitation.decline! + expect(invitation.reload.attending).to be false + end + + it 'raises RecordInvalid on validation failure' do + allow(invitation).to receive(:valid?).and_return(false) + expect { invitation.decline! }.to raise_error(ActiveRecord::RecordInvalid) + end + end end diff --git a/spec/models/workshop_invitation_spec.rb b/spec/models/workshop_invitation_spec.rb index f089a02f9..52d9de1dd 100644 --- a/spec/models/workshop_invitation_spec.rb +++ b/spec/models/workshop_invitation_spec.rb @@ -1,5 +1,6 @@ RSpec.describe WorkshopInvitation do subject(:workshop_invitation) { Fabricate(:workshop_invitation) } + it_behaves_like InvitationConcerns, :workshop_invitation, :workshop context 'defaults' do @@ -14,13 +15,14 @@ context 'if Student invitation' do before { allow(subject).to receive(:student_attending?).and_return(true) } + it { is_expected.to validate_presence_of(:tutorial) } it { is_expected.to validate_presence_of(:tutorial).on(:waitinglist) } end end context 'scopes' do - context '#attended' do + describe '#attended' do it 'ignores when attended nil' do Fabricate(:workshop_invitation, attended: nil) @@ -40,7 +42,7 @@ end end - context '#accepted_or_attended' do + describe '#accepted_or_attended' do it 'ignores when attending nil and attended nil' do Fabricate(:workshop_invitation, attending: nil, attended: nil) @@ -83,7 +85,7 @@ expect(WorkshopInvitation.year((Time.zone.now - 2.years).year).count).to eq(1) end - context '#not_reminded' do + describe '#not_reminded' do it 'includes invitations without reminders' do not_reminded = Fabricate(:student_workshop_invitation, reminded_at: nil) @@ -104,4 +106,48 @@ expect(WorkshopInvitation.on_waiting_list).to eq(waiting_list) end end + + describe '#accept!' do + let(:invitation) { Fabricate(:workshop_invitation, attending: false) } + + it 'sets attending to true' do + invitation.accept! + expect(invitation.reload.attending).to be true + end + + it 'sets rsvp_time to current time by default' do + invitation.accept! + expect(invitation.reload.rsvp_time).to be_within(1.second).of(Time.zone.now) + end + + it 'allows custom rsvp_time' do + custom_time = 1.day.ago + invitation.accept!(rsvp_time: custom_time) + expect(invitation.reload.rsvp_time).to be_within(1.second).of(custom_time) + end + + it 'allows automated_rsvp flag' do + invitation.accept!(automated_rsvp: true) + expect(invitation.reload.automated_rsvp).to be true + end + + it 'raises RecordInvalid on validation failure' do + allow(invitation).to receive(:valid?).and_return(false) + expect { invitation.accept! }.to raise_error(ActiveRecord::RecordInvalid) + end + end + + describe '#decline!' do + let(:invitation) { Fabricate(:workshop_invitation, attending: true) } + + it 'sets attending to false' do + invitation.decline! + expect(invitation.reload.attending).to be false + end + + it 'raises RecordInvalid on validation failure' do + allow(invitation).to receive(:valid?).and_return(false) + expect { invitation.decline! }.to raise_error(ActiveRecord::RecordInvalid) + end + end end diff --git a/spec/models/workshop_spec.rb b/spec/models/workshop_spec.rb index 7fbc22bf1..1787e43b4 100644 --- a/spec/models/workshop_spec.rb +++ b/spec/models/workshop_spec.rb @@ -1,12 +1,13 @@ RSpec.describe Workshop do subject(:workshop) { Fabricate(:workshop) } - include_examples "Invitable", :workshop_invitation, :workshop + + include_examples 'Invitable', :workshop_invitation, :workshop include_examples DateTimeConcerns, :workshop context 'validates' do it { is_expected.to validate_presence_of(:chapter_id) } - context "#date_and_time" do + describe '#date_and_time' do it 'does not validate if chapter_id blank' do workshop.chapter_id = nil workshop.date_and_time = nil @@ -22,7 +23,7 @@ end end - context '#end_at' do + describe '#end_at' do it 'does not validate if chapter_id blank' do workshop.chapter_id = nil workshop.ends_at = nil @@ -40,6 +41,7 @@ context 'if virtual' do before { allow(subject).to receive(:virtual?).and_return(true) } + it { is_expected.to validate_presence_of(:slack_channel) } it { is_expected.to validate_presence_of(:slack_channel_link) } it { is_expected.to validate_numericality_of(:student_spaces).is_greater_than(0) } @@ -63,7 +65,8 @@ end it 'retrieves the local time from the saved UTC value' do - workshop.update_attribute(:date_and_time, utc_time) + workshop.assign_attributes(date_and_time: utc_time) + workshop.save!(validate: false) expect(workshop.date_and_time).to eq(pacific_time) expect(workshop.date_and_time.zone).to eq('PDT') @@ -81,7 +84,8 @@ end it 'retrieves the local time from the saved UTC value' do - workshop.update_attribute(:rsvp_opens_at, utc_time) + workshop.assign_attributes(rsvp_opens_at: utc_time) + workshop.save!(validate: false) expect(workshop.rsvp_opens_at).to eq(pacific_time) expect(workshop.rsvp_opens_at.zone).to eq('PDT') @@ -89,7 +93,7 @@ end end - context '#rsvp_available?' do + describe '#rsvp_available?' do context 'rsvp is available' do it 'when the event is in the future' do workshop.date_and_time = 1.day.from_now @@ -119,7 +123,7 @@ end end - context '#to_s' do + describe '#to_s' do it 'when physical workshop' do expect(workshop.to_s).to eq('Workshop') end @@ -130,7 +134,7 @@ end end - context '#scopes' do + describe '#scopes' do describe '#host' do it 'includes workshops with sponsored hosts' do workshop_sponsor = Fabricate(:workshop_sponsor, host: true) @@ -189,7 +193,7 @@ end end - context '#invitable_yet?' do + describe '#invitable_yet?' do it 'is invitable if invitable set to true, no RSVP open time/date set' do workshop = Fabricate.build(:workshop, invitable: true) expect(workshop.invitable_yet?).to be true diff --git a/spec/models/workshop_sponsor_spec.rb b/spec/models/workshop_sponsor_spec.rb index 3d0726a66..261307a28 100644 --- a/spec/models/workshop_sponsor_spec.rb +++ b/spec/models/workshop_sponsor_spec.rb @@ -1,9 +1,37 @@ RSpec.describe WorkshopSponsor do context 'validates' do it 'sponsor_id for uniqueness' do - is_expected.to validate_uniqueness_of(:sponsor_id) + expect(subject).to validate_uniqueness_of(:sponsor_id) .scoped_to(:workshop_id) .with_message('already a sponsor') end end + + describe '#set_as_host!' do + let(:workshop_sponsor) { Fabricate(:workshop_sponsor, host: false) } + + it 'sets host to true' do + workshop_sponsor.set_as_host! + expect(workshop_sponsor.reload.host).to be true + end + + it 'raises RecordInvalid on validation failure' do + allow(workshop_sponsor).to receive(:valid?).and_return(false) + expect { workshop_sponsor.set_as_host! }.to raise_error(ActiveRecord::RecordInvalid) + end + end + + describe '#remove_as_host!' do + let(:workshop_sponsor) { Fabricate(:workshop_sponsor, host: true) } + + it 'sets host to false' do + workshop_sponsor.remove_as_host! + expect(workshop_sponsor.reload.host).to be false + end + + it 'raises RecordInvalid on validation failure' do + allow(workshop_sponsor).to receive(:valid?).and_return(false) + expect { workshop_sponsor.remove_as_host! }.to raise_error(ActiveRecord::RecordInvalid) + end + end end diff --git a/spec/presenters/invitation_presenter_spec.rb b/spec/presenters/invitation_presenter_spec.rb index 965cab438..4634ccfc4 100644 --- a/spec/presenters/invitation_presenter_spec.rb +++ b/spec/presenters/invitation_presenter_spec.rb @@ -6,9 +6,9 @@ expect(invitation_presenter.member).to eq(invitation.member) end - context '#attendance_status' do + describe '#attendance_status' do it 'returns Attending when attending' do - invitation.update_attribute(:attending, true) + invitation.accept! expect(invitation_presenter.attendance_status).to eq('Attending') end diff --git a/spec/support/shared_examples/behaves_like_an_invitation_route.rb b/spec/support/shared_examples/behaves_like_an_invitation_route.rb index 968fb15f3..d5840c706 100644 --- a/spec/support/shared_examples/behaves_like_an_invitation_route.rb +++ b/spec/support/shared_examples/behaves_like_an_invitation_route.rb @@ -17,7 +17,7 @@ expect(page).to have_link 'I can no longer attend' expect(page).to have_content("Thanks for getting back to us #{invitation.member.name}.") - expect(page.current_path).to eq(invitation_route) + expect(page).to have_current_path(invitation_route, ignore_query: true) # admin view login_as_admin(member) @@ -25,7 +25,7 @@ within 'div.row.attendee.mt-3' do expect(page).to have_content(member.full_name) expect(page).to have_selector('i.fa-history') - expect(page).to_not have_selector('i.fa-magic') + expect(page).not_to have_selector('i.fa-magic') end end @@ -34,7 +34,7 @@ invitation.update(role: 'Student', attending: nil, tutorial: nil) visit accept_invitation_route - expect(page).to_not have_link 'I can no longer attend' + expect(page).not_to have_link 'I can no longer attend' expect(page).to have_content('Tutorial must be selected') end @@ -43,13 +43,13 @@ visit accept_invitation_route expect(page).to have_link 'I can no longer attend' - expect(page.current_path).to eq(invitation_route) + expect(page).to have_current_path(invitation_route, ignore_query: true) expect(page).to have_content(I18n.t('messages.accepted_invitation', name: member.name)) end end scenario 'when they have already RSVPed and are attempting to re-accept through the invitation link' do - invitation.update_attribute(:attending, true) + invitation.accept! visit accept_invitation_route expect(current_url).to eq(invitation_url(invitation)) @@ -75,7 +75,7 @@ context 'unable to attend' do scenario 'when they are successful' do - invitation.update_attribute(:attending, true) + invitation.accept! visit invitation_route click_on 'I can no longer attend' @@ -85,7 +85,7 @@ scenario 'when they are successful and there is someone else on the waiting list' do waitinglisted = Fabricate(:workshop_invitation, workshop: invitation.workshop, role: invitation.role) WaitingList.add(waitinglisted) - invitation.update_attribute(:attending, true) + invitation.accept! visit invitation_route expect(WaitingList.next_spot(invitation.workshop, invitation.role).present?).to eq(true) @@ -93,32 +93,32 @@ expect(page).to have_content(I18n.t('messages.rejected_invitation', name: invitation.member.name)) expect(waitinglisted.reload.automated_rsvp).to eq(true) - expect(waitinglisted.reload.rsvp_time).to_not be_nil + expect(waitinglisted.reload.rsvp_time).not_to be_nil expect(WaitingList.next_spot(invitation.workshop, invitation.role).present?).to eq(false) end scenario 'when they are successful by accessing the link directly' do - invitation.update_attribute(:attending, true) + invitation.accept! visit reject_invitation_route expect(page).to have_content(I18n.t('messages.rejected_invitation', name: invitation.member.name)) - expect(page.current_path).to eq(invitation_route) + expect(page).to have_current_path(invitation_route, ignore_query: true) end scenario 'when already confirmed they are not attending' do - invitation.update_attribute(:attending, false) + invitation.decline! visit invitation_route expect(page).to have_selector(:link_or_button, 'Attend') - expect(page).to_not have_content 'I can no longer attend' + expect(page).not_to have_content 'I can no longer attend' end scenario 'when already confirmed they are not attending and reject by accessing the link directly' do - invitation.update_attribute(:attending, false) + invitation.decline! visit reject_invitation_route expect(page).to have_content(I18n.t('messages.not_attending_already')) - expect(page.current_path).to eq(invitation_route) + expect(page).to have_current_path(invitation_route, ignore_query: true) end scenario 'when already RSVPd to another event on same evening' do @@ -145,19 +145,19 @@ end scenario 'when the event is less than 3.5 hours from now' do - invitation.workshop.update_attribute(:date_and_time, Time.zone.now + 3.hours) + invitation.workshop.update_column(:date_and_time, Time.zone.now + 3.hours) visit reject_invitation_route expect(page).to have_content('You can only change your RSVP status up to 3.5 hours before the workshop') - expect(page.current_path).to eq(invitation_route) + expect(page).to have_current_path(invitation_route, ignore_query: true) end scenario 'when the event is less than 3.5 hours from now and tje reject by accessing the link directly' do - invitation.workshop.update_attribute(:date_and_time, Time.zone.now + 3.hours) + invitation.workshop.update_column(:date_and_time, Time.zone.now + 3.hours) visit reject_invitation_route expect(page).to have_content('You can only change your RSVP status up to 3.5 hours before the workshop') - expect(page.current_path).to eq(invitation_route) + expect(page).to have_current_path(invitation_route, ignore_query: true) end end