spec/grape_entity/entity_spec.rb in grape-entity-0.6.1 vs spec/grape_entity/entity_spec.rb in grape-entity-0.7.0
- old
+ new
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'ostruct'
describe Grape::Entity do
let(:fresh_class) { Class.new(Grape::Entity) }
@@ -25,11 +27,13 @@
expect { subject.expose :name, :email, as: :foo }.to raise_error ArgumentError
expect { subject.expose :name, as: :foo }.not_to raise_error
end
it 'makes sure that :format_with as a proc cannot be used with a block' do
- expect { subject.expose :name, format_with: proc {} {} }.to raise_error ArgumentError
+ # rubocop:disable Style/BlockDelimiters
+ expect { subject.expose :name, format_with: proc {} do p 'hi' end }.to raise_error ArgumentError
+ # rubocop:enable Style/BlockDelimiters
end
it 'makes sure unknown options are not silently ignored' do
expect { subject.expose :name, unknown: nil }.to raise_error ArgumentError
end
@@ -62,10 +66,134 @@
expect(subject.represent(nested_hash).serializable_hash).to eq(special: { like_nested_hash: '12' })
end
end
end
+ context 'with :expose_nil option' do
+ let(:a) { nil }
+ let(:b) { nil }
+ let(:c) { 'value' }
+
+ context 'when model is a PORO' do
+ let(:model) { Model.new(a, b, c) }
+
+ before do
+ stub_const 'Model', Class.new
+ Model.class_eval do
+ attr_accessor :a, :b, :c
+
+ def initialize(a, b, c)
+ @a = a
+ @b = b
+ @c = c
+ end
+ end
+ end
+
+ context 'when expose_nil option is not provided' do
+ it 'exposes nil attributes' do
+ subject.expose(:a)
+ subject.expose(:b)
+ subject.expose(:c)
+ expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
+ end
+ end
+
+ context 'when expose_nil option is true' do
+ it 'exposes nil attributes' do
+ subject.expose(:a, expose_nil: true)
+ subject.expose(:b, expose_nil: true)
+ subject.expose(:c)
+ expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
+ end
+ end
+
+ context 'when expose_nil option is false' do
+ it 'does not expose nil attributes' do
+ subject.expose(:a, expose_nil: false)
+ subject.expose(:b, expose_nil: false)
+ subject.expose(:c)
+ expect(subject.represent(model).serializable_hash).to eq(c: 'value')
+ end
+
+ it 'is only applied per attribute' do
+ subject.expose(:a, expose_nil: false)
+ subject.expose(:b)
+ subject.expose(:c)
+ expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
+ end
+
+ it 'raises an error when applied to multiple attribute exposures' do
+ expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
+ end
+ end
+ end
+
+ context 'when model is a hash' do
+ let(:model) { { a: a, b: b, c: c } }
+
+ context 'when expose_nil option is not provided' do
+ it 'exposes nil attributes' do
+ subject.expose(:a)
+ subject.expose(:b)
+ subject.expose(:c)
+ expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
+ end
+ end
+
+ context 'when expose_nil option is true' do
+ it 'exposes nil attributes' do
+ subject.expose(:a, expose_nil: true)
+ subject.expose(:b, expose_nil: true)
+ subject.expose(:c)
+ expect(subject.represent(model).serializable_hash).to eq(a: nil, b: nil, c: 'value')
+ end
+ end
+
+ context 'when expose_nil option is false' do
+ it 'does not expose nil attributes' do
+ subject.expose(:a, expose_nil: false)
+ subject.expose(:b, expose_nil: false)
+ subject.expose(:c)
+ expect(subject.represent(model).serializable_hash).to eq(c: 'value')
+ end
+
+ it 'is only applied per attribute' do
+ subject.expose(:a, expose_nil: false)
+ subject.expose(:b)
+ subject.expose(:c)
+ expect(subject.represent(model).serializable_hash).to eq(b: nil, c: 'value')
+ end
+
+ it 'raises an error when applied to multiple attribute exposures' do
+ expect { subject.expose(:a, :b, :c, expose_nil: false) }.to raise_error ArgumentError
+ end
+ end
+ end
+
+ context 'with nested structures' do
+ let(:model) { { a: a, b: b, c: { d: nil, e: nil, f: { g: nil, h: nil } } } }
+
+ context 'when expose_nil option is false' do
+ it 'does not expose nil attributes' do
+ subject.expose(:a, expose_nil: false)
+ subject.expose(:b)
+ subject.expose(:c) do
+ subject.expose(:d, expose_nil: false)
+ subject.expose(:e)
+ subject.expose(:f) do
+ subject.expose(:g, expose_nil: false)
+ subject.expose(:h)
+ end
+ end
+
+ expect(subject.represent(model).serializable_hash).to eq(b: nil, c: { e: nil, f: { h: nil } })
+ end
+ end
+ end
+ end
+
context 'with a block' do
it 'errors out if called with multiple attributes' do
expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError
end
@@ -110,10 +238,34 @@
subject.expose(:size) { |_| self }
expect(subject.represent({}).value_for(:size)).to be_an_instance_of fresh_class
end
end
+ context 'with block passed via &' do
+ it 'with does not pass options when block is passed via &' do
+ class SomeObject
+ def method_without_args
+ 'result'
+ end
+ end
+
+ subject.expose :that_method_without_args do |object|
+ object.method_without_args
+ end
+
+ subject.expose :that_method_without_args_again, &:method_without_args
+
+ object = SomeObject.new
+
+ value = subject.represent(object).value_for(:that_method_without_args)
+ expect(value).to eq('result')
+
+ value2 = subject.represent(object).value_for(:that_method_without_args_again)
+ expect(value2).to eq('result')
+ end
+ end
+
context 'with no parameters passed to the block' do
it 'adds a nested exposure' do
subject.expose :awesome do
subject.expose :nested do
subject.expose :moar_nested, as: 'weee'
@@ -129,11 +281,11 @@
expect(awesome).to be_nesting
expect(nested).to_not be_nil
expect(another_nested).to_not be_nil
expect(another_nested.using_class_name).to eq('Awesome')
expect(moar_nested).to_not be_nil
- expect(moar_nested.key).to eq(:weee)
+ expect(moar_nested.key(subject)).to eq(:weee)
end
it 'represents the exposure as a hash of its nested.root_exposures' do
subject.expose :awesome do
subject.expose(:nested) { |_| 'value' }
@@ -322,10 +474,19 @@
end
expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'bar')
expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(email: nil, name: 'foo')
end
+
+ it 'overrides parent class exposure' do
+ subject.expose :name
+ child_class = Class.new(subject)
+ child_class.expose :name, as: :child_name
+
+ expect(subject.represent({ name: 'bar' }, serializable: true)).to eq(name: 'bar')
+ expect(child_class.represent({ name: 'bar' }, serializable: true)).to eq(child_name: 'bar')
+ end
end
context 'register formatters' do
let(:date_formatter) { ->(date) { date.strftime('%m/%d/%Y') } }
@@ -494,11 +655,11 @@
expose :awesome_thing, as: :extra_smooth
end
end
exposure = subject.find_exposure(:awesome_thing)
- expect(exposure.key).to eq :extra_smooth
+ expect(exposure.key(subject)).to eq :extra_smooth
end
it 'merges nested :if option' do
match_proc = ->(_obj, _opts) { true }
@@ -594,10 +755,38 @@
end
exposure = subject.find_exposure(:awesome_thing)
expect(exposure.documentation).to eq(desc: 'Other description.')
end
+
+ it 'propagates expose_nil option' do
+ subject.class_eval do
+ with_options(expose_nil: false) do
+ expose :awesome_thing
+ end
+ end
+
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.conditions[0].inversed?).to be true
+ expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
+ end
+
+ it 'overrides nested :expose_nil option' do
+ subject.class_eval do
+ with_options(expose_nil: true) do
+ expose :awesome_thing, expose_nil: false
+ expose :other_awesome_thing
+ end
+ end
+
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.conditions[0].inversed?).to be true
+ expect(exposure.conditions[0].block.call(awesome_thing: nil)).to be true
+ # Conditions are only added for exposures that do not expose nil
+ exposure = subject.find_exposure(:other_awesome_thing)
+ expect(exposure.conditions[0]).to be_nil
+ end
end
describe '.represent' do
it 'returns a single entity if called with one object' do
expect(subject.represent(Object.new)).to be_kind_of(subject)
@@ -651,11 +840,11 @@
end
context 'with specified fields' do
it 'returns only specified fields with only option' do
subject.expose(:id, :name, :phone)
- representation = subject.represent(OpenStruct.new, only: [:id, :name], serializable: true)
+ representation = subject.represent(OpenStruct.new, only: %i[id name], serializable: true)
expect(representation).to eq(id: nil, name: nil)
end
it 'returns all fields except the ones specified in the except option' do
subject.expose(:id, :name, :phone)
@@ -664,11 +853,11 @@
end
it 'returns only fields specified in the only option and not specified in the except option' do
subject.expose(:id, :name, :phone)
representation = subject.represent(OpenStruct.new,
- only: [:name, :phone],
+ only: %i[name phone],
except: [:phone], serializable: true)
expect(representation).to eq(name: nil)
end
context 'with strings or symbols passed to only and except' do
@@ -734,11 +923,11 @@
user_entity.expose(:id, :name, :email)
subject.expose(:id, :name, :phone)
subject.expose(:user, using: user_entity)
- representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: [:name, :email] }], serializable: true)
+ representation = subject.represent(OpenStruct.new(user: {}), only: [:id, :name, { user: %i[name email] }], serializable: true)
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
end
it 'can specify children attributes with except' do
user_entity = Class.new(Grape::Entity)
@@ -757,11 +946,11 @@
subject.expose(:id, :name, :phone, :mobile_phone)
subject.expose(:user, using: user_entity)
representation = subject.represent(OpenStruct.new(user: {}),
- only: [:id, :name, :phone, user: [:id, :name, :email]],
+ only: [:id, :name, :phone, user: %i[id name email]],
except: [:phone, { user: [:id] }], serializable: true)
expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
end
context 'specify attribute with exposure condition' do
@@ -769,21 +958,21 @@
subject.expose(:id)
subject.with_options(if: { condition: true }) do
subject.expose(:name)
end
- representation = subject.represent(OpenStruct.new, condition: true, only: [:id, :name], serializable: true)
+ representation = subject.represent(OpenStruct.new, condition: true, only: %i[id name], serializable: true)
expect(representation).to eq(id: nil, name: nil)
end
it 'does not return fields specified in the except option' do
subject.expose(:id, :phone)
subject.with_options(if: { condition: true }) do
subject.expose(:name, :mobile_phone)
end
- representation = subject.represent(OpenStruct.new, condition: true, except: [:phone, :mobile_phone], serializable: true)
+ representation = subject.represent(OpenStruct.new, condition: true, except: %i[phone mobile_phone], serializable: true)
expect(representation).to eq(id: nil, name: nil)
end
it 'choses proper exposure according to condition' do
strategy1 = ->(_obj, _opts) { 'foo' }
@@ -861,11 +1050,11 @@
context 'attribute with alias' do
it 'returns only specified fields' do
subject.expose(:id)
subject.expose(:name, as: :title)
- representation = subject.represent(OpenStruct.new, condition: true, only: [:id, :title], serializable: true)
+ representation = subject.represent(OpenStruct.new, condition: true, only: %i[id title], serializable: true)
expect(representation).to eq(id: nil, title: nil)
end
it 'does not return fields specified in the except option' do
subject.expose(:id)
@@ -888,11 +1077,11 @@
subject.expose(:id, :name, :phone)
subject.expose(:user, using: user_entity)
subject.expose(:nephew, using: nephew_entity)
representation = subject.represent(OpenStruct.new(user: {}),
- only: [:id, :name, :user], except: [:nephew], serializable: true)
+ only: %i[id name user], except: [:nephew], serializable: true)
expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil })
end
end
end
end
@@ -1339,12 +1528,12 @@
context 'with projections passed in options' do
it 'allows to pass different :only and :except params using the same instance' do
fresh_class.expose :a, :b, :c
presenter = fresh_class.new(a: 1, b: 2, c: 3)
- expect(presenter.serializable_hash(only: [:a, :b])).to eq(a: 1, b: 2)
- expect(presenter.serializable_hash(only: [:b, :c])).to eq(b: 2, c: 3)
+ expect(presenter.serializable_hash(only: %i[a b])).to eq(a: 1, b: 2)
+ expect(presenter.serializable_hash(only: %i[b c])).to eq(b: 2, c: 3)
end
end
end
describe '#inspect' do
@@ -1760,43 +1949,9 @@
it 'instantiates with options if provided' do
expect(instance.entity(awesome: true).options).to eq(awesome: true)
end
end
- end
- end
- end
-
- describe Grape::Entity::Options do
- module EntitySpec
- class Crystalline
- attr_accessor :prop1, :prop2
-
- def initialize
- @prop1 = 'value1'
- @prop2 = 'value2'
- end
- end
-
- class CrystallineEntity < Grape::Entity
- expose :prop1, if: ->(_, options) { options.fetch(:signal) }
- expose :prop2, if: ->(_, options) { options.fetch(:beam, 'destructive') == 'destructive' }
- end
- end
-
- context '#fetch' do
- it 'without passing in a required option raises KeyError' do
- expect { EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new).as_json }.to raise_error KeyError
- end
-
- it 'passing in a required option will expose the values' do
- crystalline_entity = EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new, signal: true)
- expect(crystalline_entity.as_json).to eq(prop1: 'value1', prop2: 'value2')
- end
-
- it 'with an option that is not default will not expose that value' do
- crystalline_entity = EntitySpec::CrystallineEntity.represent(EntitySpec::Crystalline.new, signal: true, beam: 'intermittent')
- expect(crystalline_entity.as_json).to eq(prop1: 'value1')
end
end
end
end
end