ManageIQ/manageiq-api-common

View on GitHub
lib/insights/api/common/application_controller_mixins/parameters.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Insights
  module API
    module Common
      module ApplicationControllerMixins
        module Parameters
          def self.included(other)
            other.include(OpenapiEnabled)
          end

          def params_for_create
            check_if_openapi_enabled
            # We already validate this with OpenAPI validator, that validates every request, so we shouldn't do it again here.
            body_params.permit!
          end

          def safe_params_for_list
            check_if_openapi_enabled
            # :limit & :offset can be passed in for pagination purposes, but shouldn't show up as params for filtering purposes
            @safe_params_for_list ||= begin
              sort_by_default = (params[:sort_by].kind_of?(String) || params[:sort_by].kind_of?(Array)) ? [] : {}
              params.merge(params_for_polymorphic_subcollection).permit(*permitted_params, :filter => {}, :sort_by => sort_by_default)
            end
          end

          def permitted_params
            check_if_openapi_enabled
            api_doc_definition.all_attributes + [:limit, :offset, :sort_by] + [subcollection_foreign_key]
          end

          def subcollection_foreign_key
            "#{request_path_parts["primary_collection_name"].singularize}_id"
          end

          def params_for_polymorphic_subcollection
            return {} unless subcollection?
            return {} unless reflection = primary_collection_model&.reflect_on_association(request_path_parts["subcollection_name"])
            return {} unless as = reflection.options[:as]

            {"#{as}_type" => primary_collection_model.name, "#{as}_id" => request_path_parts["primary_collection_id"]}
          end

          def primary_collection_model
            @primary_collection_model ||= request_path_parts["primary_collection_name"].singularize.classify.safe_constantize
          end

          def params_for_list
            check_if_openapi_enabled
            safe_params = safe_params_for_list.slice(*all_attributes_for_index)
            if safe_params[subcollection_foreign_key_using_through_relation]
              # If this is a through relation, we need to replace the :foreign_key by the foreign key with right table
              # information. So e.g. :container_images with :tags subcollection will have {:container_image_id => ID} and we need
              # to replace it with {:container_images_tags => {:container_image_id => ID}}, where :container_images_tags is the
              # name of the mapping table.
              safe_params[through_relation_klass.table_name.to_sym] = {
                subcollection_foreign_key_using_through_relation => safe_params.delete(subcollection_foreign_key_using_through_relation)
              }
            end

            safe_params
          end

          def through_relation_klass
            check_if_openapi_enabled
            return unless subcollection?
            return unless reflection = primary_collection_model&.reflect_on_association(request_path_parts["subcollection_name"])
            return unless through = reflection.options[:through]

            primary_collection_model&.reflect_on_association(through).klass
          end

          def through_relation_name
            check_if_openapi_enabled
            # Through relation name taken from the subcollection model side, so we can use this for table join.
            return unless through_relation_klass
            return unless through_relation_association = model.reflect_on_all_associations.detect { |x| !x.polymorphic? && x.klass == through_relation_klass }

            through_relation_association.name
          end

          def subcollection_foreign_key_using_through_relation
            return unless through_relation_klass

            subcollection_foreign_key
          end

          def all_attributes_for_index
            check_if_openapi_enabled
            api_doc_definition.all_attributes + [subcollection_foreign_key_using_through_relation]
          end

          def filtered
            check_if_openapi_enabled
            association_attribute_properties =
              Insights::API::Common::Filter.association_attribute_properties(api_doc_definitions, safe_params_for_list[:filter])
            extra_attribute_properties = extra_attributes_for_filtering.merge(association_attribute_properties)

            Insights::API::Common::Filter.new(model, safe_params_for_list[:filter], api_doc_definition, extra_attribute_properties).apply
          end

          def extra_attributes_for_filtering
            {}
          end

          def pagination_limit
            safe_params_for_list[:limit]
          end

          def pagination_offset
            safe_params_for_list[:offset]
          end

          def query_sort_by
            safe_params_for_list[:sort_by]
          end

          def params_for_update
            check_if_openapi_enabled
            # We already validate this with OpenAPI validator, here only to satisfy the strong parameters check
            attr_list = *api_doc_definition.all_attributes - api_doc_definition.read_only_attributes
            strong_params_hash = sanctified_permit_param(api_doc_definition, attr_list)
            body_params.permit(strong_params_hash)
          end

          def sanctified_permit_param(api_doc_definition, attributes)
            api_doc_definition['properties'].each_with_object([]) do |(k, v), memo|
              # only add to the permit hash if we're actually trying to update it.
              next unless attributes.any? { |attr| attr.include?(k) }

              memo << if v['type'] == 'array'
                        { k => [] }
                      elsif v['type'] == 'object'
                        { k => {} }
                      else
                        k
                      end
            end
          end

          def check_if_openapi_enabled
            raise ArgumentError, "Openapi not enabled" unless self.class.openapi_enabled
          end
        end
      end
    end
  end
end