lib/jss/api_object.rb in ruby-jss-0.8.2 vs lib/jss/api_object.rb in ruby-jss-0.9.0.b1
- old
+ new
@@ -106,28 +106,22 @@
# so VALID_DATA_KEYS will be [:computers, :is_smart]
#
# *NOTE* Some API objects have data broken into subsections, in which case the
# VALID_DATA_KEYS are expected in the section :general.
#
+ #
+ # === Optional Constants
+ #
+ # ==== OTHER_LOOKUP_KEYS = [Hash{Symbol=>Hash}] Every object can be looked up by
+ # :id and :name, but some have other uniq identifiers that can also be used,
+ # e.g. :serial_number, :mac_address, and so on. This Hash, if defined,
+ # speficies those other keys for the subclass
+ # For more details about this hash, see {APIObject::DEFAULT_LOOKUP_KEYS},
+ # {APIObject.fetch}, and {APIObject#lookup_object_data}
+ #
class APIObject
- # Mix-Ins
- #####################################
-
- # Class Variables
- #####################################
-
- # This Hash holds the most recent API query for a list of all items in any subclass,
- # keyed by the subclass's RSRC_LIST_KEY. See the self.all class method.
- #
- # When the .all method is called without an argument, and this hash has
- # a matching value, the value is returned, rather than requerying the
- # API. The first time a class calls .all, or whnever refresh is
- # not false, the API is queried and the value in this hash is updated.
- #
- @@all_items = {}
-
# Class Methods
#####################################
# Return an Array of Hashes for all objects of this subclass in the JSS.
#
@@ -146,11 +140,11 @@
#
# Subclasses implementing those API classes should provide .all_xxx
# class methods for accessing those other values as mapped Arrays,
# e.g. JSS::Computer.all_udids
#
- # The results of the first query for each subclass is stored in @@all_items
+ # The results of the first query for each subclass is stored in JSS.api.object_list_cache
# and returned at every future call, so as to not requery the server every time.
#
# To force requerying to get updated data, provided a non-false argument.
# I usually use :refresh, so that it's obvious what I'm doing, but true, 1,
# or anything besides false or nil will work.
@@ -159,24 +153,24 @@
#
# @return [Array<Hash{:name=>String, :id=> Integer}>]
#
def self.all(refresh = false)
raise JSS::UnsupportedError, '.all can only be called on subclasses of JSS::APIObject' if self == JSS::APIObject
- @@all_items[self::RSRC_LIST_KEY] = nil if refresh
- return @@all_items[self::RSRC_LIST_KEY] if @@all_items[self::RSRC_LIST_KEY]
- @@all_items[self::RSRC_LIST_KEY] = JSS::API.get_rsrc(self::RSRC_BASE)[self::RSRC_LIST_KEY]
+ JSS.api.object_list_cache[self::RSRC_LIST_KEY] = nil if refresh
+ return JSS.api.object_list_cache[self::RSRC_LIST_KEY] if JSS.api.object_list_cache[self::RSRC_LIST_KEY]
+ JSS.api.object_list_cache[self::RSRC_LIST_KEY] = JSS.api.get_rsrc(self::RSRC_BASE)[self::RSRC_LIST_KEY]
end
# Returns an Array of the JSS id numbers of all the members
# of the subclass.
#
# e.g. When called from subclass JSS::Computer,
# returns the id's of all computers in the JSS
#
# @param refresh[Boolean] should the data be re-queried from the API?
#
- # @return [Array<Integer>] the ids of all items of this subclass in the JSS
+ # @return [Array<Integer>] the ids of all it1ems of this subclass in the JSS
#
def self.all_ids(refresh = false)
all(refresh).map { |i| i[:id] }
end
@@ -236,13 +230,13 @@
# @param refresh[Boolean] should the data re-queried from the API?
#
# @return [Hash{Integer => Object}] the objects requested
def self.all_objects(refresh = false)
objects_key = "#{self::RSRC_LIST_KEY}_objects".to_sym
- @@all_items[objects_key] = nil if refresh
- return @@all_items[objects_key] if @@all_items[objects_key]
- @@all_items[objects_key] = all(refresh).map { |o| new id: o[:id] }
+ JSS.api.object_list_cache[objects_key] = nil if refresh
+ return JSS.api.object_list_cache[objects_key] if JSS.api.object_list_cache[objects_key]
+ JSS.api.object_list_cache[objects_key] = all(refresh).map { |o| new id: o[:id] }
end
# Return true or false if an object of this subclass
# with the given name or id exists on the server
#
@@ -366,10 +360,48 @@
when nil
nil
end
end
+ # What are all the lookup keys available for this class?
+ #
+ # @return [Array<Symbol>] the DEFAULT_LOOKUP_KEYS plus any OTHER_LOOKUP_KEYS
+ # defined for this class
+ #
+ def self.lookup_keys
+ return DEFAULT_LOOKUP_KEYS.keys unless defined? self::OTHER_LOOKUP_KEYS
+ DEFAULT_LOOKUP_KEYS.keys + self::OTHER_LOOKUP_KEYS.keys
+ end
+
+ # @return [Hash] the available lookup keys mapped to the appropriate
+ # resource key for building a REST url to retrieve an object.
+ #
+ def self.rsrc_keys
+ hash = {}
+ all_keys = if defined?(self::OTHER_LOOKUP_KEYS)
+ DEFAULT_LOOKUP_KEYS.merge self::OTHER_LOOKUP_KEYS
+ else
+ DEFAULT_LOOKUP_KEYS
+ end
+ all_keys.each { |key, deets| hash[key] = deets[:rsrc_key]}
+ hash
+ end
+
+ # @return [Hash] the available lookup keys mapped to the appropriate
+ # list class method (e.g. id: :all_ids )
+ #
+ def self.lookup_key_list_methods
+ hash = {}
+ all_keys = if defined?(self::OTHER_LOOKUP_KEYS)
+ DEFAULT_LOOKUP_KEYS.merge self::OTHER_LOOKUP_KEYS
+ else
+ DEFAULT_LOOKUP_KEYS
+ end
+ all_keys.each { |key, deets| hash[key] = deets[:list]}
+ hash
+ end
+
# Retrieve an object from the API.
#
# This is the preferred way to retrieve existing objects from the JSS.
# It's a wrapper for using APIObject.new
# and avoids the confusion of using ruby's .new class method when you're not
@@ -380,16 +412,31 @@
# @param args[Hash] The data for fetching an object, such as id: or name:
# See {APIObject#initialize}
#
# @return [APIObject] The ruby-instance of a JSS object
#
- def self.fetch(**args)
+ def self.fetch(arg)
raise JSS::UnsupportedError, 'JSS::APIObject cannot be instantiated' if self.class == JSS::APIObject
- raise ArgumentError, 'Use .create to create new JSS objects' if args[:id] == :new
- new args
- end
+ # if given a hash (or a colletion of named params)
+ # pass to .new
+ if arg.is_a? Hash
+ raise ArgumentError, 'Use .create to create new JSS objects' if arg[:id] == :new
+ return new arg
+ end
+
+ # loop thru the lookup_key list methods for this class
+ # and if it's result includes the desired value,
+ # the pass they key and arg to .new
+ lookup_key_list_methods.each do |key, method_name|
+ return new({key => arg}) if self.send(method_name).include? arg
+ end # each key
+
+ # if we're here, we couldn't find a matching object
+ raise NoSuchItemError, "No #{self::RSRC_OBJECT_KEY} found matching '#{arg}'"
+ end # fetch
+
# Make a ruby instance of a not-yet-existing APIObject.
#
# This is the preferred way to create new objects in the JSS.
# It's a wrapper for using APIObject.new with the 'id: :new' parameter.
# and helps avoid the confusion of using ruby's .new class method for making
@@ -402,33 +449,56 @@
# @param args[Hash] The data for creating an object, such as name:
# See {APIObject#initialize}
#
# @return [APIObject] The un-created ruby-instance of a JSS object
#
- def self.make(**args)
+ def self.make(args = {})
raise JSS::UnsupportedError, 'JSS::APIObject cannot be instantiated' if self.class == JSS::APIObject
raise ArgumentError, "Use '#{self.class}.fetch id: xx' to retrieve existing JSS objects" if args[:id]
args[:id] = :new
new args
end
-
### Class Constants
#####################################
# These Symbols are added to VALID_DATA_KEYS for performing the
# :data validity test described above.
#
REQUIRED_DATA_KEYS = [:id, :name].freeze
- # By default, these keys are available for object lookups
- # Others can be added by subclasses using an array of them
- # as the second argument to super(initialize)
- # The keys must be Symbols that match the keyname in the resource url.
- # e.g. :serialnumber for JSSResource/computers/serialnumber/xxxxx
+ # All API objects have an id and a name. As such By these keys are available
+ # for object lookups.
#
- DEFAULT_LOOKUP_KEYS = [:id, :name].freeze
+ # Others can be defined by subclasses in their OTHER_LOOKUP_KEYS constant
+ # which has the same format, described here:
+ #
+ # The merged Hashes DEFAULT_LOOKUP_KEYS and OTHER_LOOKUP_KEYS
+ # define what unique identifiers can be passed as parameters to the
+ # fetch method for retrieving an object from the API.
+ # They also define the class methods that return a list (Array) of all such
+ # identifiers for the class (e.g. the :all_ids class method returns an array
+ # of all id's for an APIObject subclass)
+ #
+ # Since there's often a discrepency between the name of the identifier as
+ # an attribute (e.g. serial_number) and the REST resource key for
+ # retrieving that object (e.g. ../computers/serialnumber/xxxxx) this hash
+ # also explicitly provides the REST resource key for a given lookup key, so
+ # e.g. both serialnumber and serial_number can be used, and both will have
+ # the resource key 'serialnumber' and the list method ':all_serial_numbers'
+ #
+ # Here's how the Hash is structured, using serialnumber as an example:
+ #
+ # LOOKUP_KEYS = {
+ # serialnumber: {rsrc_key: :serialnumber, list: :all_serial_numbers},
+ # serial_number: {rsrc_key: :serialnumber, list: :all_serial_numbers}
+ # }
+ #
+ DEFAULT_LOOKUP_KEYS = {
+ id: {rsrc_key: :id, list: :all_ids},
+ name: {rsrc_key: :name, list: :all_names}
+ }.freeze
# Attributes
#####################################
# @return [Integer] the JSS id number
@@ -459,24 +529,17 @@
# @option args :id[Integer] the jss id to look up
#
# @option args :name[String] the name to look up
#
# @option args :data[Hash] the JSON output of a separate {JSS::APIConnection} query
+ # NOTE: This arg is deprecated and will be removed in a future release.
#
- # @param other_lookup_keys[Array<Symbol>] Hash keys other than :id and :name, by which an API
- # lookup may be performed.
#
- def initialize(args = {}, other_lookup_keys = [])
- args[:other_lookup_keys] ||= other_lookup_keys
+ def initialize(args = {})
raise JSS::UnsupportedError, 'JSS::APIObject cannot be instantiated' if self.class == JSS::APIObject
- ### what lookup key are we using, if any?
- lookup_keys = DEFAULT_LOOKUP_KEYS
- lookup_keys += self.class::OTHER_LOOKUP_KEYS if defined? self.class::OTHER_LOOKUP_KEYS
- lookup_key = (lookup_keys & args.keys)[0]
-
####### Previously looked-up JSON data
# DEPRECATED: pre-lookedup data is never used
# and support for it will be going away.
if args[:data]
@@ -484,20 +547,21 @@
validate_external_init_data
###### Make a new one in the JSS, but only if we've included the Creatable module
elsif args[:id] == :new
-
validate_init_for_creation(args)
setup_object_for_creation(args)
+
return
###### Look up the data via the API
else
@init_data = look_up_object_data(args)
end ## end arg parsing
+
parse_init_data
@need_to_update = false
end # init
# Public Instance Methods
@@ -594,11 +658,11 @@
#
# @return [void]
#
def delete
return nil unless @in_jss
- JSS::API.delete_rsrc @rest_rsrc
+ JSS.api.delete_rsrc @rest_rsrc
@rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name}"
@id = nil
@in_jss = false
@need_to_update = false
:deleted
@@ -666,27 +730,32 @@
#
# @return [Hash] The parsed JSON data for the object from the API
#
def look_up_object_data(args)
# what lookup key are we using?
- combined_lookup_keys = self.class::DEFAULT_LOOKUP_KEYS + args[:other_lookup_keys]
- lookup_key = (combined_lookup_keys & args.keys)[0]
+ lookup_keys = self.class.lookup_keys
+ lookup_key = (self.class.lookup_keys & args.keys)[0]
+ raise JSS::MissingDataError, "Args must include a lookup key, one of: :#{lookup_keys.join(', :')}" unless lookup_key
+ rsrc_key = self.class.rsrc_keys[lookup_key]
- raise JSS::MissingDataError, "Args must include a lookup key, one of: :#{combined_lookup_keys.join(', :')}" unless lookup_key
+ rsrc = "#{self.class::RSRC_BASE}/#{rsrc_key}/#{args[lookup_key]}"
- rsrc = "#{self.class::RSRC_BASE}/#{lookup_key}/#{args[lookup_key]}"
+ # if needed, a non-standard object key can be passed by a subclass.
+ # e.g. User when loookup is by email.
+ rsrc_object_key = args[:rsrc_object_key] ? args[:rsrc_object_key] : self.class::RSRC_OBJECT_KEY
- return JSS::API.get_rsrc(rsrc)[self.class::RSRC_OBJECT_KEY]
+ return JSS.api.get_rsrc(rsrc)[rsrc_object_key]
rescue RestClient::ResourceNotFound
- raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} found matching: #{lookup_key}/#{args[lookup_key]}"
+ raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} found matching: #{rsrc_key}/#{args[lookup_key]}"
end
# Start examining the @init_data recieved from the API
#
# @return [void]
#
def parse_init_data
+ @init_data ||= {}
# set empty strings to nil
@init_data.jss_nillify! '', :recurse
# Find the "main" subset which contains :id and :name
@main_subset = find_main_subset
@@ -800,10 +869,10 @@
# @return [Type] description_of_returned_object
#
def setup_object_for_creation(args)
# NOTE: subclasses may want to pre-populate more keys in @init_data when :id == :new
# then parse them into attributes later.
- @init_data = { name: args[:name] }
+ @init_data = args
@name = args[:name]
@in_jss = false
@rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name}"
@need_to_update = true
end