Class: TwoPercent::ScimGroup
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- TwoPercent::ScimGroup
- Defined in:
- app/models/two_percent/scim_group.rb
Class Method Summary collapse
- .destroy_by_scim_id(scim_id) ⇒ Object
- .exists_by_scim_id?(scim_id) ⇒ Boolean
- .find_by_scim_id(scim_id) ⇒ Object
-
.upsert_from_scim(resource_type, scim_hash, correlation_id: nil) ⇒ TwoPercent::ScimGroup
Creates or updates a group from SCIM data.
Instance Method Summary collapse
-
#members_for_patch ⇒ Array<Hash>
Build members array with value field only (for PatchProcessor) Uses pluck to avoid loading full AR objects.
-
#members_representation ⇒ Array<Hash>
Build SCIM members representation from join table Optimized to bypass ActiveRecord and load only needed columns.
- #replace_members(members_array) ⇒ Object
-
#scim_attribute(path) ⇒ Object?
Extracts a nested attribute from the scim_data JSON.
-
#to_domain_attributes ⇒ Hash
Extracts domain attributes for publishing in domain events.
-
#to_scim_representation ⇒ Hash
Returns full SCIM representation for HTTP responses.
- #update_from_scim!(resource_type, validated_data, correlation_id: nil) ⇒ Object
Class Method Details
.destroy_by_scim_id(scim_id) ⇒ Object
62 63 64 |
# File 'app/models/two_percent/scim_group.rb', line 62 def self.destroy_by_scim_id(scim_id) find_by_scim_id(scim_id)&.destroy end |
.exists_by_scim_id?(scim_id) ⇒ Boolean
58 59 60 |
# File 'app/models/two_percent/scim_group.rb', line 58 def self.exists_by_scim_id?(scim_id) exists?(scim_id: scim_id) end |
.find_by_scim_id(scim_id) ⇒ Object
54 55 56 |
# File 'app/models/two_percent/scim_group.rb', line 54 def self.find_by_scim_id(scim_id) find_by(scim_id: scim_id) end |
.upsert_from_scim(resource_type, scim_hash, correlation_id: nil) ⇒ TwoPercent::ScimGroup
Creates or updates a group from SCIM data
Generates a UUID for the id field if not present (for POST/create operations). Validates the SCIM data against the Group schema before persisting.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'app/models/two_percent/scim_group.rb', line 32 def self.upsert_from_scim(resource_type, scim_hash, correlation_id: nil) # Generate SCIM id (stored as scim_id) if not provided (typical for POST operations) scim_hash = scim_hash.dup scim_hash["id"] ||= SecureRandom.uuid # Extract members before validation to prevent storage in scim_data JSONB members = scim_hash.delete("members") validated_data = TwoPercent::Scim::Schema.validate_group(scim_hash, require_id: true) # Wrap in transaction to ensure rollback on member validation failure transaction do scim_group = find_or_initialize_by(scim_id: scim_hash["id"]) scim_group.update_from_scim!(resource_type, validated_data, correlation_id: correlation_id) # Sync members to join table only (never stored in scim_data) scim_group.replace_members(members) if members scim_group end end |
Instance Method Details
#members_for_patch ⇒ Array<Hash>
Build members array with value field only (for PatchProcessor) Uses pluck to avoid loading full AR objects
164 165 166 167 168 169 |
# File 'app/models/two_percent/scim_group.rb', line 164 def members_for_patch scim_group_memberships .joins(:scim_user) .pluck("two_percent_scim_users.scim_id") .map { |id| { "value" => id } } end |
#members_representation ⇒ Array<Hash>
Build SCIM members representation from join table Optimized to bypass ActiveRecord and load only needed columns
147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'app/models/two_percent/scim_group.rb', line 147 def members_representation scim_group_memberships .joins(:scim_user) .pluck("two_percent_scim_users.scim_id", "two_percent_scim_users.display_name") .map do |scim_id, display_name| { "value" => scim_id, "display" => display_name, "$ref" => "Users/#{scim_id}", } end end |
#replace_members(members_array) ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'app/models/two_percent/scim_group.rb', line 113 def replace_members(members_array) member_scim_ids = members_array.filter_map { |m| m["value"] } # Get current member scim_ids efficiently (just IDs, no full records) current_member_scim_ids = scim_group_memberships .joins(:scim_user) .pluck("two_percent_scim_users.scim_id") # Calculate diff in Ruby (cheap for ID arrays) scim_ids_to_add = member_scim_ids - current_member_scim_ids scim_ids_to_remove = current_member_scim_ids - member_scim_ids # Only validate and add NEW members (not existing ones) add_members_by_scim_id(scim_ids_to_add) if scim_ids_to_add.any? # Only remove members that need removing remove_members_by_scim_id(scim_ids_to_remove) if scim_ids_to_remove.any? end |
#scim_attribute(path) ⇒ Object?
Extracts a nested attribute from the scim_data JSON
138 139 140 141 |
# File 'app/models/two_percent/scim_group.rb', line 138 def scim_attribute(path) keys = path.split(".") scim_data.dig(*keys) end |
#to_domain_attributes ⇒ Hash
Extracts domain attributes for publishing in domain events
Returns key attributes for event payloads. Members are NOT included - consumers should query TwoPercent models directly for current state.
71 72 73 74 75 76 77 78 79 |
# File 'app/models/two_percent/scim_group.rb', line 71 def to_domain_attributes { scim_id: scim_id, external_id: external_id, display_name: display_name, resource_type: resource_type, active: active, }.compact end |
#to_scim_representation ⇒ Hash
Returns full SCIM representation for HTTP responses
84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'app/models/two_percent/scim_group.rb', line 84 def to_scim_representation representation = scim_data.merge( "id" => scim_id, "meta" => { "resourceType" => resource_type, "created" => created_at.iso8601, "lastModified" => updated_at.iso8601, } ) representation["members"] = members_representation if scim_users.loaded? representation end |
#update_from_scim!(resource_type, validated_data, correlation_id: nil) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'app/models/two_percent/scim_group.rb', line 98 def update_from_scim!(resource_type, validated_data, correlation_id: nil) core_data = validated_data[:core] self.scim_data = core_data.merge(validated_data[:extensions]) self.scim_id = core_data["id"] self.external_id = core_data["externalId"] self.display_name = core_data["displayName"] self.resource_type = resource_type extension_data = validated_data[:extensions] self.active = extension_data.dig("urn:ietf:params:scim:schemas:extension:authservice:2.0:Group", "active") != false self.correlation_id = correlation_id save! end |