require 'zendesk_api/helpers' require 'zendesk_api/trackie' require 'zendesk_api/actions' require 'zendesk_api/association' require 'zendesk_api/associations' require 'zendesk_api/verbs' module ZendeskAPI # Represents a resource that only holds data. class Data include Associations include Rescue class << self def inherited(klass) subclasses.push(klass) end def subclasses @subclasses ||= [] end # The singular resource name taken from the class name (e.g. ZendeskAPI::Ticket -> ticket) def singular_resource_name @singular_resource_name ||= ZendeskAPI::Helpers.snakecase_string(to_s.split("::").last) end # The resource name taken from the class name (e.g. ZendeskAPI::Ticket -> tickets) def resource_name @resource_name ||= Inflection.plural(singular_resource_name) end alias :model_key :resource_name # @private def only_send_unnested_params @unnested_params = true end # @private def unnested_params @unnested_params ||= false end end # @return [Hash] The resource's attributes attr_reader :attributes # @return [ZendeskAPI::Association] The association attr_accessor :association # Place to dump the last response attr_accessor :response # Create a new resource instance. # @param [Client] client The client to use # @param [Hash] attributes The optional attributes that describe the resource def initialize(client, attributes = {}) raise "Expected a Hash for attributes, got #{attributes.inspect}" unless attributes.is_a?(Hash) @association = attributes.delete(:association) || Association.new(:class => self.class) @global_params = attributes.delete(:global) || {} @client = client @attributes = ZendeskAPI::Trackie.new(attributes) if self.class.associations.none? {|a| a[:name] == self.class.singular_resource_name} ZendeskAPI::Client.check_deprecated_namespace_usage @attributes, self.class.singular_resource_name end @attributes.clear_changes unless new_record? end # Passes the method onto the attributes hash. # If the attributes are nested (e.g. { :tickets => { :id => 1 } }), passes the method onto the nested hash. def method_missing(*args, &block) raise NoMethodError, ":save is not defined" if args.first.to_sym == :save @attributes.send(*args, &block) end # Returns the resource id of the object or nil def id key?(:id) ? method_missing(:id) : nil end # Has this been object been created server-side? Does this by checking for an id. def new_record? id.nil? end # @private def loaded_associations self.class.associations.select do |association| loaded = @attributes.method_missing(association[:name]) loaded && !(loaded.respond_to?(:empty?) && loaded.empty?) end end # Returns the path to the resource def path(*args) @association.generate_path(self, *args) end # Passes #to_json to the underlying attributes hash def to_json(*args) method_missing(:to_json, *args) end # @private def to_s "#{self.class.singular_resource_name}: #{attributes.inspect}" end alias :inspect :to_s # Compares resources by class and id. If id is nil, then by object_id def ==(other) warn "Trying to compare #{other.class} to a Resource from #{caller.first}" if other && !other.is_a?(Data) other.is_a?(self.class) && ((other.id && other.id == id) || (other.object_id == self.object_id)) end alias :eql :== alias :hash :id # @private def inspect "#<#{self.class.name} #{@attributes.to_hash.inspect}>" end alias :to_param :attributes end # Indexable resource class DataResource < Data attr_accessor :error, :error_message extend Verbs end # Represents a resource that can only GET class ReadResource < DataResource extend Read end # Represents a resource that can only POST class CreateResource < DataResource include Create end # Represents a resource that can only PUT class UpdateResource < DataResource include Update end # Represents a resource that can only DELETE class DeleteResource < DataResource include Destroy end # Represents a resource that can CRUD (create, read, update, delete). class Resource < DataResource extend Read include Create include Update include Destroy end class SingularResource < Resource; end end