scimitar 2.7.1 → 2.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -2
- 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 = '2.7.
|
6
|
+
VERSION = '2.7.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 = {
|