Commit 8948ea8

mo <mo.khan@gmail.com>
2019-01-07 23:42:02
fix conflict with complex attribute named "value"
1 parent fb8826d
lib/scim/kit/v2/templates/attribute.json.jbuilder
@@ -1,12 +1,12 @@
 # frozen_string_literal: true
 
 json.key_format! camelize: :lower
-if type.complex?
+if type.complex? && !type.multi_valued
   json.set! type.name do
     dynamic_attributes.values.each do |attribute|
       render attribute, json: json
     end
   end
 else
-  json.set! type.name, value
+  json.set! type.name, _value
 end
lib/scim/kit/v2/attributable.rb
@@ -11,22 +11,28 @@ module Scim
           @dynamic_attributes = {}.with_indifferent_access
           types.each do |type|
             dynamic_attributes[type.name.underscore] = Attribute.new(type: type)
-            self.extend(create_module_for(type))
+            extend(create_module_for(type))
           end
         end
 
         private
 
+        def attribute_for(name)
+          dynamic_attributes[name]
+        end
+
         def create_module_for(type)
           name = type.name.underscore.to_sym
           Module.new do
-            define_method(name) do |*args, &block|
-              attribute = dynamic_attributes[name]
-              attribute.type.complex? ? attribute : attribute.value
+            define_method(name) do |*_args|
+              attribute = attribute_for(name)
+              return attribute._value if attribute.type.multi_valued
+
+              attribute.type.complex? ? attribute : attribute._value
             end
 
-            define_method("#{name}=") do |*args, &block|
-              dynamic_attributes[name].value = args[0]
+            define_method("#{name}=") do |*args|
+              attribute_for(name)._value = args[0]
             end
           end
         end
lib/scim/kit/v2/attribute.rb
@@ -8,16 +8,16 @@ module Scim
         include Attributable
         include Templatable
         attr_reader :type
-        attr_reader :value
+        attr_reader :_value
 
         def initialize(type:, value: nil)
           @type = type
-          @value = value
+          @_value = value
           define_attributes_for(type.attributes)
         end
 
-        def value=(new_value)
-          @value = type.coerce(new_value)
+        def _value=(new_value)
+          @_value = type.coerce(new_value)
 
           if type.canonical_values &&
              !type.canonical_values.empty? &&
lib/scim/kit/v2/attribute_type.rb
@@ -60,9 +60,9 @@ module Scim
         end
 
         def add_attribute(name:, type: :string)
-          @type = :complex
           attribute = AttributeType.new(name: name, type: type)
           yield attribute if block_given?
+          @type = :complex
           attributes << attribute
         end
 
@@ -83,6 +83,7 @@ module Scim
           if type_is?(:boolean) && ![true, false].include?(value)
             raise ArgumentError, value
           end
+          return value if multi_valued
 
           coercion = COERCION[type]
           coercion ? coercion.call(value) : value
lib/scim/kit/v2/schema.rb
@@ -20,7 +20,7 @@ module Scim
         def add_attribute(name:, type: :string)
           attribute = AttributeType.new(name: name, type: type)
           yield attribute if block_given?
-          @attributes << attribute
+          attributes << attribute
         end
 
         def self.build(*args)
spec/scim/kit/v2/attribute_spec.rb
@@ -9,41 +9,50 @@ RSpec.describe Scim::Kit::V2::Attribute do
     context 'when valid' do
       let(:user_name) { FFaker::Internet.user_name }
 
-      before { subject.value = user_name }
+      before { subject._value = user_name }
 
-      specify { expect(subject.value).to eql(user_name) }
+      specify { expect(subject._value).to eql(user_name) }
       specify { expect(subject.as_json[:userName]).to eql(user_name) }
     end
 
+    context 'when multiple values are allowed' do
+      before do
+        type.multi_valued = true
+        subject._value = %w[superman batman]
+      end
+
+      specify { expect(subject._value).to match_array(%w[superman batman]) }
+    end
+
     context 'when integer' do
       let(:number) { rand(100) }
 
-      before { subject.value = number }
+      before { subject._value = number }
 
-      specify { expect(subject.value).to eql(number.to_s) }
+      specify { expect(subject._value).to eql(number.to_s) }
     end
 
     context 'when datetime' do
       let(:datetime) { DateTime.now }
 
-      before { subject.value = datetime }
+      before { subject._value = datetime }
 
-      specify { expect(subject.value).to eql(datetime.to_s) }
+      specify { expect(subject._value).to eql(datetime.to_s) }
     end
 
     context 'when not matching a canonical value' do
       before { type.canonical_values = %w[batman robin] }
 
-      specify { expect { subject.value = 'spider man' }.to raise_error(ArgumentError) }
+      specify { expect { subject._value = 'spider man' }.to raise_error(ArgumentError) }
     end
 
     context 'when canonical value is given' do
       before do
         type.canonical_values = %w[batman robin]
-        subject.value = 'batman'
+        subject._value = 'batman'
       end
 
-      specify { expect(subject.value).to eql('batman') }
+      specify { expect(subject._value).to eql('batman') }
     end
   end
 
@@ -51,21 +60,21 @@ RSpec.describe Scim::Kit::V2::Attribute do
     let(:type) { Scim::Kit::V2::AttributeType.new(name: 'hungry', type: :boolean) }
 
     context 'when true' do
-      before { subject.value = true }
+      before { subject._value = true }
 
-      specify { expect(subject.value).to be(true) }
+      specify { expect(subject._value).to be(true) }
       specify { expect(subject.as_json[:hungry]).to be(true) }
     end
 
     context 'when false' do
-      before { subject.value = false }
+      before { subject._value = false }
 
-      specify { expect(subject.value).to be(false) }
+      specify { expect(subject._value).to be(false) }
       specify { expect(subject.as_json[:hungry]).to be(false) }
     end
 
     context 'when string' do
-      specify { expect { subject.value = 'hello' }.to raise_error(ArgumentError) }
+      specify { expect { subject._value = 'hello' }.to raise_error(ArgumentError) }
     end
   end
 
@@ -73,16 +82,16 @@ RSpec.describe Scim::Kit::V2::Attribute do
     let(:type) { Scim::Kit::V2::AttributeType.new(name: 'measurement', type: :decimal) }
 
     context 'when given float' do
-      before { subject.value = Math::PI }
+      before { subject._value = Math::PI }
 
-      specify { expect(subject.value).to eql(Math::PI) }
+      specify { expect(subject._value).to eql(Math::PI) }
       specify { expect(subject.as_json[:measurement]).to be(Math::PI) }
     end
 
     context 'when given an integer' do
-      before { subject.value = 42 }
+      before { subject._value = 42 }
 
-      specify { expect(subject.value).to eql(42.to_f) }
+      specify { expect(subject._value).to eql(42.to_f) }
       specify { expect(subject.as_json[:measurement]).to be(42.to_f) }
     end
   end
@@ -91,16 +100,16 @@ RSpec.describe Scim::Kit::V2::Attribute do
     let(:type) { Scim::Kit::V2::AttributeType.new(name: 'age', type: :integer) }
 
     context 'when given integer' do
-      before { subject.value = 34 }
+      before { subject._value = 34 }
 
-      specify { expect(subject.value).to be(34) }
+      specify { expect(subject._value).to be(34) }
       specify { expect(subject.as_json[:age]).to be(34) }
     end
 
     context 'when given float' do
-      before { subject.value = Math::PI }
+      before { subject._value = Math::PI }
 
-      specify { expect(subject.value).to eql(Math::PI.to_i) }
+      specify { expect(subject._value).to eql(Math::PI.to_i) }
     end
   end
 
@@ -109,16 +118,16 @@ RSpec.describe Scim::Kit::V2::Attribute do
     let(:datetime) { DateTime.new(2019, 0o1, 0o6, 12, 35, 0o0) }
 
     context 'when given a date time' do
-      before { subject.value = datetime }
+      before { subject._value = datetime }
 
-      specify { expect(subject.value).to eql(datetime) }
+      specify { expect(subject._value).to eql(datetime) }
       specify { expect(subject.as_json[:birthdate]).to eql(datetime.iso8601) }
     end
 
     context 'when given a string' do
-      before { subject.value = datetime.to_s }
+      before { subject._value = datetime.to_s }
 
-      specify { expect(subject.value).to eql(datetime) }
+      specify { expect(subject._value).to eql(datetime) }
     end
   end
 
@@ -127,9 +136,9 @@ RSpec.describe Scim::Kit::V2::Attribute do
     let(:photo) { IO.read('./spec/fixtures/avatar.png', mode: 'rb') }
 
     context 'when given a .png' do
-      before { subject.value = photo }
+      before { subject._value = photo }
 
-      specify { expect(subject.value).to eql(Base64.strict_encode64(photo)) }
+      specify { expect(subject._value).to eql(Base64.strict_encode64(photo)) }
       specify { expect(subject.as_json[:photo]).to eql(Base64.strict_encode64(photo)) }
     end
   end
@@ -138,9 +147,9 @@ RSpec.describe Scim::Kit::V2::Attribute do
     let(:type) { Scim::Kit::V2::AttributeType.new(name: 'group', type: :reference) }
     let(:uri) { FFaker::Internet.uri('https') }
 
-    before { subject.value = uri }
+    before { subject._value = uri }
 
-    specify { expect(subject.value).to eql(uri) }
+    specify { expect(subject._value).to eql(uri) }
     specify { expect(subject.as_json[:group]).to eql(uri) }
   end
 
@@ -163,7 +172,7 @@ RSpec.describe Scim::Kit::V2::Attribute do
     specify { expect(subject.as_json[:name][:givenName]).to eql('Tsuyoshi') }
   end
 
-  context "with multi valued complex type" do
+  context 'with multi valued complex type' do
     let(:type) do
       x = Scim::Kit::V2::AttributeType.new(name: 'emails', type: :complex)
       x.multi_valued = true
@@ -175,11 +184,13 @@ RSpec.describe Scim::Kit::V2::Attribute do
     let(:other_email) { FFaker::Internet.email }
 
     before do
-      subject.emails << { value: email, primary: true }
-      subject.emails << { value: other_email, primary: false }
+      subject._value = [
+        { value: email, primary: true },
+        { value: other_email, primary: false }
+      ]
     end
 
-    specify { expect(subject.emails).to match_array([{ value: email, primary: true }, { value: other_email, primary: false }]) }
+    specify { expect(subject._value).to match_array([{ value: email, primary: true }, { value: other_email, primary: false }]) }
     specify { expect(subject.as_json[:emails]).to match_array([{ value: email, primary: true }, { value: other_email, primary: false }]) }
   end
 end
spec/scim/kit/v2/resource_spec.rb
@@ -70,18 +70,20 @@ RSpec.describe Scim::Kit::V2::Resource do
     end
   end
 
-  context "with a complex multi valued attribute" do
+  context 'with a complex multi valued attribute' do
     let(:email) { FFaker::Internet.email }
     let(:other_email) { FFaker::Internet.email }
 
     before do
-      schema.add_attribute(name: 'emails') do |x|
+      schema.add_attribute(name: 'emails', type: :complex) do |x|
         x.multi_valued = true
         x.add_attribute(name: 'value')
         x.add_attribute(name: 'primary', type: :boolean)
       end
-      subject.emails << { value: email, primary: true }
-      subject.emails << { value: other_email, primary: false }
+      subject.emails = [
+        { value: email, primary: true },
+        { value: other_email, primary: false }
+      ]
     end
 
     specify { expect(subject.emails).to match_array([{ value: email, primary: true }, { value: other_email, primary: false }]) }