locomotivecms/mounter

View on GitHub
lib/locomotive/mounter/engine_api.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'uri'

module Locomotive
  module Mounter

    class EngineApi

      include HTTMultiParty

      format :json

      # Get a new token from the Engine API and set it for
      # this class. It raises an exception if the operation fails.
      # Example of the base uri: locomotivecms.com, nocoffee.fr.
      #
      # @param [ Hash / Array ] *args A hash or parameters in that order: uri, email, password or uri, api_key
      #
      # @return [ String ] The new token
      #
      def self.set_token(*args)
        _credentials = self.credentials(args)

        uri = URI _credentials.delete(:uri)
        self.base_uri uri.to_s
        self.basic_auth uri.user, uri.password if uri.userinfo

        response = post('/tokens.json', body: _credentials) #{ email: email, password: password })

        if response.code < 400
          self.default_params auth_token: response['token']
          response['token']
        elsif response.code == 404 # ssl option missing
          raise WrongCredentials.new("#{uri}/tokens.json does not respond. Perhaps, the ssl option is missing in your config/deploy.yml file")
        else
          raise WrongCredentials.new("#{response['message']} (#{response.code})")
        end
      end

      # Read a resource or a list of resources from the API
      # Raise an exception if something went wrong.
      #
      # @param [ String ] resource_name The path to the resource (usually, the resource name)
      # @param [ Hash ] query If we want to filter the results
      # @param [ String ] locale The locale for the request
      # @param [ Array ] attribute_names The attributes we want to keep in the response
      #
      def self.fetch(resource_name, query = {}, locale = nil, attribute_names = nil)
        params = { query: query || {} }
        params[:query][:locale] = locale if locale

        url       = "/#{resource_name}.json"
        response  = self.get(url, params)
        data      = response.parsed_response

        if response.success?
          object_or_list = self.keep_attributes(data, attribute_names)

          if response.headers['x-total-pages']
            PaginatedCollection.new(url, params, attribute_names).tap do |collection|
              collection.list           = object_or_list
              collection.total_pages    = response.headers['x-total-pages'].to_i
              collection.total_entries  = response.headers['x-total-entries'].to_i
            end
          else
            object_or_list
          end
        else
          raise ApiReadException.new(data['error'])
        end
      end

      # Create a resource from the API.
      # Raise an exception if something went wrong.
      #
      # @param [ String ] resource_name The path to the resource (usually, the resource name)
      # @param [ Hash ] attributes The attributes of the resource
      # @param [ String ] locale The locale for the request
      # @param [ Array ] attribute_names The attributes we want to keep in the response
      #
      # @return [ Object] The response of the API
      #
      def self.create(resource_name, attributes, locale = nil, attribute_names = nil)
        params    = self.build_create_or_params(resource_name, attributes, locale)
        response  = self.post("/#{resource_name}.json", params)
        data      = response.parsed_response

        if response.success?
          self.keep_attributes(data, attribute_names)
        else
          raise ApiWriteException.new(data)
        end
      end

      # Update a resource from the API.
      # Raise an exception if something went wrong.
      #
      # @param [ String ] resource_name The path to the resource (usually, the resource name)
      # @param [ String ] id The unique identifier of the resource
      # @param [ Hash ] attributes The attributes of the resource
      # @param [ String ] locale The locale for the request
      # @param [ Array ] attribute_names The attributes we want to keep in the response
      #
      # @return [ Object] The response of the API
      #
      def self.update(resource_name, id, attributes, locale = nil, attribute_names = nil)
        params    = self.build_create_or_params(resource_name, attributes, locale)
        response  = self.put("/#{resource_name}/#{id}.json", params)
        data      = response.parsed_response

        if response.success?
          self.keep_attributes(data, attribute_names)
        else
          raise ApiWriteException.new(data)
        end
      end

      def self.teardown
        Locomotive::Mounter::EngineApi.default_options[:base_uri] = nil
        Locomotive::Mounter::EngineApi.default_options[:base_auth] = nil
        Locomotive::Mounter::EngineApi.default_options[:default_params] = {}
      end

      protected

      def self.credentials(args)
        if args.first.is_a?(Hash)
          args.first
        elsif args.size == 3
          # uri, email, password
          { uri: args[0], email: args[1], password: args[2] }
        elsif args.size == 2
          # uri, api_key
          { uri: args[0], api_key: args[1] }
        else
          {}
        end
      end

      def self.build_create_or_params(resource_name, attributes, locale)
        name = resource_name.to_s.split('/').last.singularize

        params = { query: { name => attributes } }
        params[:query][:locale] = locale if locale

        params
      end

      def self.keep_attributes(data, attribute_names)
        return data if attribute_names.blank?

        case data
        when Hash then data.to_hash.delete_if { |k, _| !attribute_names.include?(k) }
        when Array
          data.map do |row|
            row.delete_if { |k, _| !attribute_names.include?(k) }
          end
        else
          data
        end
      end

    end

    class PaginatedCollection < Struct.new(:url, :params, :attribute_names)

      attr_accessor :list, :total_pages, :total_entries

      alias :size :total_entries

      def each(&block)
        if self.total_pages == 1 && self.list
          self.list.each(&block)
        else
          self.paginated_each(&block)
        end
      end

      def paginated_each(&block)
        loop do
          self.params[:query][:page] = self.page

          response  = Locomotive::Mounter::EngineApi.get(self.url, self.params)
          data      = response.parsed_response

          if response.success?
            self.list = Locomotive::Mounter::EngineApi.send(:keep_attributes, data, self.attribute_names)
            self.list.each(&block)
          else
            raise ApiReadException.new(data['error'])
          end

          break if self.page >= self.total_pages

          @page += 1
        end
      end

      def page
        @page ||= 1
      end

    end

  end
end