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,226 @@
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
+ #
7
+
8
+ # main module
9
+ module Windoo
10
+
11
+ module BaseClasses
12
+
13
+ # The base class for dealing with criteria in Software Titles.
14
+ #
15
+ # WARNING: CRITERIA ARE IMMUTABLE. See below for how to deal
16
+ # with this
17
+ #
18
+ # Criteria are individual comparisons or 'filter rules' used to
19
+ # identify matching computers, much as they are used for Jamf Smart
20
+ # Groups or Advanced Searches.
21
+ #
22
+ # For example, a single criterion might specify all computers where
23
+ # the app 'FooBar.app' is installed. Another might specify that
24
+ # FooBar.app is version 12.3.6, or that the OS is Big Sur or higher.
25
+ #
26
+ # In SoftwareTitles, criteria are used in three places:
27
+ #
28
+ # - As the 'requirements' of a Software Title.
29
+ # Each requrement is one criterion, and the Array of them
30
+ # define which computers have any version of the title
31
+ # installed. Access to the Array is handled via the
32
+ # Windoo::RequirementManager class.
33
+ #
34
+ # - As the criteria of the sole 'component' of a Patch.
35
+ # A Patch's 'components' is an Array of one item (for historical
36
+ # reasons apparently). That component contains an Array of
37
+ # criteria that define which computers have _that specific_
38
+ # version of the Patch's Title installed. Access to the Array is
39
+ # handled via the Windoo::ComponentCriteriaManager class.
40
+ #
41
+ # - As the 'capabilities' of a Patch.
42
+ # Each capability is one criterion, and the Array of them
43
+ # define which computers are capable of running, and thus
44
+ # allowed to install, this Patch. Access to the Array is handled
45
+ # via the Windoo::CapabilitytManager class.
46
+ #
47
+ # This class is the superclass of the individual types of criteria
48
+ # used in the Title Editor. For example Windoo::Requirement is a
49
+ # subclass of this class.
50
+ #
51
+ # Criteria always come in groups (perhaps a group of one) and are
52
+ # stored in Arrays. However, the arrays are not directly accessible,
53
+ # but are managed by subclasses of Windoo::CriteriaManager. For
54
+ # example, the Windoo::Requirement objects of a SoftwareTitle are stored
55
+ # in an instance of Windoo::RequirementManager.
56
+ #
57
+ # These 'managers' provide methods for adding, replacing, moving,
58
+ # and deleting the individual criteria in the array, and maintain
59
+ # consistency between the local array and the actual objects stored
60
+ # on the server.
61
+ #
62
+ # CRITERIA ARE IMMUTABLE:
63
+ #
64
+ # Criteria are immutable once created, mostly because to modify any of the
65
+ # primary values of one (name, operator, & value), you have to modify them
66
+ # all at once or you end up in a chicken/egg situation where the server will
67
+ # complain of invalid values, which can't easily be worked around when
68
+ # updating the values individually.
69
+ #
70
+ # Instead of modifying one, delete it and replace it with a new one in the
71
+ # same position in the array. There's a convenience method for this in the
72
+ # CriteriaManager called #replace_criterion(id)
73
+ #
74
+ # When creating criteria using CriteriaManager#add_criterion, they area added
75
+ # to the end of the array by default, but you can specify the position using the
76
+ # absoluteOrderId value. You can also move the position of a criterion in the
77
+ # array using CriteriaManager#update_criterion method and passing in the new
78
+ # absoluteOrderId value.
79
+ #
80
+ class Criterion < Windoo::BaseClasses::JSONObject
81
+
82
+ # Mixins
83
+ #####################
84
+
85
+ extend Windoo::Mixins::Immutable
86
+
87
+ # Constants
88
+ #####################
89
+
90
+ # The authoritative list of available types can be read from the API
91
+ # at GET 'valuelists/criteria/types', or also via
92
+ # Windoo::BaseClasses::CriteriaManager.available_types
93
+
94
+ TYPE_RECON = 'recon'
95
+ TYPE_EA = 'extensionAttribute'
96
+
97
+ TYPES = [TYPE_RECON, TYPE_EA].freeze
98
+
99
+ # These attributes must be updated together for Criteria objects
100
+ ATTRIBUTES_TO_UPDATE_TOGETHER = %i[name operator value].freeze
101
+
102
+ # Attributes
103
+ ######################
104
+
105
+ JSON_ATTRIBUTES = {
106
+
107
+ # @!attribute absoluteOrderId
108
+ # @return [Integer] The zero-based position of this requirement among
109
+ # all those used by the title. Should be identical to the Array index
110
+ # of this requirement in the #requirements attribute of the SoftwareTitle
111
+ # instance that uses this requirement
112
+ absoluteOrderId: {
113
+ class: :Integer
114
+ },
115
+
116
+ # @!attribute and_or
117
+ # @return [Symbol] Either :and or :or. This indicates how this criterion is
118
+ # joined to the previous one in a chain of boolean logic.
119
+ #
120
+ # NOTE: In the Title Editor JSON data, this key for this value is the
121
+ # word "and" and its value is a boolean: if false, the joiner is "or".
122
+ # However, because "and" is a reserved word in ruby, we convert that
123
+ # value into this one during initialization, and back when sending
124
+ # data to the Title Editor.
125
+ and_or: {
126
+ class: :Symbol
127
+ },
128
+
129
+ # @!attribute name
130
+ # @return [String] The name of the criteria to search in this requirement.
131
+ # See the API resource GET 'valuelists/criteria/names'
132
+ name: {
133
+ class: :String,
134
+ required: true
135
+ },
136
+
137
+ # @!attribute operator
138
+ # @return [String] The criteria operator to apply to the criteria name
139
+ # See the API resource POST 'valuelists/criteria/names', {name: 'Criteria Name'}
140
+ operator: {
141
+ class: :String,
142
+ required: true
143
+ },
144
+
145
+ # @!attribute value
146
+ # @return [Object] The the value to apply with the operator to the named criteria
147
+ # We can't specify the class of the value, because it might be a String, Integer, Time, or
148
+ # something else.
149
+ value: {
150
+ class: :Object
151
+ },
152
+
153
+ # @!attribute type
154
+ # @return [String] What type of criteria is the named one?
155
+ # Must be one of the values in TYPES
156
+ type: {
157
+ class: :String,
158
+ required: true
159
+ }
160
+ }.freeze
161
+
162
+ # Constructor
163
+ ######################
164
+ def initialize(**init_data)
165
+ super
166
+ # if #super didn't set @and_or, set it now
167
+ return unless @and_or.nil?
168
+
169
+ @and_or = @init_data[:and] == false ? :or : :and
170
+ end
171
+
172
+ # Public Instance Methods
173
+ ################################
174
+
175
+ # Override handle @and_or before creating
176
+ #
177
+ def to_api
178
+ api_data = super
179
+ api_data[:and] = (@and_or == :and)
180
+ api_data.delete :and_or
181
+ api_data
182
+ end
183
+
184
+ # Allow array managers to change the absoluteOrderId.
185
+ #
186
+ # @todo Only allow this to be called from a CriteriaManager.
187
+ #
188
+ # @param new_index [Integer] The new, zero-based index for this
189
+ # criterion.
190
+ #
191
+ # @return [Integer] the id of the updated criterion
192
+ #
193
+ def absoluteOrderId=(new_index)
194
+ new_value = validate_attr :absoluteOrderId, new_index
195
+ return if new_value == @absoluteOrderId
196
+
197
+ update_on_server :absoluteOrderId, new_value
198
+ @absoluteOrderId = new_value
199
+ end
200
+
201
+ # Update the local absoluteOrderId without updating it on
202
+ # the server.
203
+ #
204
+ # Why??
205
+ #
206
+ # Because changing the value on the server for one criterion
207
+ # using #absoluteOrderId= will automatically change it on the
208
+ # server for all the others.
209
+ #
210
+ # After changing one on the server and updating that one in the
211
+ # local array, the CriteriaManager will use this, without
212
+ # updating the server, to change the value for all others in the
213
+ # array to match their array index.
214
+ #
215
+ # @todo Only allow this to be called from a CriteriaManager.
216
+ #
217
+ # @return [void]
218
+ def local_absoluteOrderId=(new_index)
219
+ @absoluteOrderId = new_index
220
+ end
221
+
222
+ end # class Criterion
223
+
224
+ end # module BaseClasses
225
+
226
+ end # module Windoo
@@ -0,0 +1,472 @@
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
+
7
+ # frozen_string_literal: true
8
+
9
+ # main module
10
+ module Windoo
11
+
12
+ module BaseClasses
13
+
14
+ # The base class for objects that are instantiated from
15
+ # a JSON Hash
16
+ class JSONObject
17
+
18
+ # Constants
19
+ ######################
20
+
21
+ # When using prettyprint, don't spit out these instance variables.
22
+ PP_OMITTED_INST_VARS = %i[@init_data @container @softwareTitle].freeze
23
+
24
+ # Attributes
25
+ ######################
26
+
27
+ # @return [Hash] The raw JSON data this object was instantiated with
28
+ attr_reader :init_data
29
+
30
+ # Public Class Methods
31
+ ######################
32
+
33
+ # by default, instances of subclasses are mutable
34
+ # as a whole (even if some attributes are readonly)
35
+ # To make them immutable, they should extend
36
+ # Windoo::Mixins::Immutable, which overrides
37
+ # this method
38
+ def self.mutable?
39
+ true
40
+ end
41
+
42
+ # The merged JSON_ATTRIBUTES Hashes of
43
+ # any subclass of JSONObject including
44
+ # all ancestors up to JSONObject itself
45
+ #
46
+ # @return [Hash{Symbol => Hash}]
47
+ ####
48
+ def self.json_attributes
49
+ return {} if self == Windoo::BaseClasses::JSONObject
50
+ return @json_attributes if @json_attributes
51
+
52
+ @json_attributes = defined?(self::JSON_ATTRIBUTES) ? self::JSON_ATTRIBUTES.dup : {}
53
+ @json_attributes.merge! superclass.json_attributes if superclass.respond_to?(:json_attributes)
54
+ @json_attributes
55
+ end
56
+
57
+ # The attributes that are required to have values.
58
+ # These must be passed in when calling .create, and must
59
+ # exist in the instance when calling #save
60
+ #
61
+ # @return [Array<Symbol>]
62
+ ####
63
+ def self.required_attributes
64
+ @required_attributes ||= json_attributes.select { |_key, deets| deets[:required] }.keys
65
+ end
66
+
67
+ # @return [Array<Symbol>] the available identifier keys for objects that have them
68
+ def self.ident_keys
69
+ return @ident_keys if @ident_keys
70
+
71
+ @ident_keys = []
72
+
73
+ json_attributes.each do |key, deets|
74
+ next unless deets[:identifier]
75
+
76
+ @primary_id_key = key if deets[:identifier] == :primary
77
+ @ident_keys << key
78
+ end
79
+
80
+ @ident_keys
81
+ end
82
+
83
+ # @return [Symbol] the key of the primary identifier, if there is one
84
+ def self.primary_id_key
85
+ return @primary_id_key if @primary_id_key
86
+
87
+ # this method sets @primary_id_key as it loops through json_attributes
88
+ ident_keys
89
+ @primary_id_key
90
+ end
91
+
92
+ # create getters and setters for subclasses of JSONObject
93
+ # based on their JSON_ATTRIBUTES Hash.
94
+ #
95
+ # This method can't be private, cuz we want to call it from a
96
+ # Zeitwerk callback when subclasses are loaded.
97
+ ##############################
98
+ def self.parse_json_attributes
99
+ # nothing to do if JSON_ATTRIBUTES is not defined for this class
100
+ return unless defined? self::JSON_ATTRIBUTES
101
+
102
+ self::JSON_ATTRIBUTES.each do |attr_name, attr_def|
103
+ if attribute_already_parsed?(attr_name)
104
+ Windoo.load_msg "Ancestor of #{self} has already parsed attribute #{attr_name}"
105
+ next
106
+ end
107
+
108
+ Windoo.load_msg "Defining getters and setters for attribute '#{attr_name}' of #{self}"
109
+
110
+ # there can be only one (primary ident)
111
+ if attr_def[:identifier] == :primary
112
+ raise Windoo::UnsupportedError, 'Two identifiers marked as :primary' if @got_primary
113
+
114
+ @got_primary = true
115
+ end
116
+
117
+ # create getter unless the attr is write only
118
+ define_getters attr_name, attr_def unless attr_def[:writeonly]
119
+
120
+ # Don't crete setters for readonly attrs, or immutable objects
121
+ define_setters attr_name, attr_def if mutable? && !attr_def[:readonly]
122
+
123
+ json_attributes_parsed << attr_name
124
+ end # do |attr_name, attr_def|
125
+ end # parse_object_model
126
+
127
+ # have we already parsed our JSON_ATTRIBUTES? If so,
128
+ # we shoudn't do it again, and this can be used to check
129
+ def self.json_attributes_parsed
130
+ @json_attributes_parsed ||= []
131
+ end
132
+
133
+ # Used by auto-generated setters and .create to validate new values.
134
+ #
135
+ # returns a valid value or raises an exception
136
+ #
137
+ # This method only validates single values. When called from multi-value
138
+ # setters, it is used for each value individually.
139
+ #
140
+ # @param attr_name[Symbol], a top-level key from OAPI_PROPERTIES for this class
141
+ #
142
+ # @param value [Object] the value to validate for that attribute.
143
+ #
144
+ # @return [Object] The validated, possibly converted, value.
145
+ #
146
+ def self.validate_attr(attr_name, value)
147
+ attr_def = json_attributes[attr_name]
148
+ raise ArgumentError, "Unknown attribute: #{attr_name} for #{self} objects" unless attr_def
149
+
150
+ # validate the value based on the OAPI definition.
151
+ Windoo::Validate.json_attr value, attr_def: attr_def, attr_name: attr_name
152
+ end # validate_attr(attr_name, value)
153
+
154
+ # Private Class Methods
155
+ #####################################
156
+
157
+ # has one of our superclasses already parsed this attribute?
158
+ ##############################
159
+ def self.attribute_already_parsed?(attr_name)
160
+ superclass.respond_to?(:json_attributes_parsed) && superclass.json_attributes_parsed.include?(attr_name)
161
+ end
162
+
163
+ # create a getter for an attribute, and any aliases needed
164
+ ##############################
165
+ def self.define_getters(attr_name, attr_def)
166
+ Windoo.load_msg "..Defining getter method #{self}##{attr_name}"
167
+
168
+ define_method(attr_name) { instance_variable_get("@#{attr_name}") }
169
+
170
+ # all booleans get predicate ? aliases
171
+ alias_method("#{attr_name}?", attr_name) if attr_def[:class] == :Boolean
172
+ end # create getters
173
+ private_class_method :define_getters
174
+
175
+ # create setter for an attribute
176
+ ##############################
177
+ def self.define_setters(attr_name, attr_def)
178
+ # readonly values don't get setters
179
+ return if attr_def[:readonly]
180
+
181
+ Windoo.load_msg "..Defining setter method #{self}##{attr_name}="
182
+
183
+ define_method("#{attr_name}=") do |new_value|
184
+ new_value = validate_attr attr_name, new_value
185
+ old_value = instance_variable_get("@#{attr_name}")
186
+ return if new_value == old_value
187
+
188
+ # always update the server before the local
189
+ # value, so server errors will be raised first
190
+ # But only if this is a server object
191
+ # and
192
+ update_on_server(attr_name, new_value) if ok_to_send_to_server?(attr_name)
193
+
194
+ instance_variable_set("@#{attr_name}", new_value)
195
+ end # define method
196
+ end # define_setters
197
+ private_class_method :define_setters
198
+
199
+ ##############################
200
+ # def self.create_array_setters(attr_name, attr_def)
201
+ # create_full_array_setters(attr_name, attr_def)
202
+ # create_append_setters(attr_name, attr_def)
203
+ # create_prepend_setters(attr_name, attr_def)
204
+ # create_insert_setters(attr_name, attr_def)
205
+ # create_delete_setters(attr_name, attr_def)
206
+ # create_delete_at_setters(attr_name, attr_def)
207
+ # create_delete_if_setters(attr_name, attr_def)
208
+ # end # def create_multi_setters
209
+ # private_class_method :create_array_setters
210
+
211
+ # The attr=(newval) setter method for array values
212
+ # ##############################
213
+ # def self.create_full_array_setters(attr_name, attr_def)
214
+ # Windoo.load_msg "Creating multi-value setter method #{self}##{attr_name}="
215
+
216
+ # define_method("#{attr_name}=") do |new_value|
217
+ # initialize_multi_value_attr_array attr_name
218
+
219
+ # raise Windoo::InvalidDataError, "Value for '#{attr_name}=' must be an Array" unless new_value.is_a? Array
220
+
221
+ # # validate each item of the new array
222
+ # new_value.map! { |item| validate_attr attr_name, item }
223
+
224
+ # # now validate the array as a whole for oapi constraints
225
+ # Windoo::Validate.array_constraints(new_value, attr_def: attr_def, attr_name: attr_name)
226
+
227
+ # old_value = instance_variable_get("@#{attr_name}")
228
+ # return if new_value == old_value
229
+
230
+ # instance_variable_set("@#{attr_name}", new_value)
231
+ # end # define method
232
+
233
+ # return unless attr_def[:aliases]
234
+ # end # create_full_array_setter
235
+ # private_class_method :create_full_array_setters
236
+
237
+ # The attr_append(newval) setter method for array values
238
+ ##############################
239
+ # def self.create_append_setters(attr_name, attr_def)
240
+ # Windoo.load_msg "Creating multi-value setter method #{self}##{attr_name}_append"
241
+
242
+ # define_method("#{attr_name}_append") do |new_value|
243
+ # initialize_multi_value_attr_array attr_name
244
+
245
+ # new_value = validate_attr attr_name, new_value
246
+
247
+ # new_array = instance_variable_get("@#{attr_name}")
248
+ # new_array << new_value
249
+
250
+ # # now validate the array as a whole for oapi constraints
251
+ # Windoo::Validate.array_constraints(new_array, attr_def: attr_def, attr_name: attr_name)
252
+ # end # define method
253
+
254
+ # # always have a << alias
255
+ # alias_method "#{attr_name}<<", "#{attr_name}_append"
256
+ # end # create_append_setters
257
+ # private_class_method :create_append_setters
258
+
259
+ # The attr_prepend(newval) setter method for array values
260
+ ##############################
261
+ # def self.create_prepend_setters(attr_name, attr_def)
262
+ # Windoo.load_msg "Creating multi-value setter method #{self}##{attr_name}_prepend"
263
+
264
+ # define_method("#{attr_name}_prepend") do |new_value|
265
+ # initialize_multi_value_attr_array attr_name
266
+
267
+ # new_value = validate_attr attr_name, new_value
268
+
269
+ # new_array = instance_variable_get("@#{attr_name}")
270
+ # new_array.unshift new_value
271
+
272
+ # # now validate the array as a whole for oapi constraints
273
+ # Windoo::Validate.array_constraints(new_array, attr_def: attr_def, attr_name: attr_name)
274
+ # end # define method
275
+ # end # create_prepend_setters
276
+ # private_class_method :create_prepend_setters
277
+
278
+ # The attr_insert(index, newval) setter method for array values
279
+ # def self.create_insert_setters(attr_name, attr_def)
280
+ # Windoo.load_msg "Creating multi-value setter method #{self}##{attr_name}_insert"
281
+
282
+ # define_method("#{attr_name}_insert") do |index, new_value|
283
+ # initialize_multi_value_attr_array attr_name
284
+
285
+ # new_value = validate_attr attr_name, new_value
286
+
287
+ # new_array = instance_variable_get("@#{attr_name}")
288
+ # new_array.insert index, new_value
289
+
290
+ # # now validate the array as a whole for oapi constraints
291
+ # Windoo::Validate.array_constraints(new_array, attr_def: attr_def, attr_name: attr_name)
292
+ # end # define method
293
+ # end # create_insert_setters
294
+ # private_class_method :create_insert_setters
295
+
296
+ # The attr_delete(val) setter method for array values
297
+ ##############################
298
+ # def self.create_delete_setters(attr_name, attr_def)
299
+ # Windoo.load_msg "Creating multi-value setter method #{self}##{attr_name}_delete"
300
+
301
+ # define_method("#{attr_name}_delete") do |val|
302
+ # initialize_multi_value_attr_array attr_name
303
+
304
+ # new_array = instance_variable_get("@#{attr_name}")
305
+ # new_array.delete val
306
+ # return if old_array == new_array
307
+
308
+ # # now validate the array as a whole for oapi constraints
309
+ # Windoo::Validate.array_constraints(new_array, attr_def: attr_def, attr_name: attr_name)
310
+ # end # define method
311
+ # end # create_insert_setters
312
+ # private_class_method :create_delete_setters
313
+
314
+ # The attr_delete_at(index) setter method for array values
315
+ ##############################
316
+ # def self.create_delete_at_setters(attr_name, attr_def)
317
+ # Windoo.load_msg "Creating multi-value setter method #{self}##{attr_name}_delete_at"
318
+
319
+ # define_method("#{attr_name}_delete_at") do |index|
320
+ # initialize_multi_value_attr_array attr_name
321
+
322
+ # new_array = instance_variable_get("@#{attr_name}")
323
+ # deleted = new_array.delete_at index
324
+ # return unless deleted
325
+
326
+ # # now validate the array as a whole for oapi constraints
327
+ # Windoo::Validate.array_constraints(new_array, attr_def: attr_def, attr_name: attr_name)
328
+ # end # define method
329
+ # end # create_insert_setters
330
+ # private_class_method :create_delete_at_setters
331
+
332
+ # The attr_delete_if(block) setter method for array values
333
+ ##############################
334
+ # def self.create_delete_if_setters(attr_name, attr_def)
335
+ # Windoo.load_msg "Creating multi-value setter method #{self}##{attr_name}_delete_if"
336
+
337
+ # define_method("#{attr_name}_delete_if") do |&block|
338
+ # initialize_multi_value_attr_array attr_name
339
+
340
+ # new_array = instance_variable_get("@#{attr_name}")
341
+ # old_array = new_array.dup
342
+ # new_array.delete_if(&block)
343
+ # return if old_array == new_array
344
+
345
+ # # now validate the array as a whole for oapi constraints
346
+ # Windoo::Validate.array_constraints(new_array, attr_def: attr_def, attr_name: attr_name)
347
+ # end # define method
348
+ # end # create_insert_setters
349
+ # private_class_method :create_delete_if_setters
350
+
351
+ # Constructor
352
+ ######################
353
+ def initialize(**init_data)
354
+ @init_data = init_data
355
+ @init_data.each do |key, val|
356
+ next unless self.class.json_attributes.key? key
357
+
358
+ # The inst var is always a dup of the init_data, so that
359
+ # changes to it don't affect the init data.
360
+ ruby_val =
361
+ if val && self.class.json_attributes[key][:to_ruby]
362
+ Windoo::Converters.send self.class.json_attributes[key][:to_ruby], val.dup
363
+ else
364
+ val.dup
365
+ end
366
+
367
+ instance_variable_set "@#{key}", ruby_val
368
+ end
369
+ end
370
+
371
+ # Public Instance Methods
372
+ #####################
373
+
374
+ # @return [Hash] The data to be sent to the API, as a Hash
375
+ # to be converted to JSON before sending to the JPAPI
376
+ #
377
+ # This is currently only used when creating new objects in the API.
378
+ # Updates happen immediately per attribute, from the setter methods,
379
+ # sending only the new value to the server.
380
+ #
381
+ # It might also be used in the future to export JSON title
382
+ # definitions to import into other Title Servers.
383
+ #
384
+ def to_api
385
+ api_data = {}
386
+ attrs_for_save = self.class.json_attributes.keys
387
+
388
+ attrs_for_save.each do |attr_name|
389
+ attr_def = self.class.json_attributes[attr_name]
390
+ next if attr_def[:do_not_send]
391
+
392
+ raw_value = instance_variable_get "@#{attr_name}"
393
+
394
+ api_data[attr_name] =
395
+ if raw_value.nil?
396
+ nil
397
+ else
398
+ single_to_api(raw_value, attr_def)
399
+ end
400
+ end
401
+ api_data
402
+ end
403
+
404
+ # @return [String] the JSON to be sent to the API for this
405
+ # object
406
+ #
407
+ # This is only used when creating new objects in the API.
408
+ # Updates happen immediately from the setter methods,
409
+ # sending only the new value to the server.
410
+ #
411
+ def to_json(*_args)
412
+ JSON.pretty_generate to_api
413
+ end
414
+
415
+ # Only selected items are displayed with prettyprint
416
+ # otherwise its too much data in irb.
417
+ #
418
+ # @return [Array] the desired instance_variables
419
+ #
420
+ def pretty_print_instance_variables
421
+ instance_variables - PP_OMITTED_INST_VARS
422
+ end
423
+
424
+ # Private Instance Methods
425
+ #####################################
426
+ private
427
+
428
+ # should an attribute be sent to the server when running the setter?
429
+ # Only if its an APICollection, and the attribute is not 'do_not_send'
430
+ def ok_to_send_to_server?(attr_name)
431
+ self.class.ancestors.include?(Windoo::Mixins::APICollection) && \
432
+ !self.class.json_attributes.dig(attr_name, :do_not_send)
433
+ end
434
+
435
+ # Initialize a multi-values attribute as an empty array
436
+ # if it hasn't been created yet
437
+ def initialize_multi_value_attr_array(attr_name)
438
+ return if instance_variable_get("@#{attr_name}").is_a? Array
439
+
440
+ instance_variable_set("@#{attr_name}", [])
441
+ end
442
+
443
+ # wrapper for class method
444
+ def validate_attr(attr_name, value)
445
+ self.class.validate_attr attr_name, value
446
+ end
447
+
448
+ # call to_api on a single value if it knows that method
449
+ # or pass it to the converter module if needed
450
+ # otherwise, the value is good as is
451
+ def single_to_api(raw_value, attr_def)
452
+ if attr_def[:to_api]
453
+ Windoo::Converters.send attr_def[:to_api], raw_value.dup
454
+ elsif raw_value.respond_to?(:to_api)
455
+ raw_value.to_api
456
+ else
457
+ raw_value
458
+ end
459
+ end
460
+
461
+ # Call to_api on an array value
462
+ #
463
+ def multi_to_api(raw_array, attr_def)
464
+ raw_array ||= []
465
+ raw_array.map { |raw_value| single_to_api(raw_value, attr_def) }.compact
466
+ end
467
+
468
+ end # class JSONObject
469
+
470
+ end # module BaseClasses
471
+
472
+ end # module Windoo