windoo 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +9 -0
  3. data/LICENSE.txt +177 -0
  4. data/README.md +222 -0
  5. data/lib/windoo/base_classes/array_manager.rb +335 -0
  6. data/lib/windoo/base_classes/criteria_manager.rb +327 -0
  7. data/lib/windoo/base_classes/criterion.rb +226 -0
  8. data/lib/windoo/base_classes/json_object.rb +472 -0
  9. data/lib/windoo/configuration.rb +221 -0
  10. data/lib/windoo/connection/actions.rb +152 -0
  11. data/lib/windoo/connection/attributes.rb +156 -0
  12. data/lib/windoo/connection/connect.rb +402 -0
  13. data/lib/windoo/connection/constants.rb +55 -0
  14. data/lib/windoo/connection/token.rb +489 -0
  15. data/lib/windoo/connection.rb +92 -0
  16. data/lib/windoo/converters.rb +31 -0
  17. data/lib/windoo/exceptions.rb +34 -0
  18. data/lib/windoo/mixins/api_collection.rb +408 -0
  19. data/lib/windoo/mixins/constants.rb +43 -0
  20. data/lib/windoo/mixins/default_connection.rb +75 -0
  21. data/lib/windoo/mixins/immutable.rb +34 -0
  22. data/lib/windoo/mixins/loading.rb +38 -0
  23. data/lib/windoo/mixins/patch/component.rb +102 -0
  24. data/lib/windoo/mixins/software_title/extension_attribute.rb +106 -0
  25. data/lib/windoo/mixins/utility.rb +23 -0
  26. data/lib/windoo/objects/capability.rb +82 -0
  27. data/lib/windoo/objects/capability_manager.rb +52 -0
  28. data/lib/windoo/objects/component.rb +99 -0
  29. data/lib/windoo/objects/component_criteria_manager.rb +26 -0
  30. data/lib/windoo/objects/component_criterion.rb +66 -0
  31. data/lib/windoo/objects/extension_attribute.rb +149 -0
  32. data/lib/windoo/objects/kill_app.rb +92 -0
  33. data/lib/windoo/objects/kill_app_manager.rb +89 -0
  34. data/lib/windoo/objects/patch.rb +235 -0
  35. data/lib/windoo/objects/patch_manager.rb +240 -0
  36. data/lib/windoo/objects/requirement.rb +85 -0
  37. data/lib/windoo/objects/requirement_manager.rb +52 -0
  38. data/lib/windoo/objects/software_title.rb +407 -0
  39. data/lib/windoo/validate.rb +548 -0
  40. data/lib/windoo/version.rb +15 -0
  41. data/lib/windoo/zeitwerk_config.rb +158 -0
  42. data/lib/windoo.rb +56 -0
  43. metadata +141 -0
@@ -0,0 +1,335 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ # main module
9
+ module Windoo
10
+
11
+ module BaseClasses
12
+
13
+ # The common code for dealing with a managed array of objects in
14
+ # Software Titles.
15
+ #
16
+ # Array Managers manage an Array of instances of API objects, preventing
17
+ # direct access to the Array, but providing methods for adding, removing,
18
+ # updating, and moving array members, while appropriatly interacting with the
19
+ # API and maintaining consistency between the local Array and the server.
20
+ #
21
+ # This base class provides management of the actual Array, and doesn't
22
+ # intentionally communicate with the server at all. However, it
23
+ # may cause server interaction when calling methods on the objects held
24
+ # in the Array.
25
+ #
26
+ # Instances of subclasses of this class are held by API objects instead
27
+ # of the raw Array.
28
+ #
29
+ # For example, SoftwareTitles have a #patches method, which is a list
30
+ # of all the patches for the title. In the raw API data Hash, the :patches
31
+ # key contains an array of hashes of patch data.
32
+ #
33
+ # However, the SoftwareTitle#patches method does not return an Array.
34
+ # Intead it returns an instance of Windoo::PatchManager, a subclass of this class,
35
+ # which provides ways to add, update, and delete patches from the title.
36
+ #
37
+ # CAUTION: Do not instantiate (with .create) or delete members of the Array
38
+ # directly, use the `add_*` and `delete_` methods of the Array Manager, so
39
+ # that the local array automatically stays in sync with the server.
40
+ #
41
+ # TODO: Prevent instantiation of those objects outside of approved methods.
42
+ #
43
+ # Subclasses MUST define the constant MEMBER_CLASS to indicate the class of
44
+ # the items we are managing
45
+ #
46
+ class ArrayManager
47
+
48
+ # Constants
49
+ ##################################
50
+ ##################################
51
+
52
+ PP_OMITTED_INST_VARS = %i[@container].freeze
53
+
54
+ # Attributes
55
+ ##################################
56
+ ##################################
57
+
58
+ # @return [APICollection] the API object that contains this manager
59
+ attr_reader :container
60
+
61
+ # Constructor
62
+ ##################################
63
+ ##################################
64
+
65
+ # @param data [Array<Hash>] A JSON array of hashes from the API
66
+ # containing data the to construct one of these manager objects.
67
+ #
68
+ # @param container [Object] the object that contains this managed
69
+ # array of criteria
70
+ #
71
+ def initialize(data, container:)
72
+ @container = container
73
+ @managed_array = []
74
+ return unless data
75
+
76
+ @managed_array = data.map do |member_data|
77
+ self.class::MEMBER_CLASS.instantiate_from_container(container: container, **member_data)
78
+ end
79
+ end
80
+
81
+ # Public Instance Methods
82
+ ##################################
83
+ ##################################
84
+
85
+ # Only selected items are displayed with prettyprint
86
+ # otherwise its too much data in irb.
87
+ #
88
+ # @return [Array] the desired instance_variables
89
+ #
90
+ ###########################
91
+ def pretty_print_instance_variables
92
+ instance_variables - PP_OMITTED_INST_VARS
93
+ end
94
+
95
+ # @return [Array<Windoo::BaseClasses::Criterion>] A dup'd and frozen copy of
96
+ # the array of criteria maintained by this class
97
+ ###########################
98
+ def to_a
99
+ @managed_array.dup.freeze
100
+ end
101
+
102
+ # Some convenience wrappers for common array methods
103
+ # For other array methods, use #to_a to get the
104
+ # actual (readonly dup of) the array.
105
+ #####
106
+
107
+ # @return [Object]
108
+ ###########################
109
+ def [](idx)
110
+ @managed_array[idx]
111
+ end
112
+
113
+ # @return [Object]
114
+ ###########################
115
+ def first
116
+ @managed_array.first
117
+ end
118
+
119
+ # @return [Object]
120
+ ###########################
121
+ def last
122
+ @managed_array.last
123
+ end
124
+
125
+ # @return [Boolean]
126
+ ###########################
127
+ def empty?
128
+ @managed_array.empty?
129
+ end
130
+
131
+ # @return [Integer]
132
+ ###########################
133
+ def size
134
+ @managed_array.size
135
+ end
136
+ alias count size
137
+ alias length size
138
+
139
+ # Iterators - they have to use the
140
+ # frozen dup from to_a, since they
141
+ # might try to modify items as they
142
+ # iterate.
143
+ ###########################
144
+
145
+ # @return [Array]
146
+ ###########################
147
+ def each(&block)
148
+ to_a.each(&block)
149
+ end
150
+
151
+ # @return [Object, nil]
152
+ ###########################
153
+ def find(if_none = nil, &block)
154
+ to_a.find if_none, &block
155
+ end
156
+
157
+ # @return [Object, nil]
158
+ ###########################
159
+ def find_by_attr(attr_name, value)
160
+ return if empty?
161
+ return unless @managed_array.first.respond_to? attr_name
162
+
163
+ @managed_array.find { |i| i.send(attr_name) == value }
164
+ end
165
+
166
+ # @return [Integer, nil]
167
+ ###########################
168
+ def index(obj = nil, &block)
169
+ return to_a.index(obj) if obj
170
+
171
+ to_a.index(&block)
172
+ end
173
+
174
+ # Private Instance Methods
175
+ ##################################
176
+ ##################################
177
+ private
178
+
179
+ # Add a member to the array at a given position.
180
+ #
181
+ # Subclasses must define a related method to create the new member
182
+ # object, then call this method to add it to the array, then do any
183
+ # other processing needed after, e.g. returning some attribute of
184
+ # the object.
185
+ #
186
+ # NOTE: This method does not communicate with the server. You must add
187
+ # the object to the server in whatever method calls this one, preferably
188
+ # before calling this one, so that any server errors are raised before
189
+ # we insert the object into the array.
190
+ #
191
+ # @param new_member [Object] the object to be added to the array
192
+ #
193
+ # @param index [Integer] The array index at which to add the object.
194
+ # Defaults to -1 (the end of the array)
195
+ #
196
+ # @return [Object] the object that was added
197
+ #
198
+ ###########################
199
+ def add_member(new_member, index: -1)
200
+ @managed_array.insert index, new_member
201
+ new_member
202
+ end
203
+
204
+ # Update the details of an existing array member
205
+ #
206
+ # Subclasses should define a related method to do any kind of non-standard
207
+ # processing if needed, then call this method, perhaps after modifying the
208
+ # given attribs hash.
209
+ #
210
+ # This method will then call the matching setter method for each attrib
211
+ # provided, if available, passing in the new value.
212
+ #
213
+ # Those setters may or may not update the server immediately.
214
+ #
215
+ # @param id [Integer] The primary ID of the object to update.
216
+ # For an array of Windoo::Requirement, you would provide a value that is a
217
+ # 'requirementId', for an array of Windoo::Patch, the value would
218
+ # be a 'patchId'.
219
+ #
220
+ # @param attribs [Hash] Key=>value pairs for attributes to be updated
221
+ # and the new values to be applied.
222
+ # Each key will be transformed into a setter method and sent to the
223
+ # object with the value as input to the setter.
224
+ #
225
+ # If any special handling of an attrib is needed, the calling
226
+ # method should deal with that and modify the attribs hash
227
+ # before passing them into this method via #super.
228
+ #
229
+ # NOTE: Explicit nils are sent!
230
+ #
231
+ #
232
+ # @return [Object] the object that was updated
233
+ #
234
+ ###########################
235
+ def update_member(id, **attribs)
236
+ member = member_by_id(id)
237
+
238
+ attribs.each do |attr_name, new_val|
239
+ setter = "#{attr_name}="
240
+ member.send setter, new_val if member.respond_to? setter
241
+ end
242
+
243
+ member
244
+ end
245
+
246
+ # Move a member to a new location in the array. This
247
+ # does not talk to the server
248
+ #
249
+ # @param member [Object] the member to move
250
+ #
251
+ # @param index [Integer] the new index for the member
252
+ #
253
+ # @return [void]
254
+ ###########################
255
+ def move_member(member, index:)
256
+ curr_idx = @managed_array.index { |m| m == member }
257
+
258
+ @managed_array.insert index, @managed_array.delete_at(curr_idx)
259
+ end
260
+
261
+ # Delete a member of the array.
262
+ #
263
+ # This method will call the object's #delete method, if it has one,
264
+ # which may delete it from the server. It will then delete it from
265
+ # the local array
266
+ #
267
+ # Subclasses should define a related method that calls this one, doing
268
+ # any processing before or after
269
+ #
270
+ # @param id [Integer] The primary ID of the object to delete.
271
+ # For an array of Windoo::Requirement, you would provide a value that is a
272
+ # 'requirementId', for an array of Windoo::Patch, the value would
273
+ # be a 'patchId'.
274
+ #
275
+ # @return [Object] The object that was removed from the array
276
+ #
277
+ ###########################
278
+ def delete_member(id)
279
+ member = member_by_id(id)
280
+
281
+ # call its delete method, which may delete it from the server
282
+ member.delete if member.respond_to? :delete
283
+
284
+ # delete from the array
285
+ @managed_array.delete member
286
+
287
+ member
288
+ end
289
+
290
+ # Delete all members of the array
291
+ #
292
+ # Subclasses should override, or define a related method that calls this one,
293
+ # doing any processing before or after
294
+ #
295
+ # @return [void]
296
+ #
297
+ ###########################
298
+ def delete_all_members
299
+ @managed_array.each { |member| member.delete if member.respond_to? :delete }
300
+ @managed_array = []
301
+ end
302
+
303
+ # Return a member of the array by searching for its 'primary_id'
304
+ #
305
+ # @param id [Integer] The primary ID of the object to return.
306
+ # For an array of Windoo::Requirement, you would provide a value that is a
307
+ # 'requirementId', for an array of Windoo::Patch, the value would
308
+ # be a 'patchId'.
309
+ #
310
+ # @return [Object] The object with the given id
311
+ #
312
+ ###########################
313
+ def member_by_id(id)
314
+ member = @managed_array.find { |m| m.send(primary_id_key) == id }
315
+ return member if member
316
+
317
+ raise Windoo::NoSuchItemError, "No matching #{self.class::MEMBER_CLASS} with #{primary_id_key} #{id} found"
318
+ end
319
+
320
+ # The primary
321
+ ###########################
322
+ def primary_id_key
323
+ @primary_id_key ||= self.class::MEMBER_CLASS.primary_id_key
324
+ end
325
+
326
+ ###########################
327
+ def container_primary_id_key
328
+ @container_primary_id_key ||= @container.class.primary_id_key
329
+ end
330
+
331
+ end # class Criterion
332
+
333
+ end # module BaseClasses
334
+
335
+ end # module Windoo
@@ -0,0 +1,327 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ # main module
9
+ module Windoo
10
+
11
+ module BaseClasses
12
+
13
+ # The common code for dealing with a group of criteria-based objects
14
+ # in Software Titles.
15
+ #
16
+ # This class manages an array of instances of subclasses of
17
+ # {Windoo::BaseClasses::Criterion}
18
+ #
19
+ # See also: {Windoo::BaseClasses::ArrayManager}
20
+ # for info about managed Arrays.
21
+ #
22
+ # This class should be the superclass of classes representing
23
+ # the three ways Criteria are used in a {SoftwareTitle}.
24
+ #
25
+ # - SoftwareTitles have 'requirements'
26
+ # - These are criteria defining which computers have any version of the title installed.
27
+ # - {Patch Patches} within SoftwareTitles have 'capabilities'
28
+ # - These are criteria defining which computers are capable of installing/running the patch.
29
+ # - A Patch's '{Component component}' has a set of 'criteria'
30
+ # - These are criteria defining which comptuers have _this_ version of the title installed.
31
+ #
32
+ # Here's how that looks for requirements:
33
+ #
34
+ # - {Windoo::RequirementManager Windoo::RequirementManager} should be a subclass of this class, and must define<br/>
35
+ # the constant `MEMBER_CLASS = Windoo::Requirement` indicating that it should manage an array of...
36
+ # - {Windoo::Requirement Windoo::Requirement} objects which is a subclass of {Windoo::BaseClasses::Criterion}
37
+ # - {Windoo::SoftwareTitle#requirements} should return a single {Windoo::RequirementManager Windoo::RequirementManager} object
38
+ #
39
+ # Here's how to use a {Windoo::RequirementManager Windoo::RequirementManager}:
40
+ #
41
+ # title = Windoo::SoftwareTitle.fetch 'mytitle'
42
+ # title.requirements.to_a
43
+ # # => the readonly Array of Windoo::Requirement objects
44
+ #
45
+ # title.requirements.add_criterion(options: here)
46
+ # # => add a new Windoo::Requirement to the Array
47
+ #
48
+ # title.requirements.replace_criterion(victim_id, options: here)
49
+ # # => replace an existing Windoo::Requirement in the Array
50
+ #
51
+ # title.requirements.delete_criterion(victim_id)
52
+ # # => delete an existing Windoo::Requirement from the Array
53
+ #
54
+ # The other CriteriaManagers work the same way, they are just located in their respective places:
55
+ #
56
+ # # Patch Capabilities
57
+ #
58
+ # patch = title.patches.first
59
+ # # => a single patch
60
+ #
61
+ # patch.capabilities.to_a
62
+ # # => the readonly Array of Windoo::Capability objects
63
+ #
64
+ # patch.capabilities.add_criterion(options: here)
65
+ # # => add a new Windoo::Capability to the patch
66
+ #
67
+ # patch.capabilities.replace_criterion(victim_id, options: here)
68
+ # # => replace an existing Windoo::Capability in the patch
69
+ #
70
+ # patch.capabilities.delete_criterion(victim_id)
71
+ # # => delete an existing Windoo::Capability from the patch
72
+ #
73
+ # # Patch Component Criteria
74
+ #
75
+ # component = patch.component
76
+ # # => the component of a patch
77
+ #
78
+ # component.criteria.to_a
79
+ # # => the readonly Array of Windoo::ComponentCriterion objects
80
+ #
81
+ # component.criteria.add_criterion(options: here)
82
+ # # => add a new Windoo::ComponentCriterion to the component
83
+ #
84
+ # component.criteria.replace_criterion(victim_id, options: here)
85
+ # # => replace an existing Windoo::ComponentCriterion in the component
86
+ #
87
+ # component.criteria.delete_criterion(victim_id)
88
+ # # => delete an existing Windoo::ComponentCriterion from the component
89
+ #
90
+ #
91
+ # Subclasses MUST define the constant MEMBER_CLASS to indicate the class of the items we are managing
92
+ #
93
+ class CriteriaManager < Windoo::BaseClasses::ArrayManager
94
+
95
+ # Class Methods
96
+ ######################
97
+
98
+ # @return [Array<String>] The names of all available recon criteria names
99
+ #####
100
+ def self.available_names(cnx: Windoo.cnx)
101
+ cnx.get 'valuelists/criteria/names'
102
+ end
103
+
104
+ # @return [Array<String>] The possible criteria types
105
+ #####
106
+ def self.available_types(cnx: Windoo.cnx)
107
+ cnx.get 'valuelists/criteria/types'
108
+ end
109
+
110
+ # Find out the available critrion operators for a given criterion.
111
+ # e.g. for the criterion 'Application Title' the operators are:
112
+ # ["is", "is not", "has", "does not have"]
113
+ #
114
+ # for the criterion 'Application Bundle ID' the operators are:
115
+ # ["is", "is not", "like", "not like", "matches regex", "does not match regex"]
116
+ #
117
+ # for the criterion 'Computer Group' the operators are:
118
+ # ["member of", "not member of"]
119
+ #
120
+ # ...and so on.
121
+ #
122
+ # @param name [String] The criterion for which to get the operators
123
+ #
124
+ # @return [Array<String>] The possible operators for a given criterion name
125
+ #
126
+ #####
127
+ def self.operators_for(name, cnx: Windoo.cnx)
128
+ cnx.post 'valuelists/criteria/operators', { name: name }
129
+ end
130
+
131
+ # Public Instance Methods
132
+ ####################################
133
+
134
+ # Add a criterion to the end of this array.
135
+ #
136
+ # @param name [String] The name of the criterion
137
+ # To ask the server for an Array of all possible criteria names,
138
+ # use
139
+ # Windoo::BaseClasses::CriteriaManager.available_names
140
+ #
141
+ # @param operator [String] The operator to use for
142
+ # comparing the value given here with the value for
143
+ # a computer.
144
+ # To ask the server for an Array of all operators available for some
145
+ # criterion, use
146
+ # Windoo::BaseClasses::CriteriaManager.operators_for criterion_name
147
+ #
148
+ # @param value [String, integer] The value that will be
149
+ # compared with that on a computer, using the operator.
150
+ #
151
+ # @param type [String] how does Jamf Pro get this
152
+ # value for a computer? 'recon' means its a normal value
153
+ # gathered by a recon. 'extensionAttribute' means the value
154
+ # is generated by the extensionAttribute associated with
155
+ # this SoftwareTitle. Defaults to 'recon'.
156
+ #
157
+ # @param and_or [Symbol] :and or :or. Defines how this
158
+ # criterion is joined with the previous one in a chain of
159
+ # boolean logic. Default is :and
160
+ #
161
+ # @param absoluteOrderId [Integer] The zero-based position of this criterion
162
+ # among all the others in the array. By default, this criterion will be added
163
+ # at '-1', the end of the array
164
+ #
165
+ # @return [Integer] The id of the new criterion
166
+ #
167
+ def add_criterion(name:, operator:, value:, type: 'recon', and_or: :and, absoluteOrderId: nil)
168
+ absoluteOrderId ||= @managed_array.size
169
+
170
+ new_criterion = self.class::MEMBER_CLASS.create(
171
+ cnx: container.cnx,
172
+ container: container,
173
+ and_or: and_or,
174
+ name: name,
175
+ operator: operator,
176
+ value: value,
177
+ type: type.to_s,
178
+ absoluteOrderId: absoluteOrderId
179
+ )
180
+
181
+ # call the method from our superclass to add it to the array
182
+ add_member new_criterion, index: absoluteOrderId
183
+ update_local_absoluteOrderIds
184
+ new_criterion.primary_id
185
+ end
186
+
187
+ # Create a new criterion from the provided attributes and use it
188
+ # to replace the one with the given id.
189
+ #
190
+ # @param id [Integer] The primary ID of the criterion to update.
191
+ # So for an array of Windoo::Requirement, you would provide a 'requirementId'
192
+ #
193
+ # @param name [String] The name of the criterion
194
+ # To get an Array of all possible criteria names, use
195
+ # Windoo::BaseClasses::CriteriaManager.available_names
196
+ #
197
+ # @param operator [String] The operator to use for
198
+ # comparing the value given here with the value for
199
+ # a computer.
200
+ # To get an Array of all operators available for some criterion,
201
+ # use Windoo::BaseClasses::CriteriaManager.operators_for criterion_name
202
+ #
203
+ # @param value [String, integer] The value that will be
204
+ # compared with that on a computer, using the operator.
205
+ #
206
+ # @param type [String] how does Jamf Pro get this
207
+ # value for a computer? 'recon' means its a normal value
208
+ # gathered by a recon. 'extensionAttribute' means the value
209
+ # is generated by the extensionAttribute associated with
210
+ # this SoftwareTitle. Defaults to 'recon'.
211
+ #
212
+ # @param and_or [Symbol] :and or :or. Defines how this
213
+ # criterion is joined with the previous one in a chain of
214
+ # boolean logic. Default is :and
215
+ #
216
+ # @return [Integer] The id of the new criterion
217
+ #
218
+ def replace_criterion(id, name:, operator:, value:, type: 'recon', and_or: :and)
219
+ victim = delete_member(id)
220
+
221
+ add_criterion(
222
+ and_or: and_or,
223
+ name: name,
224
+ operator: operator,
225
+ value: value,
226
+ type: type.to_s,
227
+ absoluteOrderId: victim.absoluteOrderId
228
+ )
229
+ # no need to run update_local_absoluteOrderIds, we haven't changed the order,
230
+ # and it was called by add_criterion
231
+
232
+ # Windoo::ConnectionError should only come from the Add operation,
233
+ # usually when one of the attributes given was a problem.
234
+ #
235
+ # The delete should give a Windoo::NoSuchItemError if the id doesn't
236
+ # exist on the server.
237
+ rescue Windoo::ConnectionError
238
+ # make sure the victim was really removed from the array
239
+ # It should have been by the delete_member call above,
240
+ @managed_array.delete_if { |c| c.primary_id == victim.primary_id }
241
+
242
+ # then re-add the victim in the same position
243
+ add_criterion(
244
+ and_or: victim.and_or,
245
+ name: victim.name,
246
+ operator: victim.operator,
247
+ value: victim.value,
248
+ type: victim.type.to_s,
249
+ absoluteOrderId: victim.absoluteOrderId
250
+ )
251
+ # and re-raise the error
252
+ raise
253
+ end
254
+
255
+ # Change the position of an existing criterion in the array
256
+ #
257
+ # @param id [Integer] The primary ID of the criterion to update.
258
+ # So for an array of Windoo::Requirement, you would provide a 'requirementId'
259
+ #
260
+ # @param absoluteOrderId [Integer] The zero-based position to which you want to
261
+ # move the criterion. So if you want to make it the third criterion
262
+ # in the list, use 2.
263
+ #
264
+ # @return [Integer] the new absoluteOrderId
265
+ #
266
+ def move_criterion(id, absoluteOrderId:)
267
+ # Can't move it beyond the end of the array....
268
+ max_idx = @managed_array.size - 1
269
+ absoluteOrderId = max_idx if absoluteOrderId > max_idx
270
+
271
+ # ... or before the beginning
272
+ absoluteOrderId = 0 if absoluteOrderId.negative?
273
+
274
+ # do it on the server first, to raise potential errs before
275
+ # modifying the array
276
+ criterion = update_member id, absoluteOrderId: absoluteOrderId
277
+
278
+ # now modify the array
279
+ move_member criterion, index: absoluteOrderId
280
+ update_local_absoluteOrderIds
281
+
282
+ absoluteOrderId
283
+ end
284
+
285
+ # Delete a criterion by its id
286
+ #
287
+ # @param id [Integer] The primary ID of the criterion to delete.
288
+ # So for an array of Windoo::Requirement, you would provide a 'requirementId'
289
+ #
290
+ # @return [Integer] The id of the deleted criterion
291
+ #
292
+ def delete_criterion(id)
293
+ delid = delete_member(id).deleted_id
294
+ update_local_absoluteOrderIds
295
+ delid
296
+ end
297
+
298
+ # Delete all the criteria
299
+ #
300
+ # @return [void]
301
+ #
302
+ def delete_all_criteria
303
+ delete_all_members
304
+ end
305
+
306
+ # Private Instance Methods
307
+ ####################################
308
+ private
309
+
310
+ # Update the local absoluteOrderId of Array members
311
+ # to match their array index, without updating the server
312
+ # cuz the server should have done it automatically
313
+ #
314
+ # @return [void]
315
+ def update_local_absoluteOrderIds
316
+ @managed_array.each_with_index do |criterion, index|
317
+ next unless criterion
318
+
319
+ criterion.local_absoluteOrderId = index
320
+ end
321
+ end
322
+
323
+ end # class Criterion
324
+
325
+ end # module BaseClasses
326
+
327
+ end # module Windoo