diff --git a/app/models/event.rb b/app/models/event.rb index 94d15af81..76cebb2f7 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -164,6 +164,8 @@ def add_default_results_template def conform_changed_course return unless persisted? && course_id_changed? + # Let the belongs_to presence validation reject a blank course_id + return if course.nil? response = Interactors::ChangeEventCourse.perform!(event: self, new_course: course) response.errors.each { |error| errors.add(:base, error[:title]) } diff --git a/app/presenters/event_setup_presenter.rb b/app/presenters/event_setup_presenter.rb index 4d30de627..15439b458 100644 --- a/app/presenters/event_setup_presenter.rb +++ b/app/presenters/event_setup_presenter.rb @@ -40,11 +40,19 @@ def finish_split end def courses_for_select - available_courses = organization.courses.includes(:splits).order(:name).map do |course| + available_courses = organization.courses.includes(:splits).order(:name).to_a + # An event's course can belong to another organization; without it in the + # options, the selector falls back to "Create a new course" and the form + # submits a blank course_id + if event.course.present? && available_courses.exclude?(event.course) + available_courses = [event.course] + available_courses + end + + course_options = available_courses.map do |course| [course.name, course.id, distance_component(course)] end - [["Create a new course", nil, nil]] + available_courses + [["Create a new course", nil, nil]] + course_options end def distance_component(course) diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index bc263e271..89f583da3 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -126,6 +126,17 @@ end end + describe "clearing the course on a persisted event" do + it "is invalid rather than raising from the course-change conforming" do + event = events(:hardrock_2016) + event.course_id = nil + + expect { event.valid? }.not_to raise_error + expect(event).not_to be_valid + expect(event.errors[:course]).to include("must exist") + end + end + describe "methods that produce lap_splits and time_points" do let(:event) { build_stubbed(:event, laps_required: laps_required) } let(:laps_required) { 2 } diff --git a/spec/presenters/event_setup_presenter_spec.rb b/spec/presenters/event_setup_presenter_spec.rb new file mode 100644 index 000000000..9b44a2091 --- /dev/null +++ b/spec/presenters/event_setup_presenter_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe EventSetupPresenter do + subject { described_class.new(event, view_context) } + + let(:event) { events(:hardrock_2016) } + let(:view_context) do + double("view_context", # rubocop:disable RSpec/VerifiedDoubles + params: {}, + current_user: users(:admin_user)) + end + + describe "#courses_for_select" do + let(:option_ids) { subject.courses_for_select.map(&:second) } + + context "when the event's course belongs to the event group's organization" do + it "lists the organization's courses with a create-new option" do + expect(option_ids).to include(event.course_id) + expect(option_ids.first).to be_nil + end + end + + context "when the event's course belongs to another organization" do + let(:foreign_course) { courses(:d30_12m_course) } + + before { event.update_column(:course_id, foreign_course.id) } + + it "includes the event's course so the selector does not blank the course_id" do + expect(option_ids).to include(foreign_course.id) + end + end + end +end