Skip to content
85 changes: 85 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ GraphQL/ArgumentDescription:
- 'app/graphql/types/ability_type.rb'
- 'app/graphql/types/query_type.rb'

# Offense count: 1
GraphQL/ExtractInputType:
Exclude:
- 'app/graphql/mutations/create_filler_event.rb'

# Offense count: 14
# Configuration parameters: MaxFields, Prefixes.
# Prefixes: is, has, with, avg, min, max
Expand All @@ -36,6 +41,11 @@ GraphQL/UnnecessaryArgumentCamelize:
Lint/MissingCopEnableDirective:
Exclude:
- 'app/graphql/types/mutation_type.rb'
- 'test/graphql/mutations/create_event_proposal_test.rb'
- 'test/graphql/mutations/create_event_test.rb'
- 'test/graphql/mutations/create_filler_event_test.rb'
- 'test/graphql/mutations/update_event_proposal_test.rb'
- 'test/graphql/mutations/update_event_test.rb'

# Offense count: 1
Lint/NoReturnInBeginEndBlocks:
Expand All @@ -49,7 +59,22 @@ Metrics/BlockLength:
Exclude:
- 'config/environments/production.rb'
- 'config/initializers/doorkeeper.rb'
- 'test/graphql/mutations/create_event_proposal_test.rb'
- 'test/graphql/mutations/create_event_test.rb'
- 'test/graphql/mutations/create_filler_event_test.rb'
- 'test/liquid_drops/convention_drop_test.rb'
- 'test/models/event_test.rb'
- 'test/models/registration_policy_bucket_test.rb'
- 'test/models/registration_policy_test.rb'
- 'test/presenters/signup_count_presenter_test.rb'
- 'test/services/create_signup_request_service_test.rb'
- 'test/services/create_team_member_service_test.rb'
- 'test/services/event_change_registration_policy_service_test.rb'
- 'test/services/event_signup_service_test.rb'
- 'test/services/event_vacancy_fill_service_test.rb'
- 'test/services/event_withdraw_service_test.rb'
- 'test/services/execute_ranked_choice_signup_round_service_test.rb'
- 'test/services/execute_ranked_choice_signup_service_test.rb'

# Offense count: 7
# Configuration parameters: CountComments, Max, CountAsOne.
Expand All @@ -60,8 +85,20 @@ Metrics/ClassLength:
- 'app/graphql/types/ability_type.rb'
- 'app/graphql/types/mutation_type.rb'
- 'app/graphql/types/query_type.rb'
- 'app/models/convention.rb'
- 'app/models/event.rb'
- 'app/models/registration_policy.rb'
- 'app/presenters/signup_count_presenter.rb'
- 'app/services/event_change_registration_policy_service.rb'
- 'app/services/execute_ranked_choice_signup_round_service.rb'
- 'test/models/registration_policy_bucket_test.rb'
- 'test/models/registration_policy_test.rb'
- 'test/presenters/signup_count_presenter_test.rb'
- 'test/services/create_signup_request_service_test.rb'
- 'test/services/create_team_member_service_test.rb'
- 'test/services/event_change_registration_policy_service_test.rb'
- 'test/services/event_vacancy_fill_service_test.rb'
- 'test/services/event_withdraw_service_test.rb'

# Offense count: 1
# Configuration parameters: Max.
Expand Down Expand Up @@ -104,6 +141,7 @@ Rails/HasAndBelongsToMany:

Rails/HasManyOrHasOneDependent:
Exclude:
- 'app/models/event.rb'
- 'app/models/page.rb'

Rails/HelperInstanceVariable:
Expand All @@ -114,6 +152,9 @@ Rails/SkipsModelValidations:
Exclude:
- 'app/controllers/application_controller.rb'
- 'app/models/page.rb'
- 'app/services/event_change_registration_policy_service.rb'
- 'app/services/event_freeze_bucket_assignments_service.rb'
- 'app/services/execute_ranked_choice_signup_round_service.rb'
- 'db/migrate/20251229184620_allow_null_redirect_uri_on_oauth_appliations.rb'

# Offense count: 7
Expand All @@ -123,9 +164,53 @@ Rails/SkipsModelValidations:
Style/FrozenStringLiteralComment:
Exclude:
- 'app/models/oauth_application.rb'
- 'app/services/event_freeze_bucket_assignments_service.rb'
- 'config/initializers/cors.rb'
- 'config/initializers/doorkeeper.rb'
- 'config/initializers/doorkeeper_openid_connect.rb'
- 'lib/intercode/virtual_host_constraint.rb'
- 'test/factories/event_proposals.rb'
- 'test/factories/events.rb'
- 'test/factories/oauth_applications.rb'
- 'test/liquid_drops/convention_drop_test.rb'
- 'test/models/event_proposal_test.rb'
- 'test/models/event_test.rb'
- 'test/models/registration_policy/unlimited_test.rb'
- 'test/models/registration_policy_bucket_test.rb'
- 'test/models/registration_policy_test.rb'
- 'test/policies/oauth_application_policy_test.rb'
- 'test/services/create_signup_request_service_test.rb'
- 'test/services/create_team_member_service_test.rb'
- 'test/services/event_change_registration_policy_service_test.rb'
- 'test/services/event_freeze_bucket_assignments_service_test.rb'
- 'test/services/event_signup_service_test.rb'
- 'test/services/event_vacancy_fill_service_test.rb'
- 'test/services/event_withdraw_service_test.rb'
- 'test/services/execute_ranked_choice_signup_round_service_test.rb'
- 'test/services/execute_ranked_choice_signup_service_test.rb'

Layout/LineLength:
Exclude:
- 'test/models/registration_policy_bucket_test.rb'

Lint/MissingSuper:
Exclude:
- 'app/services/accept_event_proposal_service.rb'
- 'app/services/event_change_registration_policy_service.rb'
- 'app/services/event_freeze_bucket_assignments_service.rb'
- 'app/services/execute_ranked_choice_signup_round_service.rb'

Performance/StringInclude:
Exclude:
- 'test/models/registration_policy_test.rb'

Rails/SquishedSQLHeredocs:
Exclude:
- 'app/services/execute_ranked_choice_signup_round_service.rb'

Rails/TimeZone:
Exclude:
- 'app/models/convention.rb'
- 'test/services/event_vacancy_fill_service_test.rb'
- 'test/services/execute_ranked_choice_signup_round_service_test.rb'

13 changes: 10 additions & 3 deletions app/graphql/mutations/create_event.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# frozen_string_literal: true
class Mutations::CreateEvent < Mutations::BaseMutation
field :event, Types::EventType, null: false
description "Create a new event"

argument :event, Types::EventInputType, required: true
argument :signed_image_blob_ids, [ID], required: false
field :event, Types::EventType, null: false, description: "The newly-created event"

argument :event, Types::EventInputType, required: true, description: "The event attributes"
argument :signed_image_blob_ids,
[ID],
required: false,
description: "Signed blob IDs for images to attach to this event"

authorize_create_convention_associated_model :events

Expand All @@ -12,6 +17,8 @@ def resolve(signed_image_blob_ids: nil, **args)
form_response_attrs = JSON.parse(event_attrs.delete("form_response_attrs_json"))

event = convention.events.new(event_attrs)
# Build explicitly -- a raw hash would crash here (no accepts_nested_attributes_for).
event.registration_policy = RegistrationPolicy.build_from_hash(form_response_attrs.delete("registration_policy"))
event.assign_form_response_attributes(
event.filter_form_response_attributes_for_assignment(
form_response_attrs,
Expand Down
24 changes: 21 additions & 3 deletions app/graphql/mutations/create_event_proposal.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
# frozen_string_literal: true
class Mutations::CreateEventProposal < Mutations::BaseMutation
field :event_proposal, Types::EventProposalType, null: false
description "Create a new event proposal, optionally cloning form values from an existing one"

argument :clone_event_proposal_id, ID, required: false, camelize: true
argument :event_category_id, ID, required: false, camelize: true
field :event_proposal, Types::EventProposalType, null: false, description: "The newly-created event proposal"

argument :clone_event_proposal_id,
ID,
required: false,
camelize: true,
description: "The ID of an existing event proposal to copy compatible form values from"
argument :event_category_id,
ID,
required: false,
camelize: true,
description: "The ID of the event category the new proposal belongs to"

authorize_create_convention_associated_model :event_proposals

Expand Down Expand Up @@ -35,6 +45,14 @@ def clone_attributes_from_event_proposal_id(id, event_proposal)
event_proposal.event_category.event_proposal_form
)
clone_attributes = template_proposal.read_form_response_attributes_for_form_items(compatible_items)

# Build an independent copy -- sharing the template's row risks a dependent: :destroy cascade.
if clone_attributes["registration_policy"]
clone_attributes["registration_policy"] = RegistrationPolicy.build_from_hash(
clone_attributes["registration_policy"].as_json
)
end

event_proposal.assign_form_response_attributes(clone_attributes)
end

Expand Down
15 changes: 11 additions & 4 deletions app/graphql/mutations/create_filler_event.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# frozen_string_literal: true
class Mutations::CreateFillerEvent < Mutations::BaseMutation
field :event, Types::EventType, null: false
description "Create a new filler event (a single-run event with no signup process, e.g. an ongoing activity)"

argument :event, Types::EventInputType, required: true
argument :run, Types::RunInputType, required: false
argument :signed_image_blob_ids, [ID], required: false
field :event, Types::EventType, null: false, description: "The newly-created event"

argument :event, Types::EventInputType, required: true, description: "The event attributes"
argument :run, Types::RunInputType, required: false, description: "The initial run to create for this event"
argument :signed_image_blob_ids,
[ID],
required: false,
description: "Signed blob IDs for images to attach to this event"

authorize_create_convention_associated_model :events

Expand All @@ -14,6 +19,8 @@ def resolve(signed_image_blob_ids: nil, **args)
form_response_attrs = JSON.parse(event_attrs.delete("form_response_attrs_json"))

event = convention.events.new(event_attrs)
# Build explicitly -- a raw hash would crash here (no accepts_nested_attributes_for).
event.registration_policy = RegistrationPolicy.build_from_hash(form_response_attrs.delete("registration_policy"))
event.assign_form_response_attributes(
event.filter_form_response_attributes_for_assignment(
form_response_attrs,
Expand Down
4 changes: 2 additions & 2 deletions app/graphql/mutations/freeze_bucket_assignments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ class Mutations::FreezeBucketAssignments < Mutations::BaseMutation
load_and_authorize_convention_associated_model :events, :id, :update

def resolve(**)
old_registration_policy = event.registration_policy.dup
old_registration_policy_json = event.registration_policy.as_json

EventFreezeBucketAssignmentsService.new(event:, whodunit: current_user).call!
event.reload

log_form_response_changes(
event,
{ "registration_policy" => [old_registration_policy.as_json, event.registration_policy.as_json] }
{ "registration_policy" => [old_registration_policy_json, event.registration_policy.as_json] }
)

{ event: }
Expand Down
10 changes: 4 additions & 6 deletions app/graphql/mutations/update_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,13 @@ def resolve(**args)
private

def apply_registration_policy(event, registration_policy_attributes, bucket_key_mappings)
new_registration_policy = RegistrationPolicy.new(registration_policy_attributes)
return {} if event.registration_policy == new_registration_policy

old_registration_policy = event.registration_policy
EventChangeRegistrationPolicyService.new(event, new_registration_policy, current_user, bucket_key_mappings).call!
change = event.registration_policy_change_for(registration_policy_attributes)
return {} unless change

EventChangeRegistrationPolicyService.new(event, change.new_policy, current_user, bucket_key_mappings).call!
event.reload

{ "registration_policy" => [old_registration_policy.as_json, new_registration_policy.as_json] }
{ "registration_policy" => [change.old_json, event.registration_policy.as_json] }
end

def apply_form_response_attrs(event, form_response_attrs)
Expand Down
73 changes: 53 additions & 20 deletions app/graphql/mutations/update_event_proposal.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,71 @@
# frozen_string_literal: true
class Mutations::UpdateEventProposal < Mutations::BaseMutation
field :event_proposal, Types::EventProposalType, null: false
description "Update an event proposal"

argument :event_proposal, Types::EventProposalInputType, required: true, camelize: false
argument :id, ID, required: false
field :event_proposal, Types::EventProposalType, null: false, description: "The updated event proposal"

argument :event_proposal,
Types::EventProposalInputType,
required: true,
camelize: false,
description: "The event proposal attributes to update"
argument :id, ID, required: false, description: "The ID of the event proposal to update"

load_and_authorize_convention_associated_model :event_proposals, :id, :update

# rubocop:disable Metrics/MethodLength
def resolve(**args)
event_proposal_attrs = args[:event_proposal].to_h.stringify_keys
form_response_attrs = JSON.parse(event_proposal_attrs.delete("form_response_attrs_json"))
registration_policy_attributes = form_response_attrs.delete("registration_policy")

changes = apply_registration_policy(event_proposal, registration_policy_attributes)
changes.update(apply_form_response_attrs(event_proposal, form_response_attrs))
event_proposal.assign_attributes(event_proposal_attrs)
event_proposal.save!

log_form_response_changes(event_proposal, changes) if event_proposal.status != "draft"

{ event_proposal: }
end

private

def apply_registration_policy(event_proposal, registration_policy_attributes)
change = event_proposal.registration_policy_change_for(registration_policy_attributes)
return {} unless change

ActiveRecord::Base.transaction do
if event_proposal.registration_policy
event_proposal.registration_policy.update_from!(change.new_policy)
else
event_proposal.registration_policy = change.new_policy
event_proposal.save!
end
end

{ "registration_policy" => [change.old_json, event_proposal.registration_policy.as_json] }
end

def apply_form_response_attrs(event_proposal, form_response_attrs)
event_proposal.assign_form_response_attributes(
event_proposal.filter_form_response_attributes_for_assignment(
JSON.parse(event_proposal_attrs.delete("form_response_attrs_json")),
form_response_attrs,
event_proposal.event_category.event_proposal_form.form_items,
context[:pundit_user]
)
)
event_proposal.assign_attributes(event_proposal_attrs)
event_proposal.save!
event_proposal.form_response_attribute_changes
end

if event_proposal.status != "draft"
event_proposal.form_response_attribute_changes.each do |(key, (previous_value, new_value))|
FormResponseChange.create!(
response: event_proposal,
user_con_profile:,
field_identifier: key,
previous_value:,
new_value:
)
end
def log_form_response_changes(event_proposal, changes)
changes.each do |(key, (previous_value, new_value))|
FormResponseChange.create!(
response: event_proposal,
user_con_profile:,
field_identifier: key,
previous_value:,
new_value:
)
end

{ event_proposal: }
end
# rubocop:enable Metrics/MethodLength
end
29 changes: 29 additions & 0 deletions app/models/concerns/has_registration_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true
module HasRegistrationPolicy
extend ActiveSupport::Concern

# A proposed registration policy change, returned by #registration_policy_change_for.
class Change
# @!attribute [r] new_policy
# @return [RegistrationPolicy] a detached, unsaved policy for the caller to apply
# @!attribute [r] old_json
# @return [Hash, nil] the current policy's JSON, captured before applying the change
attr_reader :new_policy, :old_json
Comment thread
nbudin marked this conversation as resolved.

def initialize(new_policy:, old_json:)
@new_policy = new_policy
@old_json = old_json
end
end

# @param hash [Hash, nil] a raw registration policy hash (e.g. from form response attrs)
# @return [Change, nil] nil if hash is blank or describes a policy equivalent to the current one
def registration_policy_change_for(hash)
return nil unless hash

new_policy = RegistrationPolicy.build_from_hash(hash)
return nil if registration_policy&.equivalent_to?(new_policy)

Change.new(new_policy:, old_json: registration_policy&.as_json)
end
end
Loading
Loading