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