module Jamf::CollectionResource::ClassMethods

Class Methods

Public Class Methods

extended(extender) click to toggle source

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

all(sort: nil, filter: nil, instantiate: false, cnx: Jamf.cnx, refresh: nil) click to toggle source

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
available_list_methods() click to toggle source

@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
bulk_deletable?() click to toggle source
    # File lib/jamf/api/jamf_pro/mixins/collection_resource.rb
560 def bulk_deletable?
561   singleton_class.ancestors.include? Jamf::BulkDeletable
562 end
creatable?() click to toggle source

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
create(**params) click to toggle source

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
deletable?() click to toggle source

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(*ids, cnx: Jamf.cnx) click to toggle source

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
delete_path() click to toggle source

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
fetch(searchterm = nil, random: false, cnx: Jamf.cnx, **ident_and_val) click to toggle source

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
filterable?() click to toggle source
    # File lib/jamf/api/jamf_pro/mixins/collection_resource.rb
556 def filterable?
557   singleton_class.ancestors.include? Jamf::Filterable
558 end
get_path() click to toggle source

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
identifiers() click to toggle source

@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
map_all(ident, to:, cnx: Jamf.cnx, cached_list: nil, refresh: nil) click to toggle source

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
method_missing(method, *args, &block) click to toggle source

Dynamically create_identifier_list_methods when one is called.

Calls superclass method
    # 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
pager(page_size: Jamf::Pager::DEFAULT_PAGE_SIZE, sort: nil, filter: nil, instantiate: false, cnx: Jamf.cnx) click to toggle source

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
patch_path() click to toggle source

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
post_path() click to toggle source

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
put_path() click to toggle source

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
raw_data(searchterm = nil, ident: nil, value: nil, cnx: Jamf.cnx) click to toggle source

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
raw_data_by_id(id, cnx: Jamf.cnx) click to toggle source

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
raw_data_by_other_identifier(identifier, value, cnx: Jamf.cnx) click to toggle source

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
raw_data_by_searchterm_only(searchterm, cnx: Jamf.cnx) click to toggle source

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
respond_to_missing?(method, *) click to toggle source

this is needed to prevent problems with method_missing!

Calls superclass method
    # 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
valid_id(searchterm = nil, cnx: Jamf.cnx, **ident_and_val) click to toggle source

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

create_identifier_list_method(attr_name, list_method_name) click to toggle source

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