scimitar 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/models/scimitar/resources/base.rb +12 -9
- data/app/models/scimitar/resources/mixin.rb +403 -33
- data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +140 -10
- data/lib/scimitar/version.rb +2 -2
- data/spec/models/scimitar/resources/base_spec.rb +9 -1
- data/spec/models/scimitar/resources/mixin_spec.rb +683 -112
- data/spec/requests/active_record_backed_resources_controller_spec.rb +81 -5
- data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +108 -0
- metadata +2 -2
@@ -23,8 +23,8 @@ class Hash
|
|
23
23
|
#
|
24
24
|
def self.deep_indifferent_case_insensitive_access(object)
|
25
25
|
if object.is_a?(Hash)
|
26
|
-
new_hash = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new
|
27
|
-
|
26
|
+
new_hash = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new
|
27
|
+
object.each do | key, value |
|
28
28
|
new_hash[key] = deep_indifferent_case_insensitive_access(value)
|
29
29
|
end
|
30
30
|
new_hash
|
@@ -49,34 +49,164 @@ module Scimitar
|
|
49
49
|
# in a case-insensitive fashion too.
|
50
50
|
#
|
51
51
|
# During enumeration, Hash keys will always be returned in whatever case
|
52
|
-
# they were originally set.
|
52
|
+
# they were originally set. Just as with
|
53
|
+
# ActiveSupport::HashWithIndifferentAccess, though, the type of the keys is
|
54
|
+
# always returned as a String, even if originally set as a Symbol - only
|
55
|
+
# the upper/lower case nature of the original key is preserved.
|
56
|
+
#
|
57
|
+
# If a key is written more than once with the same effective meaning in a
|
58
|
+
# to-string, to-downcase form, then whatever case was used *first* wins;
|
59
|
+
# e.g. if you did hash['User'] = 23, then hash['USER'] = 42, the result
|
60
|
+
# would be {"User" => 42}.
|
61
|
+
#
|
62
|
+
# It's important to remember that Hash#merge is shallow and replaces values
|
63
|
+
# found at existing keys in the target ("this") hash with values in the
|
64
|
+
# inbound Hash. If that new value that is itself a Hash, this *replaces*
|
65
|
+
# the value. For example:
|
66
|
+
#
|
67
|
+
# * Original: <tt>'Foo' => { 'Bar' => 42 }</tt>
|
68
|
+
# * Merge: <tt>'FOO' => { 'BAR' => 24 }</tt>
|
69
|
+
#
|
70
|
+
# ...results in "this" target hash's key +Foo+ being addressed in the merge
|
71
|
+
# by inbound key +FOO+, so the case doesn't change. But the value for +Foo+
|
72
|
+
# is _replaced_ by the merging-in Hash completely:
|
73
|
+
#
|
74
|
+
# * Result: <tt>'Foo' => { 'BAR' => 24 }</tt>
|
75
|
+
#
|
76
|
+
# ...and of course we might've replaced with a totally different type, such
|
77
|
+
# as +true+:
|
78
|
+
#
|
79
|
+
# * Original: <tt>'Foo' => { 'Bar' => 42 }</tt>
|
80
|
+
# * Merge: <tt>'FOO' => true</tt>
|
81
|
+
# * Result: <tt>'Foo' => true</tt>
|
82
|
+
#
|
83
|
+
# If you're intending to merge nested Hashes, then use ActiveSupport's
|
84
|
+
# #deep_merge or an equivalent. This will have the expected outcome, where
|
85
|
+
# the hash with 'BAR' is _merged_ into the existing value and, therefore,
|
86
|
+
# the original 'Bar' key case is preserved:
|
87
|
+
#
|
88
|
+
# * Original: <tt>'Foo' => { 'Bar' => 42 }</tt>
|
89
|
+
# * Deep merge: <tt>'FOO' => { 'BAR' => 24 }</tt>
|
90
|
+
# * Result: <tt>'Foo' => { 'Bar' => 24 }</tt>
|
53
91
|
#
|
54
92
|
class HashWithIndifferentCaseInsensitiveAccess < ActiveSupport::HashWithIndifferentAccess
|
55
93
|
def with_indifferent_case_insensitive_access
|
56
94
|
self
|
57
95
|
end
|
58
96
|
|
97
|
+
def initialize(constructor = nil)
|
98
|
+
@scimitar_hash_with_indifferent_case_insensitive_access_key_map = {}
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
# It's vital that the attribute map is carried over when one of these
|
103
|
+
# objects is duplicated. Duplication of this ivar state does *not* happen
|
104
|
+
# when 'dup' is called on our superclass, so we have to do that manually.
|
105
|
+
#
|
106
|
+
def dup
|
107
|
+
duplicate = super
|
108
|
+
duplicate.instance_variable_set(
|
109
|
+
'@scimitar_hash_with_indifferent_case_insensitive_access_key_map',
|
110
|
+
@scimitar_hash_with_indifferent_case_insensitive_access_key_map
|
111
|
+
)
|
112
|
+
|
113
|
+
return duplicate
|
114
|
+
end
|
115
|
+
|
116
|
+
# Override the individual key writer.
|
117
|
+
#
|
118
|
+
def []=(key, value)
|
119
|
+
string_key = scimitar_hash_with_indifferent_case_insensitive_access_string(key)
|
120
|
+
indifferent_key = scimitar_hash_with_indifferent_case_insensitive_access_downcase(string_key)
|
121
|
+
converted_value = convert_value(value, conversion: :assignment)
|
122
|
+
|
123
|
+
# Note '||=', as there might have been a prior use of the "same" key in
|
124
|
+
# a different case. The earliest one is preserved since the actual Hash
|
125
|
+
# underneath all this is already using that variant of the key.
|
126
|
+
#
|
127
|
+
key_for_writing = (
|
128
|
+
@scimitar_hash_with_indifferent_case_insensitive_access_key_map[indifferent_key] ||= string_key
|
129
|
+
)
|
130
|
+
|
131
|
+
regular_writer(key_for_writing, converted_value)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Override #merge to express it in terms of #merge! (also overridden), so
|
135
|
+
# that merged hashes can have their keys treated indifferently too.
|
136
|
+
#
|
137
|
+
def merge(*other_hashes, &block)
|
138
|
+
dup.merge!(*other_hashes, &block)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Modifies-self version of #merge, overriding Hash#merge!.
|
142
|
+
#
|
143
|
+
def merge!(*hashes_to_merge_to_self, &block)
|
144
|
+
if block_given?
|
145
|
+
hashes_to_merge_to_self.each do |hash_to_merge_to_self|
|
146
|
+
hash_to_merge_to_self.each_pair do |key, value|
|
147
|
+
value = block.call(key, self[key], value) if self.key?(key)
|
148
|
+
self[key] = value
|
149
|
+
end
|
150
|
+
end
|
151
|
+
else
|
152
|
+
hashes_to_merge_to_self.each do |hash_to_merge_to_self|
|
153
|
+
hash_to_merge_to_self.each_pair do |key, value|
|
154
|
+
self[key] = value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
self
|
160
|
+
end
|
161
|
+
|
162
|
+
# =======================================================================
|
163
|
+
# PRIVATE INSTANCE METHODS
|
164
|
+
# =======================================================================
|
165
|
+
#
|
59
166
|
private
|
60
167
|
|
61
168
|
if Symbol.method_defined?(:name)
|
62
|
-
def
|
63
|
-
key.kind_of?(Symbol) ? key.name
|
169
|
+
def scimitar_hash_with_indifferent_case_insensitive_access_string(key)
|
170
|
+
key.kind_of?(Symbol) ? key.name : key
|
64
171
|
end
|
65
172
|
else
|
66
|
-
def
|
67
|
-
key.kind_of?(Symbol) ? key.to_s
|
173
|
+
def scimitar_hash_with_indifferent_case_insensitive_access_string(key)
|
174
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def scimitar_hash_with_indifferent_case_insensitive_access_downcase(key)
|
179
|
+
key.kind_of?(String) ? key.downcase : key
|
180
|
+
end
|
181
|
+
|
182
|
+
def convert_key(key)
|
183
|
+
string_key = scimitar_hash_with_indifferent_case_insensitive_access_string(key)
|
184
|
+
indifferent_key = scimitar_hash_with_indifferent_case_insensitive_access_downcase(string_key)
|
185
|
+
|
186
|
+
@scimitar_hash_with_indifferent_case_insensitive_access_key_map[indifferent_key] || string_key
|
187
|
+
end
|
188
|
+
|
189
|
+
def convert_value(value, conversion: nil)
|
190
|
+
if value.is_a?(Hash)
|
191
|
+
if conversion == :to_hash
|
192
|
+
value.to_hash
|
193
|
+
else
|
194
|
+
value.with_indifferent_case_insensitive_access
|
195
|
+
end
|
196
|
+
else
|
197
|
+
super
|
68
198
|
end
|
69
199
|
end
|
70
200
|
|
71
201
|
def update_with_single_argument(other_hash, block)
|
72
|
-
if other_hash.is_a?
|
202
|
+
if other_hash.is_a?(HashWithIndifferentCaseInsensitiveAccess)
|
73
203
|
regular_update(other_hash, &block)
|
74
204
|
else
|
75
205
|
other_hash.to_hash.each_pair do |key, value|
|
76
206
|
if block && key?(key)
|
77
|
-
value = block.call(convert_key(key), self[key], value)
|
207
|
+
value = block.call(self.convert_key(key), self[key], value)
|
78
208
|
end
|
79
|
-
|
209
|
+
self.[]=(key, value)
|
80
210
|
end
|
81
211
|
end
|
82
212
|
end
|
data/lib/scimitar/version.rb
CHANGED
@@ -3,11 +3,11 @@ module Scimitar
|
|
3
3
|
# Gem version. If this changes, be sure to re-run "bundle install" or
|
4
4
|
# "bundle update".
|
5
5
|
#
|
6
|
-
VERSION = '1.8.
|
6
|
+
VERSION = '1.8.2'
|
7
7
|
|
8
8
|
# Date for VERSION. If this changes, be sure to re-run "bundle install"
|
9
9
|
# or "bundle update".
|
10
10
|
#
|
11
|
-
DATE = '2024-
|
11
|
+
DATE = '2024-03-27'
|
12
12
|
|
13
13
|
end
|
@@ -16,7 +16,7 @@ RSpec.describe Scimitar::Resources::Base do
|
|
16
16
|
name: 'names', multiValued: true, complexType: Scimitar::ComplexTypes::Name, required: false
|
17
17
|
),
|
18
18
|
Scimitar::Schema::Attribute.new(
|
19
|
-
name: 'privateName', complexType: Scimitar::ComplexTypes::Name, required: false, returned:
|
19
|
+
name: 'privateName', complexType: Scimitar::ComplexTypes::Name, required: false, returned: 'never'
|
20
20
|
),
|
21
21
|
]
|
22
22
|
end
|
@@ -27,6 +27,14 @@ RSpec.describe Scimitar::Resources::Base do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
context '#initialize' do
|
30
|
+
it 'accepts nil for non-required attributes' do
|
31
|
+
resource = CustomResourse.new(name: nil, names: nil, privateName: nil)
|
32
|
+
|
33
|
+
expect(resource.name).to be_nil
|
34
|
+
expect(resource.names).to be_nil
|
35
|
+
expect(resource.privateName).to be_nil
|
36
|
+
end
|
37
|
+
|
30
38
|
shared_examples 'an initializer' do | force_upper_case: |
|
31
39
|
it 'which builds the nested type' do
|
32
40
|
attributes = {
|