Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save balakirevs/806bc537215f13e382ebe34f0295483d to your computer and use it in GitHub Desktop.
Save balakirevs/806bc537215f13e382ebe34f0295483d to your computer and use it in GitHub Desktop.
class SidePostingActiveRecordOperationsProcessor < ActiveRecordOperationsProcessor
define_jsonapi_resources_callbacks :create_included_resource_operation
after_create_resource_operation :append_create_sideposted_to_one_resource_operations
before_create_resource_operation :append_create_sideposted_to_many_resources_operations
before_replace_fields_operation :append_create_sideposted_to_many_resources_operations
def append_create_sideposted_to_one_resource_operations
return if @result.is_a?(JSONAPI::ErrorsOperationResult)
@operations.each do |operation|
resource_klass = operation.resource_klass
operation.data[:to_one].each do |relationship_name, id|
relationship = resource_klass._relationships[relationship_name]
next unless relationship
sideposted_resource = sideposted_resource(relationship, id)
next unless sideposted_resource
op = create_sideposted_resource_operation(operation, relationship, sideposted_resource)
@operations << op unless operations_contain?(op)
end
end
end
def append_create_sideposted_to_many_resources_operations
return if @result.is_a?(JSONAPI::ErrorsOperationResult)
@operations.each do |operation|
resource_klass = operation.resource_klass
id_store = {}
operation.data[:to_many].each do |relationship_name, ids|
id_store[relationship_name.to_sym] = ids.dup
end
operation.data[:to_many].each do |relationship_name, ids|
relationship = resource_klass._relationships[relationship_name]
next unless relationship
sideposted_resources = sideposted_resources(relationship, id_store[relationship_name.to_sym])
next unless sideposted_resources.any?
sideposted_resources.each do |sideposted_resource|
op = create_sideposted_resource_operation(operation, relationship, sideposted_resource)
@operations << op unless operations_contain?(op)
end
end
end
end
def sideposted_resource(relationship, id)
included = @request.params[:included]
return nil unless included
type = relationship.type.to_s.dasherize
included.each_with_index do |resource_object, index|
if resource_object['type'] == type && resource_object['id'] == id
return {
index: index,
resource_object: resource_object
}
end
end
return nil
end
def sideposted_resources(relationship, ids)
ids.map do |id|
sideposted_resource(relationship, id)
end.select(&:present?)
end
def create_sideposted_resource_operation(operation, resource_relationship, sideposted_resource)
resource_object = sideposted_resource[:resource_object]
attributes = {
'id' => resource_object['id']
}
resource_object['attributes'].each do |key, value|
attributes[@request.unformat_key(key)] = value
end
to_one = {}
to_many = {}
(resource_object['relationships'] || {}).each do |relationship_name, relationship_data|
next if relationship_data['data'].nil?
relationship_name = @request.unformat_key(relationship_name)
relationship = resource_relationship.resource_klass._relationships[relationship_name]
case relationship
when JSONAPI::Relationship::ToOne
to_one[relationship_name] = relationship_data['data']['id']
end
end
operation.data[:to_many].each do |_relationship_name, relationship_ids|
relationship_ids.delete_if { |id| id =~ /\A_/ }
end
CreateIncludedResourceOperation.new(
resource_relationship.resource_klass,
context: @request.context,
parent_operation: operation,
operations: @operations,
relationship: resource_relationship,
index: sideposted_resource[:index],
data: {
attributes: attributes,
to_one: to_one,
to_many: to_many
}
)
end
def operations_contain?(operation)
@operations.any? do |op|
op.resource_klass == operation.resource_klass && op.data == operation.data
end
end
class CreateIncludedResourceOperation < JSONAPI::CreateResourceOperation
attr_reader :parent_operation, :relationship, :operations, :index
def initialize(resource_klass, options = {})
super
@parent_operation = options[:parent_operation]
@relationship = options[:relationship]
@operations = options[:operations]
@index = options[:index]
end
def apply
relation_name = relationship.relation_name(@context)
parent_resource = parent_operation.result.resource
data[:to_one].each do |relationship_name, id|
operation = operations.find { |o| o.result.try(:resource).try(:client_id) == id }
next unless operation
data[:to_one][relationship_name] = operation.result.resource.id
end
resource =
if parent_resource._model.respond_to?("build_#{relation_name}")
model = parent_resource._model.send("build_#{relation_name}")
@resource_klass.new(model, @context)
else
@resource_klass.create(@context)
end
result = resource.replace_fields(@data)
if result == :completed && parent_resource.respond_to?("#{relationship.name}=")
parent_resource.send("#{relationship.name}=", resource._model)
end
@result = JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
rescue JSONAPI::Exceptions::Error => exception
error_code = nil
errors = exception.errors.map do |error|
error_code ||= error.code
if error.source.try(:[], :pointer)
error.source[:pointer] = error.source[:pointer].sub('/data/', "/included/#{index}/")
end
error
end
JSONAPI::ErrorsOperationResult.new(error_code, errors)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment