Class: Scimitar::Resources::Base

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Model, Errors, Schema::DerivedAttributes
Defined in:
app/models/scimitar/resources/base.rb

Overview

The base class for all SCIM resources.

Direct Known Subclasses

Group, User

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Errors

#add_errors_from_hash

Constructor Details

#initialize(options = {}) ⇒ Base

Returns a new instance of Base.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'app/models/scimitar/resources/base.rb', line 13

def initialize(options = {})
  flattened_attributes = flatten_extension_attributes(options)
  ci_all_attributes    = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new
  camel_attributes     = {}

  # Create a map where values are the schema-correct-case attribute names
  # and the values are set the same, but since the ci_all_attributes data
  # type is HashWithIndifferentCaseInsensitiveAccess, lookups in this are
  # case insensitive. Thus, arbitrary case input data can be mapped to
  # the case correctness required for ActiveModel's attribute accessors.
  #
  self.class.all_attributes.each { |attr| ci_all_attributes[attr] = attr }

  flattened_attributes.each do | key, value |
    if ci_all_attributes.key?(key)
      camel_attributes[ci_all_attributes[key]] = value
    end
  end

  super(camel_attributes)
  constantize_complex_types(camel_attributes)

  @errors = ActiveModel::Errors.new(self)
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



10
11
12
# File 'app/models/scimitar/resources/base.rb', line 10

def errors
  @errors
end

#externalIdObject

Returns the value of attribute externalId.



9
10
11
# File 'app/models/scimitar/resources/base.rb', line 9

def externalId
  @externalId
end

#idObject

Returns the value of attribute id.



9
10
11
# File 'app/models/scimitar/resources/base.rb', line 9

def id
  @id
end

#metaObject

Returns the value of attribute meta.



9
10
11
# File 'app/models/scimitar/resources/base.rb', line 9

def meta
  @meta
end

Class Method Details

.all_attributesObject



90
91
92
93
# File 'app/models/scimitar/resources/base.rb', line 90

def self.all_attributes
  scim_attributes = schemas.map(&:scim_attributes).flatten.map(&:name)
  scim_attributes + [:id, :externalId, :meta]
end

.complex_scim_attributesObject



114
115
116
# File 'app/models/scimitar/resources/base.rb', line 114

def self.complex_scim_attributes
  schemas.flat_map(&:scim_attributes).select(&:complexType).group_by(&:name)
end

.extend_schema(schema) ⇒ Object

Can be used to extend an existing resource type’s schema. For example:

module Scim
  module Schema
    class MyExtension < Scimitar::Schema::Base

      def initialize(options = {})
        super(name: 'ExtendedGroup',
              id: self.class.id,
              description: 'Represents extra info about a group',
              scim_attributes: self.class.scim_attributes)
      end

      def self.id
        'urn:ietf:params:scim:schemas:extension:extendedgroup:2.0:Group'
      end

      def self.scim_attributes
        [Scimitar::Schema::Attribute.new(name: 'someAddedAttribute',
                       type: 'string',
                       required: true,
                       canonicalValues: ['FOO', 'BAR'])]
      end
    end
  end
end

Scimitar::Resources::Group.extend_schema Scim::Schema::MyExtension


77
78
79
80
# File 'app/models/scimitar/resources/base.rb', line 77

def self.extend_schema(schema)
  derive_attributes_from_schema(schema)
  extended_schemas << schema
end

.extended_schemasObject



82
83
84
# File 'app/models/scimitar/resources/base.rb', line 82

def self.extended_schemas
  @extended_schemas ||= []
end

.find_attribute(*path) ⇒ Object

Calls to Scimitar::Schema::Base::find_attribute for each of the schemas in ::schemas, in order returned (so main schema would be first, then any extended schemas searched next). Returns the first match found, or nil.

See Scimitar::Schema::Base::find_attribute for details on parameters, more about the return value and other general information.



103
104
105
106
107
108
109
110
111
112
# File 'app/models/scimitar/resources/base.rb', line 103

def self.find_attribute(*path)
  found_attribute = nil

  self.schemas.each do | schema |
    found_attribute = schema.find_attribute(*path)
    break unless found_attribute.nil?
  end

  return found_attribute
end

.resource_type(location) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
# File 'app/models/scimitar/resources/base.rb', line 169

def self.resource_type(location)
  resource_type = ResourceType.new(
    endpoint: endpoint,
    schema: schema.id,
    id: resource_type_id,
    name: resource_type_id,
    schemaExtensions: extended_schemas.map(&:id)
  )

  resource_type.meta.location = location
  resource_type
end

.resource_type_idObject



165
166
167
# File 'app/models/scimitar/resources/base.rb', line 165

def self.resource_type_id
  name.demodulize
end

.schemasObject



86
87
88
# File 'app/models/scimitar/resources/base.rb', line 86

def self.schemas
  ([schema] + extended_schemas).flatten
end

Instance Method Details

#as_json(options = {}) ⇒ Object

Renders *in full* as JSON; typically used for write-based operations…

record = self.storage_class().new
record.from_scim!(scim_hash: scim_resource.as_json())
self.save!(record)

…so all fields, even those marked “returned: false”, are included. Use Scimitar::Resources::Mixin::to_scim to obtain a SCIM object with non-returnable fields omitted, rendering that as JSON via #to_json.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'app/models/scimitar/resources/base.rb', line 150

def as_json(options = {})
  self.meta = Meta.new unless self.meta && self.meta.is_a?(Meta)
  self.meta.resourceType = self.class.resource_type_id

  original_hash = super(options).except('errors')
  original_hash.merge!('schemas' => self.class.schemas.map(&:id))

  self.class.extended_schemas.each do |extension_schema|
    extension_attributes = extension_schema.scim_attributes.map(&:name)
    original_hash.merge!(extension_schema.id => original_hash.extract!(*extension_attributes))
  end

  original_hash
end

#complex_type_from_hash(scim_attribute, attr_value) ⇒ Object



118
119
120
121
122
123
124
# File 'app/models/scimitar/resources/base.rb', line 118

def complex_type_from_hash(scim_attribute, attr_value)
  if attr_value.is_a?(Hash)
    scim_attribute.complexType.new(attr_value)
  else
    attr_value
  end
end

#constantize_complex_types(hash) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'app/models/scimitar/resources/base.rb', line 126

def constantize_complex_types(hash)
  hash.with_indifferent_access.each_pair do |attr_name, attr_value|
    scim_attribute = self.class.complex_scim_attributes[attr_name].try(:first)

    if scim_attribute && scim_attribute.complexType
      if scim_attribute.multiValued
        self.send("#{attr_name}=", attr_value&.map {|attr_for_each_item| complex_type_from_hash(scim_attribute, attr_for_each_item)})
      else
        self.send("#{attr_name}=", complex_type_from_hash(scim_attribute, attr_value))
      end
    end
  end
end

#flatten_extension_attributes(options) ⇒ Object



38
39
40
41
42
43
44
45
46
# File 'app/models/scimitar/resources/base.rb', line 38

def flatten_extension_attributes(options)
  flattened = options.dup
  self.class.extended_schemas.each do |extended_schema|
    if extension_attrs = flattened.delete(extended_schema.id)
      flattened.merge!(extension_attrs)
    end
  end
  flattened
end

#validate_resourceObject



182
183
184
185
186
187
# File 'app/models/scimitar/resources/base.rb', line 182

def validate_resource
  self.class.schema.valid?(self)
  self.class.extended_schemas.each do |extended_schema|
    extended_schema.valid?(self)
  end
end