Class: JSONAPI::Deserializer

Inherits:
Object
  • Object
show all
Includes:
ActiveStorageSupport
Defined in:
lib/json_api/serialization/deserializer.rb

Instance Method Summary collapse

Methods included from ActiveStorageSupport

#append_only_enabled?, #attach_active_storage_files, #extract_active_storage_params_from_hash, #filter_active_storage_from_includes, #filter_polymorphic_from_includes, #find_blob_by_signed_id, #process_active_storage_attachment, #purge_on_nil_enabled?, #serialize_active_storage_relationship, #serialize_blob_identifier

Constructor Details

#initialize(params, model_class:, action: :create) ⇒ Deserializer

Returns a new instance of Deserializer.



9
10
11
12
13
14
# File 'lib/json_api/serialization/deserializer.rb', line 9

def initialize(params, model_class:, action: :create)
  @params = ParamHelpers.deep_symbolize_params(params)
  @model_class = model_class
  @definition = ResourceLoader.find_for_model(model_class)
  @action = action.to_sym
end

Instance Method Details

#apply_virtual_attribute_transformers(attrs) ⇒ Object



84
85
86
87
88
89
# File 'lib/json_api/serialization/deserializer.rb', line 84

def apply_virtual_attribute_transformers(attrs)
  transformed_params, attributes_with_setters = invoke_setter_methods(attrs)
  attributes_with_setters.each { |attr_sym| attrs.delete(attr_sym) }
  merge_transformed_params(attrs, transformed_params)
  attrs
end

#attributesObject



16
17
18
19
20
21
# File 'lib/json_api/serialization/deserializer.rb', line 16

def attributes
  attrs = extract_attributes_from_params
  attrs = attrs.transform_keys(&:to_sym) if attrs.respond_to?(:transform_keys)
  permitted_attrs = permitted_attributes_for_action
  attrs.slice(*permitted_attrs)
end

#call_setters(attrs, definition_instance) ⇒ Object



124
125
126
127
128
129
130
131
132
133
# File 'lib/json_api/serialization/deserializer.rb', line 124

def call_setters(attrs, definition_instance)
  attributes_with_setters = []
  attrs.each do |attr_sym, attr_value|
    next unless has_setter?(definition_instance, attr_sym)

    definition_instance.public_send(:"#{attr_sym}=", attr_value)
    attributes_with_setters << attr_sym
  end
  attributes_with_setters
end

#create_definition_instance_for_settersObject



120
121
122
# File 'lib/json_api/serialization/deserializer.rb', line 120

def create_definition_instance_for_setters
  @definition.new(nil, {})
end

#empty_array?(data) ⇒ Boolean

Returns:

  • (Boolean)


161
162
163
# File 'lib/json_api/serialization/deserializer.rb', line 161

def empty_array?(data)
  data.is_a?(Array) && data.empty?
end

#extract_attributes_from_paramsObject



23
24
25
# File 'lib/json_api/serialization/deserializer.rb', line 23

def extract_attributes_from_params
  @params.dig(:data, :attributes) || @params[:attributes] || {}
end

#extract_data_from_value(value_hash) ⇒ Object



157
158
159
# File 'lib/json_api/serialization/deserializer.rb', line 157

def extract_data_from_value(value_hash)
  value_hash[:data]
end

#extract_id_from_identifier(identifier) ⇒ Object



69
70
71
# File 'lib/json_api/serialization/deserializer.rb', line 69

def extract_id_from_identifier(identifier)
  RelationshipHelpers.extract_id_from_identifier(identifier)
end

#extract_ids_from_data(data) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/json_api/serialization/deserializer.rb', line 61

def extract_ids_from_data(data)
  if data.is_a?(Array)
    data.map { |r| extract_id_from_identifier(r) }
  else
    [extract_id_from_identifier(data)]
  end
end

#extract_relationship_data(relationship) ⇒ Object



57
58
59
# File 'lib/json_api/serialization/deserializer.rb', line 57

def extract_relationship_data(relationship)
  relationship[:data]
end

#find_relationship(relationship_name) ⇒ Object



53
54
55
# File 'lib/json_api/serialization/deserializer.rb', line 53

def find_relationship(relationship_name)
  relationships[relationship_name.to_sym]
end

#handle_empty_array_relationship(attrs, param_name, association_name) ⇒ Object



175
176
177
178
179
180
181
182
# File 'lib/json_api/serialization/deserializer.rb', line 175

def handle_empty_array_relationship(attrs, param_name, association_name)
  # Check if this is an ActiveStorage attachment
  if active_storage_attachment?(association_name)
    attrs[association_name.to_s] = []
  else
    attrs["#{param_name.singularize}_ids"] = []
  end
end

#handle_null_relationship(attrs, param_name, association_name) ⇒ Object



165
166
167
168
169
170
171
172
173
# File 'lib/json_api/serialization/deserializer.rb', line 165

def handle_null_relationship(attrs, param_name, association_name)
  # Handle ActiveStorage attachments specially
  if active_storage_attachment?(association_name)
    attrs[association_name.to_s] = nil
  else
    attrs["#{param_name}_id"] = nil
    attrs["#{param_name}_type"] = nil if polymorphic_association?(association_name)
  end
end

#has_setter?(definition_instance, attr_sym) ⇒ Boolean

Returns:

  • (Boolean)


135
136
137
# File 'lib/json_api/serialization/deserializer.rb', line 135

def has_setter?(definition_instance, attr_sym)
  definition_instance.respond_to?(:"#{attr_sym}=", false)
end

#invoke_setter_methods(attrs) ⇒ Object



112
113
114
115
116
117
118
# File 'lib/json_api/serialization/deserializer.rb', line 112

def invoke_setter_methods(attrs)
  definition_instance = create_definition_instance_for_setters
  return [{}, []] unless definition_instance.respond_to?(:transformed_params, true)

  attributes_with_setters = call_setters(attrs, definition_instance)
  [definition_instance.transformed_params, attributes_with_setters]
end

#merge_transformed_params(attrs, transformed_params) ⇒ Object



91
92
93
94
95
96
97
# File 'lib/json_api/serialization/deserializer.rb', line 91

def merge_transformed_params(attrs, transformed_params)
  return attrs unless transformed_params.is_a?(Hash) && transformed_params.any?

  transformed_params_symbolized = transformed_params.transform_keys(&:to_sym)
  attrs.merge!(transformed_params_symbolized)
  attrs
end

#normalize_relationship_value(value) ⇒ Object



153
154
155
# File 'lib/json_api/serialization/deserializer.rb', line 153

def normalize_relationship_value(value)
  value.is_a?(Hash) ? value : value.to_h
end

#permitted_attributes_for_actionObject



27
28
29
30
31
32
33
34
# File 'lib/json_api/serialization/deserializer.rb', line 27

def permitted_attributes_for_action
  fields = if @action == :create
             @definition.permitted_creatable_fields
           else
             @definition.permitted_updatable_fields
           end
  fields.map(&:to_sym)
end

#process_non_polymorphic_relationship(attrs, association_name, param_name, id, type) ⇒ Object



247
248
249
250
# File 'lib/json_api/serialization/deserializer.rb', line 247

def process_non_polymorphic_relationship(attrs, association_name, param_name, id, type)
  validate_relationship_type(association_name, type)
  attrs["#{param_name}_id"] = id
end

#process_polymorphic_relationship(attrs, association_name, param_name, id, type) ⇒ Object



231
232
233
234
235
# File 'lib/json_api/serialization/deserializer.rb', line 231

def process_polymorphic_relationship(attrs, association_name, param_name, id, type)
  class_name = validate_and_get_class_name(type, association_name)
  attrs["#{param_name}_id"] = id
  attrs["#{param_name}_type"] = class_name
end

#process_regular_to_one_relationship(attrs, association_name, param_name, id, type) ⇒ Object



223
224
225
226
227
228
229
# File 'lib/json_api/serialization/deserializer.rb', line 223

def process_regular_to_one_relationship(attrs, association_name, param_name, id, type)
  if polymorphic_association?(association_name)
    process_polymorphic_relationship(attrs, association_name, param_name, id, type)
  else
    process_non_polymorphic_relationship(attrs, association_name, param_name, id, type)
  end
end

#process_relationship(attrs, association_name, value) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/json_api/serialization/deserializer.rb', line 139

def process_relationship(attrs, association_name, value)
  value_hash = normalize_relationship_value(value)
  data = extract_data_from_value(value_hash)
  param_name = association_param_name(association_name)

  ensure_relationship_writable!(association_name)

  return handle_null_relationship(attrs, param_name, association_name) if data.nil?
  return handle_empty_array_relationship(attrs, param_name, association_name) if empty_array?(data)

  validate_relationship_data_format!(data, association_name)
  process_relationship_data(attrs, association_name, param_name, data)
end

#process_relationship_data(attrs, association_name, param_name, data) ⇒ Object



190
191
192
193
194
195
196
# File 'lib/json_api/serialization/deserializer.rb', line 190

def process_relationship_data(attrs, association_name, param_name, data)
  if data.is_a?(Array)
    process_to_many_relationship(attrs, association_name, param_name, data)
  else
    process_to_one_relationship(attrs, association_name, param_name, data)
  end
end

#process_relationships(attrs) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/json_api/serialization/deserializer.rb', line 99

def process_relationships(attrs)
  permitted_relationships = @definition.relationship_names.map(&:to_s)

  relationships.each do |key, value|
    association_name = key.to_s
    next unless permitted_relationships.include?(association_name)

    process_relationship(attrs, association_name, value)
  end

  attrs
end

#process_to_many_relationship(attrs, association_name, param_name, data) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/json_api/serialization/deserializer.rb', line 198

def process_to_many_relationship(attrs, association_name, param_name, data)
  ids = data.map { |r| extract_id(r) }
  types = data.map { |r| extract_type(r) }

  # Check if this is an ActiveStorage attachment
  if types.any? && self.class.active_storage_blob_type?(types.first)
    process_active_storage_attachment(attrs, association_name, ids, singular: false)
    return
  end

  validate_relationship_type(association_name, types.first) unless polymorphic_association?(association_name)
  attrs["#{param_name.singularize}_ids"] = ids
end

#process_to_one_relationship(attrs, association_name, param_name, data) ⇒ Object



212
213
214
215
216
217
218
219
220
221
# File 'lib/json_api/serialization/deserializer.rb', line 212

def process_to_one_relationship(attrs, association_name, param_name, data)
  id = extract_id(data)
  type = extract_type(data)

  if self.class.active_storage_blob_type?(type)
    return process_active_storage_attachment(attrs, association_name, id, singular: true)
  end

  process_regular_to_one_relationship(attrs, association_name, param_name, id, type)
end

#relationship_id(relationship_name) ⇒ Object



73
74
75
# File 'lib/json_api/serialization/deserializer.rb', line 73

def relationship_id(relationship_name)
  relationship_ids(relationship_name).first
end

#relationship_ids(relationship_name) ⇒ Object



43
44
45
46
47
48
49
50
51
# File 'lib/json_api/serialization/deserializer.rb', line 43

def relationship_ids(relationship_name)
  relationship = find_relationship(relationship_name)
  return [] unless relationship

  data = extract_relationship_data(relationship)
  return [] unless data

  extract_ids_from_data(data)
end

#relationshipsObject



36
37
38
39
40
41
# File 'lib/json_api/serialization/deserializer.rb', line 36

def relationships
  # Handle both nested (:data => {:relationships => ...}) and flat (:relationships => ...) structures
  rels = @params.dig(:data, :relationships) || @params[:relationships] || {}
  rels = rels.to_h if rels.respond_to?(:to_h)
  rels.is_a?(Hash) ? rels : {}
end

#to_model_attributesObject



77
78
79
80
81
82
# File 'lib/json_api/serialization/deserializer.rb', line 77

def to_model_attributes
  attrs = attributes.dup
  attrs = apply_virtual_attribute_transformers(attrs)
  attrs = process_relationships(attrs)
  attrs.transform_keys(&:to_s)
end

#validate_and_get_class_name(type, association_name) ⇒ Object



237
238
239
240
241
242
243
244
245
# File 'lib/json_api/serialization/deserializer.rb', line 237

def validate_and_get_class_name(type, association_name)
  class_name = RelationshipHelpers.type_to_class_name(type)
  class_name.constantize
  class_name
rescue NameError
  raise ArgumentError,
        "Invalid relationship type for #{association_name}: " \
        "'#{type}' does not correspond to a valid model class"
end

#validate_relationship_data_format!(data, association_name) ⇒ Object

Raises:

  • (ArgumentError)


184
185
186
187
188
# File 'lib/json_api/serialization/deserializer.rb', line 184

def validate_relationship_data_format!(data, association_name)
  return if valid_relationship_data?(data)

  raise ArgumentError, "Invalid relationship data for #{association_name}: missing type or id"
end