Class: ForestAdminAgent::Serializer::ForestSerializer

Inherits:
Object
  • Object
show all
Includes:
JSONAPI::Serializer
Defined in:
lib/forest_admin_agent/serializer/forest_serializer.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(object, options = nil) ⇒ ForestSerializer

Returns a new instance of ForestSerializer.



14
15
16
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 14

def initialize(object, options = nil)
  super
end

Instance Attribute Details

#attributes_mapObject

Returns the value of attribute attributes_map.



8
9
10
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 8

def attributes_map
  @attributes_map
end

#to_many_associationsObject

Returns the value of attribute to_many_associations.



10
11
12
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 10

def to_many_associations
  @to_many_associations
end

#to_one_associationsObject

Returns the value of attribute to_one_associations.



9
10
11
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 9

def to_one_associations
  @to_one_associations
end

Class Method Details

.reset_cache!Object



27
28
29
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 27

def self.reset_cache!
  @@primary_keys = {}
end

Instance Method Details

#add_attribute(name, options = {}, &block) ⇒ Object



43
44
45
46
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 43

def add_attribute(name, options = {}, &block)
  @attributes_map ||= {}
  @attributes_map[name] = format_field(name, options)
end

#add_to_many_association(name, options = {}, &block) ⇒ Object



137
138
139
140
141
142
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 137

def add_to_many_association(name, options = {}, &block)
  options[:include_links] = options.fetch(:include_links, true)
  options[:include_data] = options.fetch(:include_data, false)
  @to_many_associations ||= {}
  @to_many_associations[name] = format_field(name, options)
end

#add_to_one_association(name, options = {}, &block) ⇒ Object



89
90
91
92
93
94
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 89

def add_to_one_association(name, options = {}, &block)
  options[:include_links] = options.fetch(:include_links, true)
  options[:include_data] = options.fetch(:include_data, false)
  @to_one_associations ||= {}
  @to_one_associations[name] = format_field(name, options)
end

#attributesObject



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 55

def attributes
  forest_collection = ForestAdminAgent::Facades::Container.datasource.get_collection(
    @options[:class_name].gsub('::', '__')
  )
  fields = forest_collection.schema[:fields].select { |_field_name, field| field.type == 'Column' }
  fields.each { |field_name, _field| add_attribute(field_name) }
  return {} if attributes_map.nil?
  attributes = {}

  attributes_map.each do |attribute_name, attr_data|
    next if !should_include_attr?(attribute_name, attr_data)
    value = evaluate_attr_or_block(attribute_name, attr_data[:attr_or_block])
    # Only include the attribute if it's not nil or if the key exists in the object
    # This prevents setting all fields to null for included records
    if value.nil?
      # Check if the key actually exists in the object (for Hash objects)
      # If the key exists in the object, it means that the value is purposely set to null
      next unless @object.is_a?(Hash) && @object.key?(attribute_name.to_s)
    end
    attributes[format_name(attribute_name)] = value
  end
  attributes
end

#base_urlObject



18
19
20
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 18

def base_url
  '/forest'
end

#evaluate_attr_or_block(attribute_name, attr_or_block) ⇒ Object



79
80
81
82
83
84
85
86
87
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 79

def evaluate_attr_or_block(attribute_name, attr_or_block)
  if attr_or_block.is_a?(Proc)
    # A custom block was given, call it to get the value.
    instance_eval(&attr_or_block)
  else
    # Default behavior, call a method by the name of the attribute.
    object[attr_or_block]
  end
end

#format_field(name, options) ⇒ Object



48
49
50
51
52
53
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 48

def format_field(name, options)
  {
    attr_or_block: block_given? ? block : name,
    options: options,
  }
end

#format_name(attribute_name) ⇒ Object



39
40
41
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 39

def format_name(attribute_name)
  attribute_name.to_s
end

#has_many_relationshipsObject



127
128
129
130
131
132
133
134
135
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 127

def has_many_relationships
  return {} if @to_many_associations.nil?
  data = {}
  @to_many_associations.each do |attribute_name, attr_data|
    next if !should_include_attr?(attribute_name, attr_data)
    data[attribute_name.to_sym] = attr_data
  end
  data
end

#has_one_relationship(attribute_name, attr_data) ⇒ Object

A PolymorphicManyToOne can't be SQL-joined, so list projections return a phantom object with no primary key. Rebuild the id from the foreign key on the owner record.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 98

def has_one_relationship(attribute_name, attr_data)
  object = super
  return object if object.nil? # relation not projected (e.g. update/store responses)

  field = ForestAdminAgent::Facades::Container.datasource
                                              .get_collection(@options[:class_name].gsub('::', '__'))
                                              .schema[:fields][attribute_name.to_s]
  return object unless field&.type == 'PolymorphicManyToOne'

  foreign_key = @object[field.foreign_key]
  foreign_type = @object[field.foreign_key_type_field]
  return nil if foreign_key.nil? || foreign_type.nil?

  # foreign_key_targets is keyed by formatted collection names (Admin::User => Admin__User),
  # but foreign_type is the raw AR type column value, so format it before the lookup.
  primary_key = (field.foreign_key_targets || {})[foreign_type.gsub('::', '__')] || 'id'
  (object.is_a?(Hash) ? object : {}).merge(primary_key => foreign_key)
end

#has_one_relationshipsObject



117
118
119
120
121
122
123
124
125
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 117

def has_one_relationships
  return {} if @to_one_associations.nil?
  data = {}
  @to_one_associations.each do |attribute_name, attr_data|
    next if !should_include_attr?(attribute_name, attr_data)
    data[attribute_name.to_sym] = attr_data
  end
  data
end

#idObject



31
32
33
34
35
36
37
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 31

def id
  @@primary_keys ||= {}
  primary_keys = @@primary_keys[type] ||= ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(
    ForestAdminAgent::Facades::Container.datasource.get_collection(type)
  )
  primary_keys.map { |key| @object[key] }.join('|')
end


231
232
233
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 231

def relationship_related_link(attribute_name)
  "#{self_link}/#{format_name(attribute_name)}"
end


227
228
229
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 227

def relationship_self_link(attribute_name)
  "#{self_link}/relationships/#{format_name(attribute_name)}"
end

#relationshipsObject



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 144

def relationships
  datasource = ForestAdminAgent::Facades::Container.datasource
  forest_collection = datasource.get_collection(@options[:class_name].gsub('::', '__'))
  relations_to_many = forest_collection.schema[:fields].select do |_field_name, field|
    %w[OneToMany ManyToMany PolymorphicOneToMany].include?(field.type)
  end
  relations_to_one = forest_collection.schema[:fields].select do |_field_name, field|
    %w[OneToOne ManyToOne PolymorphicManyToOne PolymorphicOneToOne].include?(field.type)
  end

  relations_to_one.each { |field_name, _field| add_to_one_association(field_name) }

  data = {}
  has_one_relationships.each do |attribute_name, attr_data|
    formatted_attribute_name = format_name(attribute_name)
    data[formatted_attribute_name] = {}

    if attr_data[:options][:include_links]
      links_self = relationship_self_link(attribute_name)
      links_related = relationship_related_link(attribute_name)
      data[formatted_attribute_name]['links'] = {} if links_self || links_related
      data[formatted_attribute_name]['links']['related'] = {} if links_self
      data[formatted_attribute_name]['links']['related']['href'] = links_self if links_self
    end

    object = has_one_relationship(attribute_name, attr_data)
    # Only include 'data' key if the relationship object exists
    # Omit 'data' key entirely when null instead of setting it to null
    unless object.nil? || (object.respond_to?(:empty?) && object.empty?)
      relation = datasource.get_collection(@options[:class_name].gsub('::', '__'))
                           .schema[:fields][attribute_name.to_s]
      options = @options.clone
      if relation.type == 'PolymorphicManyToOne'
        options[:class_name] = @object[relation.foreign_key_type_field]
        related_object_serializer = ForestSerializer.new(object, options)
        data[formatted_attribute_name]['data'] = {
          'type' => related_object_serializer.type.to_s,
          'id' => related_object_serializer.id.to_s,
        }
      else
        options[:class_name] = datasource.get_collection(relation.foreign_collection).name
        related_object_serializer = ForestSerializer.new(object, options)
        data[formatted_attribute_name]['data'] = {
          'type' => related_object_serializer.type.to_s,
          'id' => related_object_serializer.id.to_s,
        }
      end
    end
  end

  relations_to_many.each { |field_name, _field| add_to_many_association(field_name) }

  has_many_relationships.each do |attribute_name, attr_data|
    formatted_attribute_name = format_name(attribute_name)
    data[formatted_attribute_name] = {}

    if attr_data[:options][:include_links]
      links_self = relationship_self_link(attribute_name)
      links_related = relationship_related_link(attribute_name)
      data[formatted_attribute_name]['links'] = {} if links_self || links_related
      data[formatted_attribute_name]['links']['related'] = {} if links_self
      data[formatted_attribute_name]['links']['related']['href'] = links_self if links_self
    end

    if @_include_linkages.include?(formatted_attribute_name) || attr_data[:options][:include_data]
      data[formatted_attribute_name]['data'] = []
      objects = has_many_relationship(attribute_name, attr_data) || []
      relation = datasource.get_collection(@options[:class_name].gsub('::', '__')).schema[:fields][attribute_name.to_s]
      options = @options.clone
      options[:class_name] = datasource.get_collection(relation.foreign_collection).name
      objects.each do |obj|
        related_object_serializer = JSONAPI::Serializer.find_serializer(obj, @options)
        data[formatted_attribute_name]['data'] << {
          'type' => related_object_serializer.type.to_s,
          'id' => related_object_serializer.id.to_s,
        }
      end
    end
  end

  data
end

#typeObject



22
23
24
25
# File 'lib/forest_admin_agent/serializer/forest_serializer.rb', line 22

def type
  class_name = @options[:class_name]
  @@class_names[class_name] ||= class_name.gsub('::', '__')
end