main
1# frozen_string_literal: true
2
3module Scim
4 module Kit
5 module V2
6 # Represents a dynamic attribute that corresponds to a SCIM type
7 module Attributable
8 include Enumerable
9
10 # Returns a hash of the generated dynamic attributes
11 # @return [Hash] the dynamic attributes keys by their name
12 def dynamic_attributes
13 @dynamic_attributes ||= {}.with_indifferent_access
14 end
15
16 # Defines dynamic attributes on the resource for the types provided
17 # @param resource [Scim::Kit::V2::Resource] the resource to attach dynamic attributes to.
18 # @param types [Array<Scim::Kit::V2::AttributeType>] the array of types
19 def define_attributes_for(resource, types)
20 types.each { |x| attribute(x, resource) }
21 end
22
23 # Assigns attribute values via the provided hash.
24 # @param attributes [Hash] The name/values to assign.
25 def assign_attributes(attributes = {})
26 attributes.each do |key, value|
27 next if key.to_sym == :schemas
28
29 if key.to_s.start_with?(Schemas::EXTENSION)
30 assign_attributes(value)
31 else
32 write_attribute(key, value)
33 end
34 end
35 end
36
37 # Returns the attribute identified by the name.
38 # @param name [String] the name of the attribute to return
39 # @return [Scim::Kit::V2::Attribute] the attribute or {Scim::Kit::V2::UnknownAttribute}
40 def attribute_for(name)
41 dynamic_attributes[name.to_s.underscore] ||
42 dynamic_attributes[name] ||
43 UnknownAttribute.new(name)
44 end
45
46 # Returns the value associated with the attribute name
47 # @param name [String] the name of the attribute
48 # @return [Object] the value assigned to the attribute
49 def read_attribute(name)
50 attribute = attribute_for(name)
51 return attribute._value if attribute._type.multi_valued
52
53 attribute._type.complex? ? attribute : attribute._value
54 end
55
56 # Assigns the value to the attribute with the given name
57 # @param name [String] the name of the attribute
58 # @param value [Object] the value to assign to the attribute
59 def write_attribute(name, value)
60 if value.is_a?(Hash)
61 attribute_for(name)&.assign_attributes(value)
62 else
63 attribute_for(name)._value = value
64 end
65 end
66
67 # yields each attribute to the provided block
68 # @param [Block] the block to yield each attribute to.
69 def each(&block)
70 dynamic_attributes.each_value(&block)
71 end
72
73 private
74
75 def create_module_for(type)
76 name = type.name.to_sym
77 Module.new do
78 define_method(name) do |*_args|
79 read_attribute(name)
80 end
81
82 define_method("#{name}=") do |*args|
83 write_attribute(name, args[0])
84 end
85 end
86 end
87
88 def attribute(type, resource)
89 previously_defined = dynamic_attributes.key?(type.name)
90 dynamic_attributes[previously_defined ? type.fully_qualified_name : type.name] =
91 Attribute.new(type: type, resource: resource)
92 extend(create_module_for(type)) unless previously_defined
93 end
94 end
95 end
96 end
97end