module Jamf::CollectionResource::ClassMethods
Class Methods
Public Class Methods
when this module is included, also extend our ‘parent’ class methods
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 90 def self.extended(extender) 91 Jamf.load_msg "--> #{extender} is extending Jamf::CollectionResource::ClassMethods" 92 end
Public Instance Methods
Get all instances of a CollectionResource
, possibly sorted or limited by a filter.
By default, this method will return a single Array
data about all items in the CollectionResouce, in the server’s default sort order, or a sort order you specify. As long as you don’t request a filtered result, this full list is cached for future use (see Caching, below)
If you specify a filter, the Array
returned by the server will contain only matching objects, and it will not be cached.
#### Server-side Sorting
Sorting criteria can be provided using the ‘sort:’ parameter, which is a String
of the format ‘property:direction’, where direction is ‘asc’ or ‘desc’ E.g.
"username:asc"
Multiple properties are supported, either as separate strings in an Array
, or a single string, comma separated. E.g.
"username:asc,timestamp:desc"
is the same as
["username:asc", "timestamp:desc"]
which will sort by username alphabetically, and within each username, sort by timestamp newest first.
Please see the JamfPro API documentation for the resource for details about available sorting properties and default sorting criteria
When the sort: param is provided, the server is always queried, and the results will be cached, as long as the results were not filtered. See Caching, below.
#### Filtering
Some CollectionResouces support RSQL filters to limit which objects are returned by the server. These filters can be applied using the filter: parameter, in which case this ‘all` method will return “all that match the filter”.
The filter parameter is a string, as it would be provided in the API URL manually, e.g. ‘categoryName==“Category”’ (be careful about inner quoting)
If the resource doesn’t support filters, the filter parameter is ignored.
Please see the JamfPro API documentation for the resource to see if filters are supported, and a list of available fields. See also developer.jamf.com/jamf-pro/docs/filtering-with-rsql
#### Instantiation
All data from the API comes from the server in JSON format, mostly as JSON ‘objects’, which are the equivalent of ruby Hashes. When fetching an individual instance of an object from the API, ruby-jss uses the JSON Hash
to create the ruby object, i.e. to ‘instantiate’ it as an instance of its ruby class. Doing this for many objects can slow things down.
Because of this, the ‘all’ method defaults to returning an Array
of the minimally-processed JSON Hashes it gets from the API. If you can get your desired data from these Hashes, it may be more efficient to do so.
However sometimes you really need the fully instantiated ruby objects for all items returned - especially if you’re using filters and not actually
processing all items of the class. In such cases you can pass a truthy
value to the instantiate: parameter, and the Array
will contain fully instantiated ruby objects, not Hashes of API data.
#### Caching - none for Jamf
Pro API objects.
Unlike the Classic APIObjects, Objects from the Jamf
Pro API are not cached and any call to ‘all’ or methods that use it, will always query the API. If you need to use the resulting array for multiple tasks, save it into a variable and use that.
@param sort [String, Array
<String>] Server-side sorting criteria in the
format: property:direction, where direction is 'asc' or 'desc'. Multiple properties are supported, either as separate strings in an Array, or a single string, comma separated.
@param filter [String] An RSQL filter string. Not all collection resources
currently support filters, and if they don't, this will be ignored.
@param instantiate [Boolean] Defaults to false. Should the items in the
returned Array be ruby instances of the CollectionObject subclass, or plain Hashes of data as returned by the API?
@param cnx [Jamf::Connection] The API connection to use, default: Jamf.cnx
@return [Array<Hash, Jamf::CollectionResource
>] The objects in the collection
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 258 def all(sort: nil, filter: nil, instantiate: false, cnx: Jamf.cnx, refresh: nil) 259 # if we are here, we need to query for all items, possibly filtered and 260 # sorted 261 sort = Jamf::Sortable.parse_url_sort_param(sort) 262 filter = filterable? ? Jamf::Filterable.parse_url_filter_param(filter) : nil 263 instantiate &&= self 264 265 # always use a pager to get all pages, because even if you don't ask for 266 # paged data, it comes in pages or 2000 267 Jamf::Pager.all_pages( 268 list_path: self::LIST_PATH, 269 sort: sort, 270 filter: filter, 271 instantiate: instantiate, 272 cnx: cnx 273 ) 274 end
@return [Hash{String: Symbol}] Method name to matching attribute name for
all identifiers
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 611 def available_list_methods 612 return @available_list_methods if @available_list_methods 613 614 @available_list_methods = {} 615 616 identifiers.each do |i| 617 meth_name = i.to_s.end_with?('s') ? "all_#{i}es" : "all_#{i}s" 618 @available_list_methods[meth_name] = i 619 end 620 @available_list_methods 621 end
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 560 def bulk_deletable? 561 singleton_class.ancestors.include? Jamf::BulkDeletable 562 end
By default, Collection Resources are creatable, i.e. new instances can be created with .create, and added to the JSS
with .save If a subclass is NOT creatble for any reason, just add
extend Jamf::Uncreatable
and this method will return false
@return [Boolean]
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 490 def creatable? 491 true 492 end
Make a new thing to be added to the API
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 496 def create(**params) 497 # no such animal when .creating 498 params.delete :id 499 500 # Which connection to use 501 params[:cnx] ||= Jamf.cnx 502 503 # So the super constructor knows we are instantiating an object that 504 # isn't from the API, and will do validation on all params. 505 params[:creating_from_create] = true 506 507 new(**params) 508 end
By default, CollectionResource
instances are deletable. If not, just extend the subclass with Jamf::Undeletable
, and this will return false, and .delete & delete
will raise errors
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 552 def deletable? 553 true 554 end
Delete one or more objects by id TODO: fix this return value, no more ErrorInfo
@param ids [Array<String,Integer>] The ids to delete
@param cnx [Jamf::Connection] The connection to use, default: Jamf.cnx
@return [Array<Jamf::Connection::JamfProAPIError::ErrorInfo] Info about any ids
that failed to be deleted.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 576 def delete(*ids, cnx: Jamf.cnx) 577 raise Jamf::UnsupportedError, "Deleting #{self} objects is not currently supported" unless deletable? 578 579 return bulk_delete(ids, cnx: Jamf.cnx) if bulk_deletable? 580 581 errs = [] 582 ids.each do |id_to_delete| 583 cnx.jp_delete "#{delete_path}/#{id_to_delete}" 584 rescue Jamf::Connection::JamfProAPIError => e 585 raise e unless e.http_response.status == 404 586 587 errs += e.errors 588 end # ids.each 589 errs 590 end
The path for DELETEing a single object from the collection.
Classes including CollectionResource
really need to define DELETE_PATH if it is not the same as the LIST_PATH.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 146 def delete_path 147 @delete_path ||= defined?(self::DELETE_PATH) ? self::DELETE_PATH : self::LIST_PATH 148 end
Retrieve a member of a CollectionResource
from the API
To create new members to be added to the JSS
, use {Jamf::CollectionResource.create}
You must know the specific identifier attribute you’re looking up, e.g. :id or :name or :udid, (or an aliase thereof) then you can specify it like ‘.fetch name: ’somename’‘, or `.fetch udid: ’someudid’‘
@param cnx the connection to use to fetch the object
@param ident_and_val an identifier attribute key and a search value
@return [CollectionResource] The ruby-instance of a Jamf
object
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 526 def fetch(searchterm = nil, random: false, cnx: Jamf.cnx, **ident_and_val) 527 if searchterm == :random 528 random = true 529 searchterm = nil 530 end 531 532 data = 533 if searchterm 534 raw_data searchterm, cnx: cnx 535 elsif random 536 all.sample 537 else 538 ident, value = ident_and_val.first 539 ident && value ? raw_data(ident: ident, value: value, cnx: cnx) : nil 540 end 541 542 raise Jamf::NoSuchItemError, "No matching #{self}" unless data 543 544 data[:cnx] = cnx 545 new(**data) 546 end
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 556 def filterable? 557 singleton_class.ancestors.include? Jamf::Filterable 558 end
The path for GETting a single object. The desired object id will be appended to the end, e.g. if this value is ‘v1/buildings’ and you want to GET the record for building id 23, then we will GET from ‘v1/buildings/23’
Classes including CollectionResource
really need to define GET_PATH if it is not the same as the LIST_PATH.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 105 def get_path 106 @get_path ||= defined?(self::GET_PATH) ? self::GET_PATH : self::LIST_PATH 107 end
@return [Array<Symbol>] the attribute names that are marked as identifiers
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 153 def identifiers 154 idents = self::OAPI_PROPERTIES.select { |_attr, deets| deets[:identifier] }.keys 155 idents += self::ALT_IDENTIFIERS if defined? self::ALT_IDENTIFIERS 156 idents += self::NON_UNIQUE_IDENTIFIERS if defined? self::NON_UNIQUE_IDENTIFIERS 157 idents.delete_if { |i| !self::OAPI_PROPERTIES.key?(i) } 158 idents 159 end
A Hash
of all members of this collection where the keys are some identifier and values are any other attribute.
@param ident [Symbol] An identifier of this Class, used as the key
for the mapping Hash. Aliases are acceptable, e.g. :sn for :serialNumber
@param to [Symbol] The attribute to which the ident will be mapped.
Aliases are acceptable, e.g. :name for :displayName
@param cached_list [Array<Hash>] The result of a previous call to .all
can be passed in here, to prevent calling .all again to generate a fresh list.
@param cnx (see .all)
@return [Hash {Symbol: Object}] A Hash
of identifier mapped to attribute
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 321 def map_all(ident, to:, cnx: Jamf.cnx, cached_list: nil, refresh: nil) 322 raise Jamf::InvalidDataError, "No identifier :#{ident} for class #{self}" unless 323 identifiers.include? ident 324 325 raise Jamf::NoSuchItemError, "No attribute :#{to} for class #{self}" unless self::OAPI_PROPERTIES.key? to 326 327 list = cached_list || all(cnx: cnx) 328 to_class = self::OAPI_PROPERTIES[to][:class] 329 to_multi = self::OAPI_PROPERTIES[to][:multi] 330 mapped = list.map do |i| 331 mapped_val = 332 if to_class.is_a?(Symbol) 333 i[to] 334 elsif to_multi 335 i[to].map { |sub_i| to_class.new(sub_i) } 336 else 337 to_class.new(i[to]) 338 end 339 340 [i[ident], mapped_val] 341 end # do i 342 mapped.to_h 343 end
Dynamically create_identifier_list_methods when one is called.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 594 def method_missing(method, *args, &block) 595 if available_list_methods.key? method.to_s 596 attr_name = available_list_methods[method.to_s] 597 create_identifier_list_method attr_name.to_sym, method 598 send method, *args 599 else 600 super 601 end 602 end
Return a Jamf::Pager
object for retrieving all collection items in smaller groups.
For other parameters, see CollectionResource.all
@param page_size [Integer] The pager object returns results in groups of
this many items. Minimum is 1, maximum is 2000, default is 100 Note: the final page of data may contain fewer items than the page_size
@return [Jamf::Pager] An object from which you can retrieve sequential or
arbitrary pages from the collection.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 288 def pager(page_size: Jamf::Pager::DEFAULT_PAGE_SIZE, sort: nil, filter: nil, instantiate: false, cnx: Jamf.cnx) 289 sort = Jamf::Sortable.parse_url_sort_param(sort) 290 filter = filterable? ? Jamf::Filterable.parse_url_filter_param(filter) : nil 291 instantiate &&= self 292 293 Jamf::Pager.new( 294 page_size: page_size, 295 list_path: self::LIST_PATH, 296 sort: sort, 297 filter: filter, 298 instantiate: instantiate, 299 cnx: cnx 300 ) 301 end
The path for PATCHing (updating in-place) a single object. The desired object id will be appended to the end, e.g. if this value is ‘v1/buildings’ and you want to PATCH the record for building id 23, then we will PATCH to ‘v1/buildings/23’
Classes including CollectionResource
really need to define PATCH_PATH if it is not the same as the LIST_PATH.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 128 def patch_path 129 @patch_path ||= defined?(self::PATCH_PATH) ? self::PATCH_PATH : self::LIST_PATH 130 end
The path for POSTing to create a single object in the collection.
Classes including CollectionResource
really need to define POST_PATH if it is not the same as the LIST_PATH.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 137 def post_path 138 @post_path ||= defined?(self::POST_PATH) ? self::POST_PATH : self::LIST_PATH 139 end
The path for PUTting (replacing) a single object. The desired object id will be appended to the end, e.g. if this value is ‘v1/buildings’ and you want to PUT the record for building id 23, then we will PUT to ‘v1/buildings/23’
Classes including CollectionResource
really need to define PUT_PATH if it is not the same as the LIST_PATH.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 116 def put_path 117 @put_path ||= defined?(self::PUT_PATH) ? self::PUT_PATH : self::LIST_PATH 118 end
Given a key (identifier) and value for this collection, return the raw data Hash
(the JSON object) for the matching API object or nil if there’s no match for the given value.
In general you should use this if the form:
raw_data identifier: value
where identifier is one of the available identifiers for this class like id:, name:, serialNumber: etc.
In the unlikely event that you dont know which identifier a value is for or want to be able to take any of them without specifying, then you can use
raw_data some_value
If some_value is an integer or a string containing an integer, it is assumed to be an :id, otherwise all the available identifers are searched, in the order you see them when you call <class>.identifiers
If no matching object is found, nil is returned.
Everything except :id is treated as a case-insensitive String
@param value [String, Integer] The identifier value to search fors
@param key: [Symbol] The identifier being used for the search.
E.g. if :serialNumber, then the value must be a known serial number, it is not checked against other identifiers. Defaults to :id
@param cnx: (see .all)
@return [Hash, nil] the basic dataset of the matching object,
or nil if it doesn't exist
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 382 def raw_data(searchterm = nil, ident: nil, value: nil, cnx: Jamf.cnx) 383 # given a value with no ident key 384 return raw_data_by_searchterm_only(searchterm, cnx: cnx) if searchterm 385 386 # if we're here, we should know our ident key and value 387 raise ArgumentError, 'Required parameter "identifier: value", where identifier is id:, name: etc.' unless ident && value 388 389 return raw_data_by_id(value, cnx: cnx) if ident == :id 390 return unless identifiers.include? ident 391 392 raw_data_by_other_identifier(ident, value, cnx: cnx) 393 end
get the basic dataset by id, with optional request params to get more than basic data
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 413 def raw_data_by_id(id, cnx: Jamf.cnx) 414 cnx.jp_get "#{get_path}/#{id}" 415 rescue Jamf::Connection::JamfProAPIError => e 416 return nil if e.errors.any? { |err| err.code == 'INVALID_ID' } 417 418 raise e 419 end
Given an indentier attr. key, and a value, return the raw data where that ident has that value, or nil
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 425 def raw_data_by_other_identifier(identifier, value, cnx: Jamf.cnx) 426 # if the API supports filtering by this identifier, just use that 427 return pager(filter: "#{identifier}==\"#{value}\"", page_size: 1, cnx: cnx).page(:first).first if filterable? && filter_keys.include?(identifier) 428 429 # otherwise we have to loop thru all the objects looking for the value 430 cmp_val = value.to_s 431 all(cnx: cnx).each do |data| 432 return data if data[identifier].to_s.casecmp? cmp_val 433 end 434 435 nil 436 end
Match the given value in all possibly identifiers
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 396 def raw_data_by_searchterm_only(searchterm, cnx: Jamf.cnx) 397 # if this is an integer or j_integer, assume its an ID 398 return raw_data_by_id(searchterm, cnx: cnx) if searchterm.to_s.j_integer? 399 400 identifiers.each do |ident| 401 next if ident == :id 402 403 data = raw_data_by_other_identifier(ident, searchterm, cnx: cnx) 404 return data if data 405 end # identifiers.each 406 407 nil 408 end
this is needed to prevent problems with method_missing
!
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 605 def respond_to_missing?(method, *) 606 available_list_methods.key?(method.to_s) || super 607 end
Look up the valid ID for any arbitrary identifier. In general you should use this if the form:
valid_id identifier: value
where identifier is one of the available identifiers for this class like id:, name:, serialNumber: etc.
In the unlikely event that you dont know which identifier a value is for or want to be able to take any of them without specifying, then you can use
valid_id some_value
If some_value is an integer or a string containing an integer, it is assumed to be an id: otherwise all the available identifers are searched, in the order you see them when you call <class>.identifiers
If no matching object is found, nil is returned.
WARNING: Do not use this to look up ids for getting the raw API data for an object. Since this calls .raw_data itself, it is redundant to use .valid_id to get an id to then pass on to .raw_data Use raw_data
directly like this:
data = raw_data(ident: val)
@param value [String,Integer] A value for an arbitrary identifier
@param cnx [Jamf::Connection] The connection to use. default: Jamf.cnx
@param ident_and_val [Hash{Symbol: String}] The identifier key and the value
to look for in that key, e.g. name: 'foo' or serialNumber: 'ASDFGH'
@return [String, nil] The id (integer-in-string) of the object, or nil
if no match found
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 477 def valid_id(searchterm = nil, cnx: Jamf.cnx, **ident_and_val) 478 raw_data(searchterm, cnx: cnx, **ident_and_val)&.dig(:id) 479 end
Private Instance Methods
called from method_missing
to create identifier lists on the fly. No filtering or sorting of these lists.
# File lib/jamf/api/jamf_pro/mixins/collection_resource.rb 626 def create_identifier_list_method(attr_name, list_method_name) 627 # only if the attr_name exists in the OAPI_PROPERTIES _and_ 628 # is listed as an identifier for this class. 629 if defined?(self::OAPI_PROPERTIES) && self::OAPI_PROPERTIES.key?(attr_name) && identifiers.include?(attr_name) 630 attr_def = self::OAPI_PROPERTIES[attr_name] 631 632 define_singleton_method(list_method_name) do |_refresh = nil, cnx: Jamf.cnx, cached_list: nil| 633 all_list = cached_list || all(cnx: cnx) 634 if attr_def[:class].is_a? Symbol 635 all_list.map { |i| i[attr_name] } 636 else 637 all_list.map { |i| attr_def[:class].new i[attr_name] } 638 end 639 end # define_singleton_method 640 Jamf.load_msg "Defined method #{self}##{list_method_name}" 641 else 642 643 define_singleton_method(list_method_name) do |*| 644 raise NoMethodError, "no method '#{list_method_name}': '#{attr_name}' is not an indentifier for #{self}" 645 end 646 end 647 end