summaryrefslogtreecommitdiff
path: root/lib/scim/kit/v2/attributable.rb
blob: d8145b6f478b1f024f9f02781aea2dcb36901424 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# frozen_string_literal: true

module Scim
  module Kit
    module V2
      # Represents a dynamic attribute that corresponds to a SCIM type
      module Attributable
        include Enumerable

        # Returns a hash of the generated dynamic attributes
        # @return [Hash] the dynamic attributes keys by their name
        def dynamic_attributes
          @dynamic_attributes ||= {}.with_indifferent_access
        end

        # Defines dynamic attributes on the resource for the types provided
        # @param resource [Scim::Kit::V2::Resource] the resource to attach dynamic attributes to.
        # @param types [Array<Scim::Kit::V2::AttributeType>] the array of types
        def define_attributes_for(resource, types)
          types.each { |x| attribute(x, resource) }
        end

        # Assigns attribute values via the provided hash.
        # @param attributes [Hash] The name/values to assign.
        def assign_attributes(attributes = {})
          attributes.each do |key, value|
            next if key.to_sym == :schemas

            if key.to_s.start_with?(Schemas::EXTENSION)
              assign_attributes(value)
            else
              write_attribute(key, value)
            end
          end
        end

        # Returns the attribute identified by the name.
        # @param name [String] the name of the attribute to return
        # @return [Scim::Kit::V2::Attribute] the attribute or {Scim::Kit::V2::UnknownAttribute}
        def attribute_for(name)
          dynamic_attributes[name.to_s.underscore] ||
            dynamic_attributes[name] ||
            UnknownAttribute.new(name)
        end

        # Returns the value associated with the attribute name
        # @param name [String] the name of the attribute
        # @return [Object] the value assigned to the attribute
        def read_attribute(name)
          attribute = attribute_for(name)
          return attribute._value if attribute._type.multi_valued

          attribute._type.complex? ? attribute : attribute._value
        end

        # Assigns the value to the attribute with the given name
        # @param name [String] the name of the attribute
        # @param value [Object] the value to assign to the attribute
        def write_attribute(name, value)
          if value.is_a?(Hash)
            attribute_for(name)&.assign_attributes(value)
          else
            attribute_for(name)._value = value
          end
        end

        # yields each attribute to the provided block
        # @param [Block] the block to yield each attribute to.
        def each(&block)
          dynamic_attributes.each_value(&block)
        end

        private

        def create_module_for(type)
          name = type.name.to_sym
          Module.new do
            define_method(name) do |*_args|
              read_attribute(name)
            end

            define_method("#{name}=") do |*args|
              write_attribute(name, args[0])
            end
          end
        end

        def attribute(type, resource)
          previously_defined = dynamic_attributes.key?(type.name)
          dynamic_attributes[previously_defined ? type.fully_qualified_name : type.name] =
            Attribute.new(type: type, resource: resource)
          extend(create_module_for(type)) unless previously_defined
        end
      end
    end
  end
end