TalentBox/harpy

View on GitHub
lib/harpy/resource.rb

Summary

Maintainability
A
55 mins
Test Coverage
require "active_support"
require "active_support/core_ext/object/blank"
require "active_support/core_ext/numeric/bytes"
require "active_model"
require "json"

module Harpy
  module Resource
    extend ActiveSupport::Concern

    included do |base|
      base.extend ActiveModel::Naming
      base.send :include, ActiveModel::Conversion
      base.extend ActiveModel::Translation
      base.extend ActiveModel::Callbacks
      base.send :include, ActiveModel::Validations
      base.send :include, ActiveModel::Validations::Callbacks
      base.define_model_callbacks :save, :create, :update, :destroy, :only => [:before, :after]
      base.send :include, InstanceMethods
    end

    def self.from_url(hash)
      results = {}
      hash.each do |klass, urls|
        results[klass] = Harpy.client.get [*urls]
      end
      Harpy.client.run results.values.flatten
      results.each do |klass, requests|
        requests.collect! do |request|
          klass.send :from_url_handler, request.response
        end
      end
      results
    end

    module ClassMethods
      def from_url(url)
        case url
        when Array
          Harpy.client.run(Harpy.client.get url).collect{|response| from_url_handler response}
        else
          from_url_handler Harpy.client.get url
        end
      end

      def from_urn(urn)
        url = Harpy.entry_point.urn urn
        from_url url if url
      end

      def from_id(id)
        from_urn urn id
      end

      def urn(id)
        raise NotImplementedError
      end

      def resource_name
        name.underscore
      end

      def search(conditions={})
        response = Harpy.client.get url, :params => conditions
        case response.code
        when 200
          parsed = JSON.parse response.body
          items = parsed[resource_name].collect{|model| new model}
          Harpy::Collection.new parsed.merge(:items => items)
        else
          Harpy.client.invalid_code response
        end
      end

      def with_url(url)
        raise ArgumentError unless block_given?
        key = "#{resource_name}_url"
        old, Thread.current[key] = Thread.current[key], url
        result = yield
        Thread.current[key] = old
        result
      end

      def delete_from_url(url)
        delete_from_url_handler Harpy.client.delete url
      end

      def delete_from_urn(urn)
        url = Harpy.entry_point.urn urn
        url ? delete_from_url(url) : false
      end

      def delete_from_id(id)
        delete_from_urn urn id
      end

      def uman_attribute_name(attr, options = {})
        attr
      end

      def lookup_ancestors
        [self]
      end

    private

      def url
        Thread.current["#{resource_name}_url"] || Harpy.entry_point.resource_url(resource_name)
      end

      def from_url_handler(response)
        case response.code
        when 200
          new JSON.parse response.body
        when 404
          nil
        else
          Harpy.client.invalid_code response
        end
      end

      def delete_from_url_handler(response)
        case response.code
        when 204
          true
        when 404
          false
        else
          Harpy.client.invalid_code response
        end
      end
    end

    module InstanceMethods

      def initialize(attrs = nil)
        @attrs = {}
        self.attributes = attrs || {}
      end

      def attributes=(attrs)
        attrs.each do |key, value|
          if respond_to? "#{key}="
            send "#{key}=", value
          else
            @attrs[key] = value
          end
        end
      end

      def as_json(*args)
        hash = @attrs.dup
        hash.delete "link"
        hash.delete "urn"
        hash
      end

      def save
        if valid?
          run_callbacks :save do
            json = JSON.generate as_json
            raise BodyToBig, "Size: #{json.bytesize} bytes (max 1MB)" if json.bytesize > 1.megabyte
            persisted? ? update(json) : create(json)
          end
        else
          false
        end
      end

      def destroy
        raise Harpy::UrlRequired unless url
        run_callbacks :destroy do
          process_response Harpy.client.delete(url), :destroy
        end
      end

      def link(rel)
        link = (@attrs["link"]||[]).detect{|l| l["rel"]==rel.to_s}
        link["href"] if link
      end

      def url
        link "self"
      end

      def url_collection
        Harpy.entry_point.resource_url self.class.resource_name
      end

      def id
        @attrs["urn"].split(":").last if @attrs["urn"]
      end

      def persisted?
        @attrs["urn"].present?
      end

      def inspect
        "<#{self.class.name} @attrs:#{@attrs.inspect} @errors:#{errors.full_messages.inspect} persisted:#{persisted?}>"
      end

      def has_key?(key)
        @attrs.has_key? key.to_s
      end

      def hash
        urn.hash
      end

      def ==(other)
        other.equal?(self) || (urn && other.instance_of?(self.class) && other.urn == urn)
      end
      alias_method :eql?, :==

    private

      def create(json)
        run_callbacks :create do
          process_response Harpy.client.post(url_collection, :body => json), :create
        end
      end

      def update(json)
        run_callbacks :update do
          raise Harpy::UrlRequired unless url
          process_response Harpy.client.put(url, :body => json), :update
        end
      end

      def process_response(response, context)
        case response.code
        when 200, 201, 302
          @attrs.merge! JSON.parse(response.body)
          true
        when 204
          context==:create ? Harpy.client.invalid_code(response) : true
        when 401
          raise Harpy::Unauthorized, "Server returned a 401 response code"
        when 422
          JSON.parse(response.body)["errors"].each do |attr, attr_errors|
            attr_errors.each{|attr_error| errors.add(attr, :invalid, message: attr_error) }
          end
          false
        else
          Harpy.client.invalid_code response
        end
      end

      def read_attribute_for_validation(key)
        @attrs[key]
      end

      def method_missing(method, *args)
        key = method.to_s
        if persisted? && !@attrs.has_key?(key)
          super
        elsif key=~/[\]=]\z/
          super
        else
          @attrs[key]
        end
      end
    end
  end
end