zertico/api-client

View on GitHub
lib/api-client/base.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'active_model'

module ApiClient
  # ApiClient::Base provides a way to make easy api requests as well as making possible to use it inside rails.
  # A possible implementation:
  #  class Car < ApiClient::Base
  #    attr_accessor :color, :name, :year
  #  end
  # This class will handle Rails form as well as it works with respond_with.
  class Base
    include ActiveModel::Validations
    include ActiveModel::Conversion
    extend ActiveModel::Naming

    extend ApiClient::ClassMethods
    include ApiClient::InstanceMethods

    # @return [Integer] the id of the object.
    attr_accessor :id

    # @return [Hash] the request response.
    attr_accessor :response

    # @return [Hash] the errors object.
    attr_reader :errors

    # Initialize an object based on a hash of attributes.
    #
    # @param [Hash] attributes object attributes.
    # @return [Base] the object initialized.
    def initialize(attributes = {})
      @errors = ApiClient::Errors.new(self)
      remove_root(attributes).each do |name, value|
        send("#{name.to_s}=", value)
      end
    end

    # Return if a object is persisted on the database or not.
    #
    # @return [False] always return false.
    def persisted?
      false
    end

    # Return the api name to be used by this model.
    #
    # @return [False] return the default api name.
    def self.path
      @path || :default
    end

    # Return the api name to be used by this model.
    #
    # @return [False] return the default api name.
    def self.path=(path)
      @path = path.to_sym
    end

    # Return the api name to be used by this model.
    #
    # @return [False] return the default api name.
    def path
      self.class.path
    end

    # Return the resource path of the object on the api url.
    #
    # @return [String] the resource path on the api for this object.
    def self.resource_path
      return self.to_s.gsub('::', '/').downcase.pluralize unless @resource_path
      @resource_path
    end

    # Set the resource path of the object on the api.
    #
    # @param [String] resource_path path string.
    def self.resource_path=(resource_path)
      resource_path = resource_path[1, resource_path.size - 1] if resource_path[0, 1] == '/'
      @resource_path = resource_path
    end

    # Return the Root node name for this Class.
    #
    # @return [String] a string with the root node name for this class.
    def self.root_node
      @root_node.blank? ? self.to_s.split('::').last.underscore : @root_node
    end

    # Set a custom root node name instead of the Class name.
    #
    # @param [String] root_node root node name.
    def self.root_node=(root_node)
      @root_node = root_node
    end

    # Set methods to initialize associated objects.
    #
    # @param [Hash] associations classes.
    def self.associations=(associations = {})
      associations.each do |association, class_name|
        class_eval <<-EVAL
          def #{association.to_s}=(attributes = {})
            return @#{association.to_s} = attributes.map { |attr| #{class_name.constantize}.new(attr) } if attributes.instance_of?(Array)
            @#{association.to_s} = #{class_name.constantize}.new(attributes)
          end
          def #{association.to_s}
            @#{association.to_s}
          end
        EVAL
      end
    end

    class << self
      alias_method :association=, :associations=
    end

    # Overwrite #attr_acessor method to save instance_variable names.
    #
    # @param [Array] vars instance variables.
    def self.attr_accessor(*vars)
      @attributes ||= []
      @attributes.concat(vars)
      super
    end

    # Return an array with all instance variables setted through attr_accessor.
    #
    # @return [Array] instance variables.
    def self.attributes
      @attributes
    end

    # Return a hash with all instance variables setted through attr_accessor and its currently values.
    #
    # @return [Hash] instance variables and its values.
    def attributes
      self.class.instance_variable_get('@attributes').inject({}) { |hash, attribute| hash.merge(attribute.to_sym => self.send("#{attribute}")) }
    end

    # Update instance values based on a hash
    #
    # @param attr New attributes
    def attributes=(attr = {})
      remove_root(attr).each do |key, value|
        send("#{key}=", value)
      end
    end

    # Return a hash with a root node and all instance variables setted through attr_accessor and its currently values.
    #
    # @return [Hash] instance variables and its values.
    def to_hash
      { self.class.root_node.to_sym => attributes }
    end

    # Initialize a collection of objects. The collection will be an ApiClient::Collection object.
    # The objects in the collection will be all instances of this (ApiClient::Base) class.
    #
    # @return [Collection] a collection of objects.
    def self.collection
      url = "#{ApiClient.config.path[path]}#{resource_path}"
      attributes = ApiClient::Parser.response(ApiClient::Dispatcher.get(url), url)
      ApiClient::Collection.new(attributes, self)
    end

    class << self
      alias_method :all, :collection
    end

    # Set the hash of errors, making keys symbolic.
    #
    # @param [Hash] errs errors of the object.
    def errors=(errs = {})
      errors.add_errors(Hash[errs.map{|(key,value)| [key.to_sym,value]}])
    end
  end
end