Skip to main content

Overview

The FormFill model represents a filled-out form instance based on a FormTemplate. It manages form data, photo attachments, signatures, and PDF generation for inspections.

Database Schema

Attributes

id
integer
required
Primary key
name
string
Name of the form fill instance
form_template_id
integer
required
Foreign key to the associated form template
inspection_id
integer
Foreign key to the associated inspection (optional)
form_structure
string
JSON string defining the form field structure (copied from template)
data
jsonb
default:"{}"
JSONB column storing all form field values and metadataIndexed: GIN index for efficient querying
pdf_generation_status
enum
default:"ready"
Status of PDF generationValues:
  • ready - Ready to generate PDF
  • generating - PDF generation in progress
  • completed - PDF generated successfully
  • failed - PDF generation error
pdf_created
boolean
default:"false"
Whether an individual PDF has been created for this form fillIndexed: For efficient querying
last_generated_at
datetime
Timestamp of last PDF generation
created_at
datetime
Timestamp when the form fill was created
updated_at
datetime
Timestamp when the form fill was last updated

Associations

Belongs To

form_template
belongs_to
required
The template this form fill is based on
form_fill = FormFill.find(1)
form_fill.form_template # => #<FormTemplate...>
inspection
belongs_to
The inspection this form fill belongs to (optional)
form_fill = FormFill.find(1)
form_fill.inspection # => #<Inspection...> or nil

Active Storage Attachments

filled_pdf
has_one_attached
The generated PDF file for this form fill
form_fill = FormFill.find(1)
form_fill.filled_pdf.attached? # => true/false
photos
has_many_attached
Multiple photos attached to various form fields
form_fill = FormFill.find(1)
form_fill.photos.count # => 5

Enums

pdf_generation_status

enum :pdf_generation_status, {
  ready: "ready",
  generating: "generating",
  completed: "completed",
  failed: "failed"
}
Usage:
form_fill.ready? # => true
form_fill.generating!
form_fill.generating? # => true

Data Column Methods

get_field_value

get_field_value(field_name)
method
Retrieves the value for a specific field from the data column.Parameters:
  • field_name (String) - Name of the field
Returns: Field value or nilExample:
form_fill.get_field_value("inspector_name")
# => "John Doe"

set_field_value

set_field_value(field_name, value)
method
Stores a value for a specific field in the data column.Parameters:
  • field_name (String) - Name of the field
  • value (Any) - Value to store
Returns: true if successful, false otherwiseExample:
form_fill.set_field_value("inspector_name", "John Doe")
# => true

bulk_update_data

bulk_update_data(field_hash)
method
Updates multiple fields efficiently in a single operation.Parameters:
  • field_hash (Hash) - Hash of field names and values
Returns: true if successful, false otherwiseExample:
form_fill.bulk_update_data({
  inspector_name: "John Doe",
  inspection_date: "2024-01-15",
  status: "Pass"
})
# => true

Photo Management Methods

attach_photo_for_field

attach_photo_for_field(field_name, photo_file)
method
Attaches a photo to a specific field. Supports multiple photos per field.Parameters:
  • field_name (String) - Name of the field
  • photo_file (File/IO) - Photo file to attach
Returns: Hash with :success and :attachment_id or :errorExample:
result = form_fill.attach_photo_for_field(
  "equipment_photo",
  File.open("photo.jpg")
)
# => { success: true, attachment_id: "inspection_1_equipment_photo_a1b2c3d4" }

get_photo_for_field

get_photo_for_field(field_name)
method
Retrieves the last photo attached to a field.Parameters:
  • field_name (String) - Name of the field
Returns: ActiveStorage attachment or nilExample:
photo = form_fill.get_photo_for_field("equipment_photo")
photo.url # => "/rails/active_storage/blobs/.../photo.jpg"

get_photos_for_field

get_photos_for_field(field_name)
method
Retrieves all photos attached to a field.Parameters:
  • field_name (String) - Name of the field
Returns: Array of ActiveStorage attachmentsExample:
photos = form_fill.get_photos_for_field("equipment_photo")
photos.each { |photo| puts photo.filename }

get_photos_by_field

get_photos_by_field
method
Retrieves all photos organized by field name.Returns: Hash with field names as keys and arrays of photo data as valuesExample:
photos_hash = form_fill.get_photos_by_field
# => {
#   "equipment_photo" => [
#     { photo: #<ActiveStorage::Attachment>, attachment_id: "..." }
#   ],
#   "valve_photo" => [...]
# }

remove_photo_for_field

remove_photo_for_field(field_name, photo_id = nil)
method
Removes photo(s) from a field.Parameters:
  • field_name (String) - Name of the field
  • photo_id (String, optional) - Specific photo ID to remove; if nil, removes all photos
Returns: Hash with :success and :message or :errorExample:
# Remove all photos from field
form_fill.remove_photo_for_field("equipment_photo")

# Remove specific photo
form_fill.remove_photo_for_field("equipment_photo", "inspection_1_equipment_photo_a1b2c3d4")

get_photos_with_context

get_photos_with_context
method
Retrieves all photos with their field context (type, section, label).Returns: Array of hashes with photo and field metadataExample:
photos = form_fill.get_photos_with_context
# => [
#   {
#     photo: #<ActiveStorage::Attachment>,
#     field_type: "Photo",
#     section_name: "Equipment Section",
#     label_name: "Main Valve"
#   }
# ]

Signature Management Methods

attach_signature_for_field

attach_signature_for_field(field_name, image_file)
method
Attaches a signature image to a signature field. Automatically handles technician vs. client signatures.Parameters:
  • field_name (String) - Name of the signature field
  • image_file (File/IO) - Signature image (PNG or JPEG)
Returns: Hash with :success and :attachment_id or :errorSignature Types:
  • Signature_Field - Technician signature
  • Signature_Annex - Client signature
Example:
result = form_fill.attach_signature_for_field(
  "technician_signature",
  File.open("signature.png")
)
# => { success: true, attachment_id: "inspection_1_signature_technician_x7y8z9" }

get_signature_for_field

get_signature_for_field(field_name)
method
Retrieves the signature for a specific field. Automatically filters by signature type.Parameters:
  • field_name (String) - Name of the signature field
Returns: ActiveStorage attachment or nilExample:
signature = form_fill.get_signature_for_field("technician_signature")
signature.url # => "/rails/active_storage/blobs/.../signature.png"

remove_all_signatures_for_field

remove_all_signatures_for_field(field_name)
method
Removes all signatures from a signature field.Parameters:
  • field_name (String) - Name of the signature field
Example:
form_fill.remove_all_signatures_for_field("technician_signature")

PDF Management Methods

pdf_url

pdf_url
method
Returns the URL path for the generated PDF.Returns: String or nilExample:
form_fill.pdf_url
# => "/rails/active_storage/blobs/.../filled_form.pdf"

mark_pdf_created!

mark_pdf_created!
method
Marks the PDF as successfully created and updates timestamps.Example:
form_fill.mark_pdf_created!
form_fill.pdf_created? # => true
form_fill.completed? # => true

has_individual_pdf?

has_individual_pdf?
method
Checks if this form fill has an individual PDF created.Returns: BooleanExample:
form_fill.has_individual_pdf? # => true

main_form_fill?

main_form_fill?
method
Checks if this is the main form fill for the inspection.Returns: BooleanExample:
form_fill.main_form_fill? # => true

should_include_in_main_merge?

should_include_in_main_merge?
method
Determines if this form fill should be included in the main PDF merge.Returns: BooleanLogic: Returns true if PDF is created and this is not the main form fillExample:
form_fill.should_include_in_main_merge? # => false

Data Analysis Methods

calculate_form_counts

calculate_form_counts
method
Calculates counts of Pass, Fail, and N/A values in the form data.Returns: Hash with :pass, :fail, and :na countsExample:
counts = form_fill.calculate_form_counts
# => { pass: 15, fail: 2, na: 3 }

get_sprinklers_data

get_sprinklers_data
method
Extracts sprinkler-specific data from the form.Returns: Hash with sprinkler informationExample:
sprinklers = form_fill.get_sprinklers_data
# => {
#   number: 150,
#   date: "2023-01-15",
#   brand: "Viking",
#   notes: "All functional"
# }

get_deficiencies_for_processing

get_deficiencies_for_processing
method
Retrieves all deficiency data for PDF generation.Returns: Array of deficiency hashesExample:
deficiencies = form_fill.get_deficiencies_for_processing
# => [
#   {
#     "name" => "valve_deficiency",
#     "value" => "Leaking",
#     "comment_value" => "Requires replacement",
#     "Item" => "Main Valve",
#     "Riser" => "A1",
#     "C" => "Critical",
#     "D" => "Immediate"
#   }
# ]

Legacy Data Migration

has_legacy_data?

has_legacy_data?
method
Checks if this form fill has legacy data stored in form_structure.Returns: BooleanExample:
form_fill.has_legacy_data? # => false

migrate_legacy_data!

migrate_legacy_data!
method
Migrates legacy data from form_structure to the new data column.Returns: Boolean indicating successExample:
form_fill.migrate_legacy_data!
# Moves all field values from form_structure to data column

merge_structure_with_data

merge_structure_with_data
method
Merges form structure with data values for PDF generation.Returns: Array of field hashes with valuesExample:
merged = form_fill.merge_structure_with_data
# => [
#   { "name" => "inspector_name", "type" => "Text", "value" => "John Doe" },
#   ...
# ]

Utility Methods

cleanup_duplicate_photos!

cleanup_duplicate_photos!
method
Removes duplicate photos, keeping only the most recent for each field.Returns: Hash with cleanup statisticsExample:
result = form_fill.cleanup_duplicate_photos!
# => { cleaned: 3, message: "Cleaned 3 duplicate photos" }

sync_photos_with_structure!

sync_photos_with_structure!
method
Synchronizes existing photos with the form structure.Returns: Boolean indicating successExample:
form_fill.sync_photos_with_structure!
# => true

Usage Examples

Creating and Filling a Form

# Create a form fill from a template
form_fill = FormFill.create!(
  name: "Monthly Inspection - January 2024",
  form_template: template,
  inspection: inspection
)

# Fill in form data
form_fill.bulk_update_data({
  inspector_name: "John Doe",
  inspection_date: "2024-01-15",
  valve_status: "Pass",
  pump_status: "Pass"
})

# Attach photos
form_fill.attach_photo_for_field("valve_photo", File.open("valve.jpg"))
form_fill.attach_photo_for_field("pump_photo", File.open("pump.jpg"))

# Attach signatures
form_fill.attach_signature_for_field("technician_signature", File.open("sig.png"))

Generating a PDF

form_fill = FormFill.find(1)

# Update status to generating
form_fill.generating!

# Generate PDF (in your service)
# ...

# Mark as completed
form_fill.mark_pdf_created!

# Access the PDF
url = form_fill.pdf_url

Querying Form Data

form_fill = FormFill.find(1)

# Get specific field value
inspector = form_fill.get_field_value("inspector_name")

# Get all photos for a field
photos = form_fill.get_photos_for_field("equipment_photo")

# Get form statistics
counts = form_fill.calculate_form_counts
puts "Passed: #{counts[:pass]}, Failed: #{counts[:fail]}, N/A: #{counts[:na]}"

# Get all deficiencies
deficiencies = form_fill.get_deficiencies_for_processing
deficiencies.each do |def|
  puts "#{def['Item']}: #{def['value']} - #{def['comment_value']}"
end

Build docs developers (and LLMs) love