---
# System prepended metadata

title: Composite Pattern

---

# Composite Pattern

https://refactoring.guru/design-patterns/composite

## Applicability
1. Use the Composite pattern when you have to implement a **tree-like** object structure.
2. Use the pattern when you want the client code to **treat both simple and complex elements uniformly**.


## Examples
![](https://i.imgur.com/NXK3E8K.png)
A box could even add some extra cost to the final price, such as packaging cost.

## Structure
![](https://i.imgur.com/ZeeJ3eu.png)

## Apply to OA Form Field rendering

![](https://i.imgur.com/yHkJD6N.png)

![](https://i.imgur.com/PpgHeNH.png)

```ruby
class FormComponent
  attr_accessor :parent

  def add(child)
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

  def render
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

class GroupComponent < FormComponent
  def initialize(title:)
    @title = title
    @children = []
  end

  def add(child_component)
    @children += Array.wrap(child_component)
  end

  def render
    puts ""
    puts "----- Render Group Component --------"
    @children.each(&:render)
  end

  def render_json
    {
      group_title: @title,
      children: @children.map(&:render_json)
    }
  end
end

class FieldComponent < FormComponent
  attr_reader :record, :school_field

  def initialize(record, school_field)
    @record = record
    @school_field = school_field
  end

  def render
    case school_field
    when ColumnNavigation
      puts ""
      puts "========= #{school_field.title} ========="
    else
      puts "#{school_field.label}: #{record.read_field(school_field)}"
    end
  end

  def render_json
    case school_field
    when ColumnNavigation
      {
        name: school_field.name,
        label: school_field.label,
        value: school_field.title,
      }
    else
      {
        name: school_field.name,
        label: school_field.label,
        value: record.read_field(school_field),
      }
    end
  end
end

# return [Array<FormComponent>]
def decorate_school_field(record, school_field)
  if school_field.class < FieldGroup
    records = record.read_field(school_field)
    components = records.map do |child_record|
      component = GroupComponent.new(title: school_field.label)
      school_field.children.each do |child_field|
        child_component = decorate_school_field(child_record, child_field)
        component.add(child_component)
      end

      component
    end

    return components
  else
    return FieldComponent.new(record, school_field)
  end
end

student = Student.find(726)
application_form = student.application_form
form_component = GroupComponent.new(title: application_form.title)

application_form.fields.map do |field|
  components = decorate_school_field(student, field)
  form_component.add(components)
end

# form_component.render

File.open(Rails.root.join("result.json"), "w") do |f|
  f.write form_component.render_json.to_json
end
```

$ rails runner xx.rb