module Flapjack::Gateways::JSONAPI::Methods::AssociationGet

Public Class Methods

registered(app) click to toggle source
# File lib/flapjack/gateways/jsonapi/methods/association_get.rb, line 11
def self.registered(app)
  app.helpers Flapjack::Gateways::JSONAPI::Helpers::Headers
  app.helpers Flapjack::Gateways::JSONAPI::Helpers::Miscellaneous
  app.helpers Flapjack::Gateways::JSONAPI::Helpers::Serialiser

  Flapjack::Gateways::JSONAPI::RESOURCE_CLASSES.each do |resource_class|

    jsonapi_links = if resource_class.respond_to?(:jsonapi_associations)
      resource_class.jsonapi_associations || {}
    else
      {}
    end

    singular_links = jsonapi_links.select {|n, jd|
      jd.link.is_a?(TrueClass) && jd.get.is_a?(TrueClass) && :singular.eql?(jd.number)
    }

    multiple_links = jsonapi_links.select {|n, jd|
      jd.link.is_a?(TrueClass) && jd.get.is_a?(TrueClass) && :multiple.eql?(jd.number)
    }

    resource = resource_class.short_model_name.plural

    unless singular_links.empty? && multiple_links.empty?

      app.class_eval do
        # GET and PATCH duplicate a lot of swagger code, but swagger-blocks
        # make it difficult to DRY things due to the different contexts in use
        single = resource.singularize

        singular_links.each_pair do |link_name, link_data|
          link_type = link_data.data_klass.short_model_name.name

          swagger_path "/#{resource}/{#{single}_id}/#{link_name}" do
            operation :get do
              key :description, link_data.descriptions[:get]
              key :operationId, "get_#{single}_#{link_name}"
              key :produces, [Flapjack::Gateways::JSONAPI.media_type_produced]
              parameter do
                key :name, "#{single}_id".to_sym
                key :in, :path
                key :description, "Id of a #{single}"
                key :required, true
                key :type, :string
              end
              response 200 do
                key :description, "GET #{resource} #{link_name} success"
                schema do
                  key :"$ref", "#{link_type}Reference".to_sym
                end
              end
              response 403 do
                key :description, "Forbidden; invalid request"
                schema do
                  key :'$ref', :Errors
                end
              end
              response 404 do
                key :description, "Not Found"
                schema do
                  key :'$ref', :Errors
                end
              end
            end
          end

          swagger_path "/#{resource}/{#{single}_id}/relationships/#{link_name}" do
            operation :get do
              key :description, link_data.descriptions[:get]
              key :operationId, "get_#{single}_#{link_name}"
              key :produces, [JSONAPI_MEDIA_TYPE]
              parameter do
                key :name, "#{single}_id".to_sym
                key :in, :path
                key :description, "Id of a #{single}"
                key :required, true
                key :type, :string
              end
              response 200 do
                key :description, "GET #{resource} success"
                schema do
                  key :"$ref", "#{link_type}Reference".to_sym
                end
              end
              response 403 do
                key :description, "Forbidden; invalid request"
                schema do
                  key :'$ref', :Errors
                end
              end
              response 404 do
                key :description, "Not Found"
                schema do
                  key :'$ref', :Errors
                end
              end
            end
          end
        end

        multiple_links.each_pair do |link_name, link_data|
          link_type = link_data.data_klass.short_model_name.name
          swagger_path "/#{resource}/{#{single}_id}/#{link_name}" do
            operation :get do
              key :description, link_data.descriptions[:get]
              key :operationId, "get_#{single}_#{link_name}"
              key :produces, [Flapjack::Gateways::JSONAPI.media_type_produced]
              parameter do
                key :name, "#{single}_id".to_sym
                key :in, :path
                key :description, "Id of a #{single}"
                key :required, true
                key :type, :string
              end
              response 200 do
                key :description, "GET #{resource} response"
                schema do
                  key :type, :array
                  items do
                    key :"$ref", "#{link_type}Reference".to_sym
                  end
                end
              end
              response 403 do
                key :description, "Forbidden; invalid request"
                schema do
                  key :'$ref', :Errors
                end
              end
              response 404 do
                key :description, "Not Found"
                schema do
                  key :'$ref', :Errors
                end
              end
            end
          end
          swagger_path "/#{resource}/{#{single}_id}/relationships/#{link_name}" do
            operation :get do
              key :description, link_data.descriptions[:get]
              key :operationId, "get_#{single}_links_#{link_name}"
              key :produces, [Flapjack::Gateways::JSONAPI.media_type_produced]
              parameter do
                key :name, "#{single}_id".to_sym
                key :in, :path
                key :description, "Id of a #{single}"
                key :required, true
                key :type, :string
              end
              response 200 do
                key :description, "GET #{resource} response"
                schema do
                  key :type, :array
                  items do
                    key :"$ref", "#{link_type}Reference".to_sym
                  end
                end
              end
              response 403 do
                key :description, "Forbidden; invalid request"
                schema do
                  key :'$ref', :Errors
                end
              end
              response 404 do
                key :description, "Not Found"
                schema do
                  key :'$ref', :Errors
                end
              end
            end
          end
        end
      end
    end

    assoc_patt = jsonapi_links.keys.map(&:to_s).join("|")

    app.get %r{^/#{resource}/(#{Flapjack::UUID_RE})/(?:relationships/)?(#{assoc_patt})(\?.+)?} do
      resource_id = params[:captures][0]
      assoc_name  = params[:captures][1].to_sym

      halt(404) unless singular_links.has_key?(assoc_name) ||
        multiple_links.has_key?(assoc_name)

      fields = params[:fields]
      incl   = params[:include].nil? ? nil : params[:include].split(',')

      assoc = jsonapi_links[assoc_name]

      extra_locks = incl.nil? ? [] : lock_for_include_clause(resource_class,
              :include => incl.dup)

      locks = assoc.lock_klasses | extra_locks
      locks.sort_by!(&:name)

      status 200

      halt(err(404, 'Unknown relationship')) if assoc.nil?

      empty = false
      included = nil
      result = nil
      meta = nil

      resource_class.lock(*locks) do
        res = resource_class.intersect(:id => resource_id)
        empty = res.empty?

        unless empty
          associated = res.all.first.send(assoc_name)

          result = case assoc.number
          when :multiple

            unless params[:filter].nil?
              unless params[:filter].is_a?(Array)
                halt(err(403, "Filter parameters must be passed as an Array"))
              end

              bad = params[:filter].detect {|f| f !~ /^#{assoc_name}\.(?:[^,]+)$/ }
              unless bad.nil?
                halt(err(403, "Filter params must start with '#{assoc_name}.', not contain comma: '#{bad}'"))
              end

              filters = params[:filter].collect do |f|
                f.sub(/^#{assoc_name}\./, '')
              end

              filter_ops = filter_params(assoc.data_klass, filters)

              associated = associated.intersect(filter_ops)
            end

            associated = associated.sort(:id => :asc)

            resources, _, meta = paginate_get(associated, :page => params[:page],
              :per_page => params[:per_page])

            result = resources.eql?([]) ? [] : resources.ids
          when :singular
            result = associated.id
          end

          unless incl.nil?
            included = as_jsonapi_included(resource_class, res,
              :include => incl, :fields => fields, :query_type => :association)
          end
        end
      end
      raise ::Zermelo::Records::Errors::RecordNotFound.new(resource_class, resource_id) if empty

      links = {
        :self    => "#{request.base_url}/#{resource}/#{resource_id}/relationships/#{assoc_name}",
        :related => "#{request.base_url}/#{resource}/#{resource_id}/#{assoc_name}"
      }

      data = case result
      when Array, Set
        result.map {|assoc_id| {:type => assoc.type, :id => assoc_id} }
      when String
        {:type => assoc.type, :id => result}
      else
        nil
      end

      ret = {:data => data, :links => links}
      ret[:included] = included unless included.nil?
      ret[:meta] = meta unless meta.nil?
      Flapjack.dump_json(ret)
    end
  end
end