# 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