Change Opportunity Question to Pre Screen Question
September 7th, 2026


Overview

This renames the OpportunityQuestion concept to PreScreenQuestion and makes it polymorphic (questionable_id / questionable_type) so pre-screen questions can be attached to any entity, not just Opportunity. It also replaces the self-referential parent_id/sub_questions hierarchy with a polymorphic options association, and adds :date as a documented question_type option.

Backing migration: db/migrate/20260904134529_migrate_opportunity_questions_to_pre_screen_questions.rb — renames the table to pre_screen_questions, replaces opportunity_id + parent_id with a polymorphic questionable reference (non-null), and backfills existing rows (parent_id rows become PreScreenQuestion options; opportunity_id rows become Opportunity questions).


Object Types

Removed: Types::Objects::OpportunityQuestion

The entire object type was deleted and replaced by Types::Objects::PreScreenQuestion.

Removed field

Notes

parent

Replaced by the polymorphic questionable association (not exposed).

sub_questions (connection)

Renamed/replaced by options (plain list, no longer a connection).

Added: Types::Objects::PreScreenQuestion

class Types::Objects::PreScreenQuestion < Types::BaseObject
field :id, ID, null: true
field :is_mandatory, Boolean, null: true
field :position, Integer, null: true
field :question_text, String, null: true
field :question_type, String, null: true
field :options, [Types::Objects::PreScreenQuestion], null: true
end

Why it was created: to generalize screening questions so any record (e.g. Opportunity, or another PreScreenQuestion for options/sub-questions) can own them via questionable.

Types::Objects::Opportunity — field changes

Change

Old

New

Renamed

has_opportunity_questions: Boolean

has_pre_screen_questions: Boolean — checks object.pre_screen_questions.present?

Renamed + retyped

opportunity_questions: OpportunityQuestion.connection_type (connection)

pre_screen_questions: [PreScreenQuestion] (plain list, still uses AssociationLoader)


Input Types

Inputs::OpportunityQuestion → renamed to Inputs::PreScreenQuestion

Change

Detail

Removed argument

parent_id: ID — hierarchy now handled via questionable_*

Added argument

questionable_id: ID (required) — ID of the owning record

Added argument

questionable_type: String (required) — class name of the owning record (e.g. "Opportunity", "PreScreenQuestion")

Updated argument

question_type description now includes :date: Options - [:text, :paragraph, :dichotomous, :attachment, :checkbox, :multiple_choice, :date]

Unchanged

question_text: String, position: Integer, is_mandatory: Boolean


Queries

Removed

Query

Reason

get_opportunity_questions(opportunity_id: ID!)

Was already deprecated (Use "opportunityQuestions" field instead); now fully removed.

opportunity_questions

removed. use pre_screen_questions inside the opportunity only



Mutations

Removed

Mutation

Replaced by

add_opportunity_question(opportunity_id: ID, opportunity_question: OpportunityQuestionInput)

add_pre_screen_question

update_opportunity_question(id: ID!, opportunity_question: OpportunityQuestionInput)

update_pre_screen_question

remove_opportunity_question(id: ID!)

remove_pre_screen_question

bulk_remove_opportunity_questions(ids: [ID])

bulk_remove_pre_screen_questions

Added

add_pre_screen_question(pre_screen_question: PreScreenQuestionInput) → PreScreenQuestion

Creates a PreScreenQuestion attached polymorphically to the record identified by questionable_id / questionable_type. The old opportunity_id argument is gone — pass questionableType: "Opportunity" with questionableId to attach to an opportunity, or questionableType: "PreScreenQuestion" to create an option/sub-question.

Why: supports attaching questions to any entity type, not only opportunities.

mutation {
addPreScreenQuestion(
preScreenQuestion: {
questionableId: "123"
questionableType: "Opportunity"
questionText: "Why do you want to apply?"
questionType: "paragraph"
position: 1
isMandatory: true
}
) {
id
questionText
questionType
position
isMandatory
}
}

update_pre_screen_question(id: ID!, pre_screen_question: PreScreenQuestionInput) → PreScreenQuestion

Finds the question by id and updates it with the provided attributes. Same behavior as before, renamed.

mutation {
updatePreScreenQuestion(
id: "456"
preScreenQuestion: {
questionableId: "123"
questionableType: "Opportunity"
questionText: "Updated question text"
questionType: "multiple_choice"
position: 2
isMandatory: false
}
) {
id
questionText
}
}

remove_pre_screen_question(id: ID!) → PreScreenQuestion

Finds the question by id and destroys it.

mutation {
removePreScreenQuestion(id: "456") {
id
}
}

bulk_remove_pre_screen_questions(ids: [ID]) → [PreScreenQuestion]

Destroys all PreScreenQuestion records matching the given ids.

mutation {
bulkRemovePreScreenQuestions(ids: ["456", "789"]) {
id
}
}

Modified (same name, behavior change)

assign_questionnaire(id: ID!, questionnaire_id: ID!) → Opportunity

Unchanged signature. Internally it now destroys/creates pre_screen_questions instead of opportunity_questions, and creates questionnaire sub-questions via pre_screen_question.options.create(...) (polymorphic) rather than sub_questions.create(...) via parent_id.

mutation {
assignQuestionnaire(id: "123", questionnaireId: "42") {
id
preScreenQuestions {
id
questionText
options { id questionText }
}
}
}

Summary of Schema Name Changes

Old name

New name

Kind

Types::Objects::OpportunityQuestion

Types::Objects::PreScreenQuestion

Object type

Inputs::OpportunityQuestion

Inputs::PreScreenQuestion

Input type

opportunity_questions (query)

— removed —

Query field

get_opportunity_questions (query, deprecated)

— removed —

Query field

has_opportunity_questions

has_pre_screen_questions

Opportunity field

opportunity_questions (connection on Opportunity)

pre_screen_questions (list)

Opportunity field

sub_questions (connection)

options (list)

Question field

parent

— removed (polymorphic questionable)

Question field

parent_id (input arg)

questionable_id + questionable_type

Input argument

add_opportunity_question

add_pre_screen_question

Mutation

update_opportunity_question

update_pre_screen_question

Mutation

remove_opportunity_question

remove_pre_screen_question

Mutation

bulk_remove_opportunity_questions

bulk_remove_pre_screen_questions

Mutation

Breaking Changes for Clients

  • All opportunityQuestion(s) / getOpportunityQuestions / hasOpportunityQuestions / addOpportunityQuestion / updateOpportunityQuestion / removeOpportunityQuestion / bulkRemoveOpportunityQuestions schema names no longer exist — clients must migrate to the preScreenQuestion(s) equivalents.
  • Opportunity.opportunityQuestions was a Relay-style connection; Opportunity.preScreenQuestions is a plain list — pagination arguments (first, after, edges/node, pageInfo) are no longer applicable.
  • PreScreenQuestion.options is a plain list, not a connection (previously subQuestions was a connection).
  • addPreScreenQuestion no longer accepts opportunityId; questionableId and questionableType are now required inside the input.
Crafted by