ruby 46 lines · 9 steps

Nested survey forms in Rails

Two models wire up nested attributes, cascading deletes, and custom validations so a survey and its questions save as one unit.

Explained by highlit
1class Survey < ApplicationRecord
2 has_many :questions, dependent: :destroy
3 has_many :respondents, dependent: :nullify
4 
5 accepts_nested_attributes_for :questions,
6 allow_destroy: true,
7 reject_if: ->(attrs) { attrs["prompt"].blank? && attrs["id"].blank? }
8 
9 validates :title, presence: true, length: { maximum: 200 }
10 validate :must_have_at_least_one_question
11 
12 private
13 
14 def must_have_at_least_one_question
15 return if questions.reject(&:marked_for_destruction?).any?
16 
17 errors.add(:base, "Add at least one question before publishing")
18 end
19end
20 
21class Question < ApplicationRecord
22 belongs_to :survey
23 has_many :choices, dependent: :destroy
24 
25 accepts_nested_attributes_for :choices,
26 allow_destroy: true,
27 reject_if: :blank_choice?
28 
29 enum :kind, { single_choice: 0, multiple_choice: 1, free_text: 2 }
30 
31 validates :prompt, presence: true
32 validate :choices_required_for_choice_kinds
33 
34 private
35 
36 def blank_choice?(attributes)
37 attributes["label"].blank?
38 end
39 
40 def choices_required_for_choice_kinds
41 return if free_text?
42 return if choices.reject(&:marked_for_destruction?).size >= 2
43 
44 errors.add(:choices, "must include at least two options")
45 end
46end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1accepts_nested_attributes_for with reject_if lets one form build parents and children together while silently dropping empty entries.
  2. 2Counting records after filtering marked_for_destruction? gives accurate validations even mid-form when rows are flagged for deletion.
  3. 3dependent: matters for intent — :destroy removes children while :nullify just unhooks them without deleting.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Nested survey forms in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code