module JSONAPI::Relationships::Sorting
def apply_db_sorts(related, db_sorts)
def apply_db_sorts(related, db_sorts) db_sorts.each do |sort_field| direction = RelationshipHelpers.extract_sort_direction(sort_field) field = RelationshipHelpers.extract_sort_field_name(sort_field) related = related.order(field => direction) end related end
def apply_sorting_to_relationship(related, association)
def apply_sorting_to_relationship(related, association) sorts = parse_sort_param return related if sorts.empty? related_model = association.klass db_sorts, virtual_sorts = partition_sorts_by_type(sorts, related_model) related = apply_db_sorts(related, db_sorts) apply_virtual_sorts_if_needed(related, virtual_sorts, related_model) end
def apply_virtual_sorts_if_needed(related, virtual_sorts, related_model)
def apply_virtual_sorts_if_needed(related, virtual_sorts, related_model) return related unless virtual_sorts.any? resource_class = ResourceLoader.find_for_model(related_model) sort_records_by_virtual_attributes(related.to_a, virtual_sorts, resource_class) end
def compare_by_field(record_a, record_b, sort_field, resource_class)
def compare_by_field(record_a, record_b, sort_field, resource_class) direction = RelationshipHelpers.extract_sort_direction(sort_field) field = RelationshipHelpers.extract_sort_field_name(sort_field) value_a = get_virtual_value(record_a, field, resource_class) value_b = get_virtual_value(record_b, field, resource_class) comparison = compare_values(value_a, value_b) direction == :desc ? -comparison : comparison end
def compare_records(record_a, record_b, virtual_sorts, resource_class)
def compare_records(record_a, record_b, virtual_sorts, resource_class) virtual_sorts.each do |sort_field| comparison = compare_by_field(record_a, record_b, sort_field, resource_class) return comparison unless comparison.zero? end 0 end
def compare_values(value_a, value_b)
def compare_values(value_a, value_b) return 0 if value_a.nil? && value_b.nil? return -1 if value_a.nil? return 1 if value_b.nil? value_a <=> value_b end
def get_virtual_value(record, field, resource_class)
def get_virtual_value(record, field, resource_class) resource_instance = resource_class.new(record, {}) field_sym = field.to_sym return resource_instance.public_send(field_sym) if resource_instance.respond_to?(field_sym, false) nil end
def partition_sorts_by_type(sorts, related_model)
def partition_sorts_by_type(sorts, related_model) sorts.partition do |sort_field| field = RelationshipHelpers.extract_sort_field_name(sort_field) related_model.column_names.include?(field.to_s) end end
def render_invalid_sort_fields(invalid_fields)
def render_invalid_sort_fields(invalid_fields) render_parameter_errors( invalid_fields, title: "Invalid Sort Field", detail_proc: ->(field) { "Invalid sort field requested: #{field}" }, source_proc: ->(_field) { { parameter: "sort" } }, ) end
def sort_records_by_virtual_attributes(records, virtual_sorts, resource_class)
def sort_records_by_virtual_attributes(records, virtual_sorts, resource_class) records.sort do |a, b| compare_records(a, b, virtual_sorts, resource_class) end end
def validate_sort_fields_for_association(sorts, association)
def validate_sort_fields_for_association(sorts, association) resource_class = ResourceLoader.find_for_model(association.klass) valid_fields = valid_sort_fields_for_resource(resource_class, association.klass) invalid_fields = invalid_sort_fields_for_columns(sorts, valid_fields) return if invalid_fields.empty? render_invalid_sort_fields(invalid_fields) end
def validate_sort_param
def validate_sort_param sorts = parse_sort_param return if sorts.empty? association = @resource.class.reflect_on_association(@relationship_name) return unless association&.collection? validate_sort_fields_for_association(sorts, association) end