spec/grape_entity/entity_spec.rb in grape-entity-0.4.8 vs spec/grape_entity/entity_spec.rb in grape-entity-0.5.0
- old
+ new
@@ -9,16 +9,16 @@
describe '.expose' do
context 'multiple attributes' do
it 'is able to add multiple exposed attributes with a single call' do
subject.expose :name, :email, :location
- expect(subject.exposures.size).to eq 3
+ expect(subject.root_exposures.size).to eq 3
end
- it 'sets the same options for all exposures passed' do
+ it 'sets the same options for all.root_exposures passed' do
subject.expose :name, :email, :location, documentation: true
- subject.exposures.values.each { |v| expect(v).to eq(documentation: true) }
+ subject.root_exposures.each { |v| expect(v.documentation).to eq true }
end
end
context 'option validation' do
it 'makes sure that :as only works on single attribute calls' do
@@ -59,27 +59,29 @@
entity.prop1 = 'MODIFIED 2'
entity
end
object = EntitySpec::SomeObject1.new
- value = subject.represent(object).send(:value_for, :bogus)
+ value = subject.represent(object).value_for(:bogus)
expect(value).to be_instance_of EntitySpec::BogusEntity
- prop1 = value.send(:value_for, :prop1)
+ prop1 = value.value_for(:prop1)
expect(prop1).to eq 'MODIFIED 2'
end
context 'with parameters passed to the block' do
it 'sets the :proc option in the exposure options' do
block = ->(_) { true }
subject.expose :name, using: 'Awesome', &block
- expect(subject.exposures[:name]).to eq(proc: block, using: 'Awesome')
+ exposure = subject.find_exposure(:name)
+ expect(exposure.subexposure.block).to eq(block)
+ expect(exposure.using_class_name).to eq('Awesome')
end
it 'references an instance of the entity without any options' do
subject.expose(:size) { |_| self }
- expect(subject.represent({}).send(:value_for, :size)).to be_an_instance_of fresh_class
+ expect(subject.represent({}).value_for(:size)).to be_an_instance_of fresh_class
end
end
context 'with no parameters passed to the block' do
it 'adds a nested exposure' do
@@ -88,37 +90,42 @@
subject.expose :moar_nested, as: 'weee'
end
subject.expose :another_nested, using: 'Awesome'
end
- expect(subject.exposures).to eq(
- awesome: {},
- awesome__nested: { nested: true },
- awesome__nested__moar_nested: { as: 'weee', nested: true },
- awesome__another_nested: { using: 'Awesome', nested: true }
- )
+ awesome = subject.find_exposure(:awesome)
+ nested = awesome.find_nested_exposure(:nested)
+ another_nested = awesome.find_nested_exposure(:another_nested)
+ moar_nested = nested.find_nested_exposure(:moar_nested)
+
+ 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)
end
- it 'represents the exposure as a hash of its nested exposures' do
+ it 'represents the exposure as a hash of its nested.root_exposures' do
subject.expose :awesome do
subject.expose(:nested) { |_| 'value' }
subject.expose(:another_nested) { |_| 'value' }
end
- expect(subject.represent({}).send(:value_for, :awesome)).to eq(
+ expect(subject.represent({}).value_for(:awesome)).to eq(
nested: 'value',
another_nested: 'value'
)
end
- it 'does not represent nested exposures whose conditions are not met' do
+ it 'does not represent nested.root_exposures whose conditions are not met' do
subject.expose :awesome do
subject.expose(:condition_met, if: ->(_, _) { true }) { |_| 'value' }
subject.expose(:condition_not_met, if: ->(_, _) { false }) { |_| 'value' }
end
- expect(subject.represent({}).send(:value_for, :awesome)).to eq(condition_met: 'value')
+ expect(subject.represent({}).value_for(:awesome)).to eq(condition_met: 'value')
end
it 'does not represent attributes, declared inside nested exposure, outside of it' do
subject.expose :awesome do
subject.expose(:nested) { |_| 'value' }
@@ -137,11 +144,11 @@
}
}
)
end
- it 'complex nested attributes' do
+ it 'merges complex nested attributes' do
class ClassRoom < Grape::Entity
expose(:parents, using: 'Parent') { |_| [{}, {}] }
end
class Person < Grape::Entity
@@ -179,11 +186,48 @@
}
]
)
end
- it 'is safe if its nested exposures are safe' do
+ it 'merges results of deeply nested double.root_exposures inside of nesting exposure' do
+ entity = Class.new(Grape::Entity) do
+ expose :data do
+ expose :something do
+ expose(:x) { |_| 'x' }
+ end
+ expose :something do
+ expose(:y) { |_| 'y' }
+ end
+ end
+ end
+ expect(entity.represent({}).serializable_hash).to eq(
+ data: {
+ something: {
+ x: 'x',
+ y: 'y'
+ }
+ }
+ )
+ end
+
+ it 'serializes deeply nested presenter exposures' do
+ e = Class.new(Grape::Entity) do
+ expose :f
+ end
+ subject.expose :a do
+ subject.expose :b do
+ subject.expose :c do
+ subject.expose :lol, using: e
+ end
+ end
+ end
+ expect(subject.represent(lol: { f: 123 }).serializable_hash).to eq(
+ a: { b: { c: { lol: { f: 123 } } } }
+ )
+ end
+
+ it 'is safe if its nested.root_exposures are safe' do
subject.with_options safe: true do
subject.expose :awesome do
subject.expose(:nested) { |_| 'value' }
end
subject.expose :not_awesome do
@@ -200,35 +244,35 @@
)
end
end
end
- context 'inherited exposures' do
- it 'returns exposures from an ancestor' do
+ context 'inherited.root_exposures' do
+ it 'returns.root_exposures from an ancestor' do
subject.expose :name, :email
child_class = Class.new(subject)
- expect(child_class.exposures).to eq(subject.exposures)
+ expect(child_class.root_exposures).to eq(subject.root_exposures)
end
- it 'returns exposures from multiple ancestor' do
+ it 'returns.root_exposures from multiple ancestor' do
subject.expose :name, :email
parent_class = Class.new(subject)
child_class = Class.new(parent_class)
- expect(child_class.exposures).to eq(subject.exposures)
+ expect(child_class.root_exposures).to eq(subject.root_exposures)
end
- it 'returns descendant exposures as a priority' do
+ it 'returns descendant.root_exposures as a priority' do
subject.expose :name, :email
child_class = Class.new(subject)
child_class.expose :name do |_|
'foo'
end
- expect(subject.exposures[:name]).not_to have_key :proc
- expect(child_class.exposures[:name]).to have_key :proc
+ 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
end
context 'register formatters' do
let(:date_formatter) { ->(date) { date.strftime('%m/%d/%Y') } }
@@ -263,81 +307,97 @@
it 'formats an exposure with a :format_with lambda that returns a value from the entity instance' do
object = {}
subject.expose(:size, format_with: ->(_value) { self.object.class.to_s })
- expect(subject.represent(object).send(:value_for, :size)).to eq object.class.to_s
+ expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
end
it 'formats an exposure with a :format_with symbol that returns a value from the entity instance' do
subject.format_with :size_formatter do |_date|
self.object.class.to_s
end
object = {}
subject.expose(:size, format_with: :size_formatter)
- expect(subject.represent(object).send(:value_for, :size)).to eq object.class.to_s
+ expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
end
it 'works global on Grape::Entity' do
Grape::Entity.format_with :size_formatter do |_date|
self.object.class.to_s
end
object = {}
subject.expose(:size, format_with: :size_formatter)
- expect(subject.represent(object).send(:value_for, :size)).to eq object.class.to_s
+ expect(subject.represent(object).value_for(:size)).to eq object.class.to_s
end
end
it 'works global on Grape::Entity' do
Grape::Entity.expose :a
object = { a: 11, b: 22 }
- expect(Grape::Entity.represent(object).send(:value_for, :a)).to eq 11
+ expect(Grape::Entity.represent(object).value_for(:a)).to eq 11
subject.expose :b
- expect(subject.represent(object).send(:value_for, :a)).to eq 11
- expect(subject.represent(object).send(:value_for, :b)).to eq 22
+ expect(subject.represent(object).value_for(:a)).to eq 11
+ expect(subject.represent(object).value_for(:b)).to eq 22
Grape::Entity.unexpose :a
end
end
describe '.unexpose' do
it 'is able to remove exposed attributes' do
subject.expose :name, :email
subject.unexpose :email
- expect(subject.exposures).to eq(name: {})
+ expect(subject.root_exposures.length).to eq 1
+ expect(subject.root_exposures[0].attribute).to eq :name
end
- context 'inherited exposures' do
+ context 'inherited.root_exposures' do
it 'when called from child class, only removes from the attribute from child' do
subject.expose :name, :email
child_class = Class.new(subject)
child_class.unexpose :email
- expect(child_class.exposures).to eq(name: {})
- expect(subject.exposures).to eq(name: {}, email: {})
+ expect(child_class.root_exposures.length).to eq 1
+ expect(child_class.root_exposures[0].attribute).to eq :name
+ expect(subject.root_exposures[0].attribute).to eq :name
+ expect(subject.root_exposures[1].attribute).to eq :email
end
context 'when called from the parent class' do
it 'remove from parent and do not remove from child classes' do
subject.expose :name, :email
child_class = Class.new(subject)
subject.unexpose :email
- expect(subject.exposures).to eq(name: {})
- expect(child_class.exposures).to eq(name: {}, email: {})
+ expect(subject.root_exposures.length).to eq 1
+ expect(subject.root_exposures[0].attribute).to eq :name
+ expect(child_class.root_exposures[0].attribute).to eq :name
+ expect(child_class.root_exposures[1].attribute).to eq :email
end
end
end
+ it 'does not allow unexposing inside of nesting exposures' do
+ expect do
+ Class.new(Grape::Entity) do
+ expose :something do
+ expose :x
+ unexpose :x
+ end
+ end
+ end.to raise_error(/You cannot call 'unexpose`/)
+ end
+
it 'works global on Grape::Entity' do
- Grape::Entity.expose :a
- expect(Grape::Entity.exposures).to eq(a: {})
- Grape::Entity.unexpose :a
- expect(Grape::Entity.exposures).to eq({})
+ Grape::Entity.expose :x
+ expect(Grape::Entity.root_exposures[0].attribute).to eq(:x)
+ Grape::Entity.unexpose :x
+ expect(Grape::Entity.root_exposures).to eq([])
end
end
describe '.with_options' do
it 'raises an error for unknown options' do
@@ -348,18 +408,20 @@
end
expect { subject.class_eval(&block) }.to raise_error ArgumentError
end
- it 'applies the options to all exposures inside' do
+ it 'applies the options to all.root_exposures inside' do
subject.class_eval do
with_options(if: { awesome: true }) do
expose :awesome_thing, using: 'Awesome'
end
end
- expect(subject.exposures[:awesome_thing]).to eq(if: { awesome: true }, using: 'Awesome')
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.using_class_name).to eq('Awesome')
+ expect(exposure.conditions[0].cond_hash).to eq(awesome: true)
end
it 'allows for nested .with_options' do
subject.class_eval do
with_options(if: { awesome: true }) do
@@ -367,21 +429,24 @@
expose :awesome_thing
end
end
end
- expect(subject.exposures[:awesome_thing]).to eq(if: { awesome: true }, using: 'Something')
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.using_class_name).to eq('Something')
+ expect(exposure.conditions[0].cond_hash).to eq(awesome: true)
end
it 'overrides nested :as option' do
subject.class_eval do
with_options(as: :sweet) do
expose :awesome_thing, as: :extra_smooth
end
end
- expect(subject.exposures[:awesome_thing]).to eq(as: :extra_smooth)
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.key).to eq :extra_smooth
end
it 'merges nested :if option' do
match_proc = ->(_obj, _opts) { true }
@@ -399,14 +464,15 @@
end
end
end
end
- expect(subject.exposures[:awesome_thing]).to eq(
- if: { awesome: false, less_awesome: true },
- if_extras: [:awesome, match_proc]
- )
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.conditions.any?(&:inversed?)).to be_falsey
+ expect(exposure.conditions[0].symbol).to eq(:awesome)
+ expect(exposure.conditions[1].block).to eq(match_proc)
+ expect(exposure.conditions[2].cond_hash).to eq(awesome: false, less_awesome: true)
end
it 'merges nested :unless option' do
match_proc = ->(_, _) { true }
@@ -424,33 +490,37 @@
end
end
end
end
- expect(subject.exposures[:awesome_thing]).to eq(
- unless: { awesome: false, less_awesome: true },
- unless_extras: [:awesome, match_proc]
- )
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.conditions.all?(&:inversed?)).to be_truthy
+ expect(exposure.conditions[0].symbol).to eq(:awesome)
+ expect(exposure.conditions[1].block).to eq(match_proc)
+ expect(exposure.conditions[2].cond_hash).to eq(awesome: false, less_awesome: true)
end
it 'overrides nested :using option' do
subject.class_eval do
with_options(using: 'Something') do
expose :awesome_thing, using: 'SomethingElse'
end
end
- expect(subject.exposures[:awesome_thing]).to eq(using: 'SomethingElse')
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.using_class_name).to eq('SomethingElse')
end
it 'aliases :with option to :using option' do
subject.class_eval do
with_options(using: 'Something') do
expose :awesome_thing, with: 'SomethingElse'
end
end
- expect(subject.exposures[:awesome_thing]).to eq(using: 'SomethingElse')
+
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.using_class_name).to eq('SomethingElse')
end
it 'overrides nested :proc option' do
match_proc = ->(_obj, _opts) { 'more awesomer' }
@@ -458,21 +528,23 @@
with_options(proc: ->(_obj, _opts) { 'awesome' }) do
expose :awesome_thing, proc: match_proc
end
end
- expect(subject.exposures[:awesome_thing]).to eq(proc: match_proc)
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.block).to eq(match_proc)
end
it 'overrides nested :documentation option' do
subject.class_eval do
with_options(documentation: { desc: 'Description.' }) do
expose :awesome_thing, documentation: { desc: 'Other description.' }
end
end
- expect(subject.exposures[:awesome_thing]).to eq(documentation: { desc: 'Other description.' })
+ exposure = subject.find_exposure(:awesome_thing)
+ expect(exposure.documentation).to eq(desc: 'Other description.')
end
end
describe '.represent' do
it 'returns a single entity if called with one object' do
@@ -585,10 +657,26 @@
it 'can specify "except" attributes as strings and symbols' do
representation = subject.represent(object, except: [:id, 'address', { user: [:id, 'name'] }], serializable: true)
expect(representation).to eq(name: nil, phone: nil, user: { email: nil })
end
+
+ context 'with nested attributes' do
+ before do
+ subject.expose :additional do
+ subject.expose :something
+ end
+ end
+
+ it 'preserves nesting' do
+ expect(subject.represent({ something: 123 }, only: [{ additional: [:something] }], serializable: true)).to eq(
+ additional: {
+ something: 123
+ }
+ )
+ end
+ end
end
it 'can specify children attributes with only' do
user_entity = Class.new(Grape::Entity)
user_entity.expose(:id, :name, :email)
@@ -642,10 +730,82 @@
end
representation = subject.represent(OpenStruct.new, condition: true, except: [: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' }
+ strategy2 = ->(_obj, _opts) { 'bar' }
+
+ subject.expose :id, proc: strategy1
+ subject.expose :id, proc: strategy2
+ expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'bar')
+ expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
+
+ subject.unexpose_all
+
+ subject.expose :id, proc: strategy1, if: :condition
+ subject.expose :id, proc: strategy2
+ expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'bar')
+ expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
+
+ subject.unexpose_all
+
+ subject.expose :id, proc: strategy1
+ subject.expose :id, proc: strategy2, if: :condition
+ expect(subject.represent({}, condition: false, serializable: true)).to eq(id: 'foo')
+ expect(subject.represent({}, condition: true, serializable: true)).to eq(id: 'bar')
+
+ subject.unexpose_all
+
+ subject.expose :id, proc: strategy1, if: :condition1
+ subject.expose :id, proc: strategy2, if: :condition2
+ expect(subject.represent({}, condition1: false, condition2: false, serializable: true)).to eq({})
+ expect(subject.represent({}, condition1: false, condition2: true, serializable: true)).to eq(id: 'bar')
+ expect(subject.represent({}, condition1: true, condition2: false, serializable: true)).to eq(id: 'foo')
+ expect(subject.represent({}, condition1: true, condition2: true, serializable: true)).to eq(id: 'bar')
+ end
+
+ it 'does not merge nested exposures with plain hashes' do
+ subject.expose(:id)
+ subject.expose(:info, if: :condition1) do
+ subject.expose :a, :b
+ subject.expose(:additional, if: :condition2) do |_obj, _opts|
+ {
+ x: 11, y: 22, c: 123
+ }
+ end
+ end
+ subject.expose(:info, if: :condition2) do
+ subject.expose(:additional) do
+ subject.expose :c
+ end
+ end
+ subject.expose(:d, as: :info, if: :condition3)
+
+ obj = { id: 123, a: 1, b: 2, c: 3, d: 4 }
+
+ expect(subject.represent(obj, serializable: true)).to eq(id: 123)
+ expect(subject.represent(obj, condition1: true, serializable: true)).to eq(id: 123, info: { a: 1, b: 2 })
+ expect(subject.represent(obj, condition2: true, serializable: true)).to eq(
+ id: 123,
+ info: {
+ additional: {
+ c: 3
+ }
+ }
+ )
+ expect(subject.represent(obj, condition1: true, condition2: true, serializable: true)).to eq(
+ id: 123,
+ info: {
+ a: 1, b: 2, additional: { c: 3 }
+ }
+ )
+ expect(subject.represent(obj, condition3: true, serializable: true)).to eq(id: 123, info: 4)
+ expect(subject.represent(obj, condition1: true, condition2: true, condition3: true, serializable: true)).to eq(id: 123, info: 4)
+ end
end
context 'attribute with alias' do
it 'returns only specified fields' do
subject.expose(:id)
@@ -854,10 +1014,15 @@
{ key: 'hair_color', value: 'brown' }
],
friends: [
double(name: 'Friend 1', email: 'friend1@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []),
double(name: 'Friend 2', email: 'friend2@example.com', characteristics: [], fantasies: [], birthday: Time.gm(2012, 2, 27), friends: [])
+ ],
+ extra: { key: 'foo', value: 'bar' },
+ nested: [
+ { name: 'n1', data: { key: 'ex1', value: 'v1' } },
+ { name: 'n2', data: { key: 'ex2', value: 'v2' } }
]
}
end
subject { fresh_class.new(model) }
@@ -887,19 +1052,26 @@
end
fresh_class.expose :name, safe: true
expect(fresh_class.new(some_class.new).serializable_hash).to eq(name: true)
end
- it "does expose attributes that don't exist on the object as nil" do
+ it "does expose attributes that don't exist on the object" do
fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
res = fresh_class.new(model).serializable_hash
expect(res).to have_key :email
expect(res).to have_key :nonexistent_attribute
expect(res).to have_key :name
end
+ it "does expose attributes that don't exist on the object as nil" do
+ fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
+
+ res = fresh_class.new(model).serializable_hash
+ expect(res[:nonexistent_attribute]).to eq(nil)
+ end
+
it 'does expose attributes marked as safe if model is a hash object' do
fresh_class.expose :name, safe: true
res = fresh_class.new(name: 'myname').serializable_hash
expect(res).to have_key :name
@@ -1016,10 +1188,112 @@
fresh_class.expose :name, :embedded
presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithHash.new)
expect(presenter.serializable_hash).to eq(name: 'abc', embedded: { a: nil, b: { abc: 'def' } })
end
end
+
+ context '#attr_path' do
+ it 'for all kinds of attributes' do
+ module EntitySpec
+ class EmailEntity < Grape::Entity
+ expose(:email, as: :addr) { |_, o| o[:attr_path].join('/') }
+ end
+
+ class UserEntity < Grape::Entity
+ expose(:name, as: :full_name) { |_, o| o[:attr_path].join('/') }
+ expose :email, using: 'EntitySpec::EmailEntity'
+ end
+
+ class ExtraEntity < Grape::Entity
+ expose(:key) { |_, o| o[:attr_path].join('/') }
+ expose(:value) { |_, o| o[:attr_path].join('/') }
+ end
+
+ class NestedEntity < Grape::Entity
+ expose(:name) { |_, o| o[:attr_path].join('/') }
+ expose :data, using: 'EntitySpec::ExtraEntity'
+ end
+ end
+
+ fresh_class.class_eval do
+ expose(:id) { |_, o| o[:attr_path].join('/') }
+ expose(:foo, as: :bar) { |_, o| o[:attr_path].join('/') }
+ expose :title do
+ expose :full do
+ expose(:prefix, as: :pref) { |_, o| o[:attr_path].join('/') }
+ expose(:main) { |_, o| o[:attr_path].join('/') }
+ end
+ end
+ expose :friends, as: :social, using: 'EntitySpec::UserEntity'
+ expose :extra, using: 'EntitySpec::ExtraEntity'
+ expose :nested, using: 'EntitySpec::NestedEntity'
+ end
+
+ expect(subject.serializable_hash).to eq(
+ id: 'id',
+ bar: 'bar',
+ title: { full: { pref: 'title/full/pref', main: 'title/full/main' } },
+ social: [
+ { full_name: 'social/full_name', email: { addr: 'social/email/addr' } },
+ { full_name: 'social/full_name', email: { addr: 'social/email/addr' } }
+ ],
+ extra: { key: 'extra/key', value: 'extra/value' },
+ nested: [
+ { name: 'nested/name', data: { key: 'nested/data/key', value: 'nested/data/value' } },
+ { name: 'nested/name', data: { key: 'nested/data/key', value: 'nested/data/value' } }
+ ]
+ )
+ end
+
+ it 'allows customize path of an attribute' do
+ module EntitySpec
+ class CharacterEntity < Grape::Entity
+ expose(:key) { |_, o| o[:attr_path].join('/') }
+ expose(:value) { |_, o| o[:attr_path].join('/') }
+ end
+ end
+
+ fresh_class.class_eval do
+ expose :characteristics, using: EntitySpec::CharacterEntity,
+ attr_path: ->(_obj, _opts) { :character }
+ end
+
+ expect(subject.serializable_hash).to eq(
+ characteristics: [
+ { key: 'character/key', value: 'character/value' }
+ ]
+ )
+ end
+
+ it 'can drop one nest level by set path_for to nil' do
+ module EntitySpec
+ class NoPathCharacterEntity < Grape::Entity
+ expose(:key) { |_, o| o[:attr_path].join('/') }
+ expose(:value) { |_, o| o[:attr_path].join('/') }
+ end
+ end
+
+ fresh_class.class_eval do
+ expose :characteristics, using: EntitySpec::NoPathCharacterEntity, attr_path: proc { nil }
+ end
+
+ expect(subject.serializable_hash).to eq(
+ characteristics: [
+ { key: 'key', value: 'value' }
+ ]
+ )
+ end
+ end
+
+ 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)
+ end
+ end
end
describe '#value_for' do
before do
fresh_class.class_eval do
@@ -1038,21 +1312,23 @@
expose :fantasies, format_with: ->(f) { f.reverse }
end
end
it 'passes through bare expose attributes' do
- expect(subject.send(:value_for, :name)).to eq attributes[:name]
+ expect(subject.value_for(:name)).to eq attributes[:name]
end
it 'instantiates a representation if that is called for' do
- rep = subject.send(:value_for, :friends)
+ rep = subject.value_for(:friends)
expect(rep.reject { |r| r.is_a?(fresh_class) }).to be_empty
expect(rep.first.serializable_hash[:name]).to eq 'Friend 1'
expect(rep.last.serializable_hash[:name]).to eq 'Friend 2'
end
context 'child representations' do
+ after { EntitySpec::FriendEntity.unexpose_all }
+
it 'disables root key name for child representations' do
module EntitySpec
class FriendEntity < Grape::Entity
root 'friends', 'friend'
expose :name, :email
@@ -1061,11 +1337,11 @@
fresh_class.class_eval do
expose :friends, using: EntitySpec::FriendEntity
end
- rep = subject.send(:value_for, :friends)
+ rep = subject.value_for(:friends)
expect(rep).to be_kind_of Array
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
expect(rep.first.serializable_hash[:name]).to eq 'Friend 1'
expect(rep.last.serializable_hash[:name]).to eq 'Friend 2'
end
@@ -1082,11 +1358,11 @@
expose :custom_friends, using: EntitySpec::FriendEntity do |user, _opts|
user.friends
end
end
- rep = subject.send(:value_for, :custom_friends)
+ rep = subject.value_for(:custom_friends)
expect(rep).to be_kind_of Array
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
expect(rep.first.serializable_hash).to eq(name: 'Friend 1', email: 'friend1@example.com')
expect(rep.last.serializable_hash).to eq(name: 'Friend 2', email: 'friend2@example.com')
end
@@ -1103,11 +1379,11 @@
expose :first_friend, using: EntitySpec::FriendEntity do |user, _opts|
user.friends.first
end
end
- rep = subject.send(:value_for, :first_friend)
+ rep = subject.value_for(:first_friend)
expect(rep).to be_kind_of EntitySpec::FriendEntity
expect(rep.serializable_hash).to eq(name: 'Friend 1', email: 'friend1@example.com')
end
it 'passes through the proc which returns empty with custom options(:using)' do
@@ -1121,11 +1397,11 @@
fresh_class.class_eval do
expose :first_friend, using: EntitySpec::FriendEntity do |_user, _opts|
end
end
- rep = subject.send(:value_for, :first_friend)
+ rep = subject.value_for(:first_friend)
expect(rep).to be_kind_of EntitySpec::FriendEntity
expect(rep.serializable_hash).to be_nil
end
it 'passes through exposed entity with key and value attributes' do
@@ -1138,11 +1414,11 @@
fresh_class.class_eval do
expose :characteristics, using: EntitySpec::CharacteristicsEntity
end
- rep = subject.send(:value_for, :characteristics)
+ rep = subject.value_for(:characteristics)
expect(rep).to be_kind_of Array
expect(rep.reject { |r| r.is_a?(EntitySpec::CharacteristicsEntity) }).to be_empty
expect(rep.first.serializable_hash[:key]).to eq 'hair_color'
expect(rep.first.serializable_hash[:value]).to eq 'brown'
end
@@ -1158,17 +1434,17 @@
fresh_class.class_eval do
expose :friends, using: EntitySpec::FriendEntity
end
- rep = subject.send(:value_for, :friends)
+ rep = subject.value_for(:friends)
expect(rep).to be_kind_of Array
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
expect(rep.first.serializable_hash[:email]).to be_nil
expect(rep.last.serializable_hash[:email]).to be_nil
- rep = subject.send(:value_for, :friends, user_type: :admin)
+ rep = subject.value_for(:friends, Grape::Entity::Options.new(user_type: :admin))
expect(rep).to be_kind_of Array
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
expect(rep.first.serializable_hash[:email]).to eq 'friend1@example.com'
expect(rep.last.serializable_hash[:email]).to eq 'friend2@example.com'
end
@@ -1184,28 +1460,28 @@
fresh_class.class_eval do
expose :friends, using: EntitySpec::FriendEntity
end
- rep = subject.send(:value_for, :friends, collection: false)
+ rep = subject.value_for(:friends, Grape::Entity::Options.new(collection: false))
expect(rep).to be_kind_of Array
expect(rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }).to be_empty
expect(rep.first.serializable_hash[:email]).to eq 'friend1@example.com'
expect(rep.last.serializable_hash[:email]).to eq 'friend2@example.com'
end
end
it 'calls through to the proc if there is one' do
- expect(subject.send(:value_for, :computed, awesome: 123)).to eq 123
+ expect(subject.value_for(:computed, Grape::Entity::Options.new(awesome: 123))).to eq 123
end
it 'returns a formatted value if format_with is passed' do
- expect(subject.send(:value_for, :birthday)).to eq '02/27/2012'
+ expect(subject.value_for(:birthday)).to eq '02/27/2012'
end
it 'returns a formatted value if format_with is passed a lambda' do
- expect(subject.send(:value_for, :fantasies)).to eq ['Nessy', 'Double Rainbows', 'Unicorns']
+ expect(subject.value_for(:fantasies)).to eq ['Nessy', 'Double Rainbows', 'Unicorns']
end
it 'tries instance methods on the entity first' do
module EntitySpec
class DelegatingEntity < Grape::Entity
@@ -1221,16 +1497,16 @@
end
end
friend = double('Friend', name: 'joe', email: 'joe@example.com')
rep = EntitySpec::DelegatingEntity.new(friend)
- expect(rep.send(:value_for, :name)).to eq 'cooler name'
- expect(rep.send(:value_for, :email)).to eq 'joe@example.com'
+ expect(rep.value_for(:name)).to eq 'cooler name'
+ expect(rep.value_for(:email)).to eq 'joe@example.com'
another_friend = double('Friend', email: 'joe@example.com')
rep = EntitySpec::DelegatingEntity.new(another_friend)
- expect(rep.send(:value_for, :name)).to eq 'cooler name'
+ expect(rep.value_for(:name)).to eq 'cooler name'
end
context 'using' do
before do
module EntitySpec
@@ -1242,30 +1518,30 @@
it 'string' do
fresh_class.class_eval do
expose :friends, using: 'EntitySpec::UserEntity'
end
- rep = subject.send(:value_for, :friends)
+ rep = subject.value_for(:friends)
expect(rep).to be_kind_of Array
expect(rep.size).to eq 2
expect(rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }).to be true
end
it 'class' do
fresh_class.class_eval do
expose :friends, using: EntitySpec::UserEntity
end
- rep = subject.send(:value_for, :friends)
+ rep = subject.value_for(:friends)
expect(rep).to be_kind_of Array
expect(rep.size).to eq 2
expect(rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }).to be true
end
end
end
- describe '#documentation' do
+ describe '.documentation' do
it 'returns an empty hash is no documentation is provided' do
fresh_class.expose :name
expect(subject.documentation).to eq({})
end
@@ -1286,10 +1562,22 @@
fresh_class.expose :birthday
expect(subject.documentation).to eq(label: doc, email: doc)
end
+ it 'resets memoization when exposing additional attributes' do
+ fresh_class.expose :x, documentation: { desc: 'just x' }
+ expect(fresh_class.instance_variable_get(:@documentation)).to be_nil
+ doc1 = fresh_class.documentation
+ expect(fresh_class.instance_variable_get(:@documentation)).not_to be_nil
+ fresh_class.expose :y, documentation: { desc: 'just y' }
+ expect(fresh_class.instance_variable_get(:@documentation)).to be_nil
+ doc2 = fresh_class.documentation
+ expect(doc1).to eq(x: { desc: 'just x' })
+ expect(doc2).to eq(x: { desc: 'just x' }, y: { desc: 'just y' })
+ end
+
context 'inherited documentation' do
it 'returns documentation from ancestor' do
doc = { type: 'foo', desc: 'bar' }
fresh_class.expose :name, documentation: doc
child_class = Class.new(fresh_class)
@@ -1324,85 +1612,21 @@
expect(fresh_class.documentation).to eq(name: doc, email: doc)
expect(child_class.documentation).to eq(name: doc)
expect(nephew_class.documentation).to eq(name: doc, email: new_doc)
end
- end
- end
- describe '#key_for' do
- it 'returns the attribute if no :as is set' do
- fresh_class.expose :name
- expect(subject.class.send(:key_for, :name)).to eq :name
+ it 'includes only root exposures' do
+ fresh_class.expose :name, documentation: { desc: 'foo' }
+ fresh_class.expose :nesting do
+ fresh_class.expose :smth, documentation: { desc: 'should not be seen' }
+ end
+ expect(fresh_class.documentation).to eq(name: { desc: 'foo' })
+ end
end
-
- it 'returns a symbolized version of the attribute' do
- fresh_class.expose :name
- expect(subject.class.send(:key_for, 'name')).to eq :name
- end
-
- it 'returns the :as alias if one exists' do
- fresh_class.expose :name, as: :nombre
- expect(subject.class.send(:key_for, 'name')).to eq :nombre
- end
end
- describe '#conditions_met?' do
- it 'only passes through hash :if exposure if all attributes match' do
- exposure_options = { if: { condition1: true, condition2: true } }
-
- expect(subject.send(:conditions_met?, exposure_options, {})).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true)).to be true
- expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true)).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true)).to be true
- end
-
- it 'looks for presence/truthiness if a symbol is passed' do
- exposure_options = { if: :condition1 }
-
- expect(subject.send(:conditions_met?, exposure_options, {})).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be true
- expect(subject.send(:conditions_met?, exposure_options, condition1: false)).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: nil)).to be false
- end
-
- it 'looks for absence/falsiness if a symbol is passed' do
- exposure_options = { unless: :condition1 }
-
- expect(subject.send(:conditions_met?, exposure_options, {})).to be true
- expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: false)).to be true
- expect(subject.send(:conditions_met?, exposure_options, condition1: nil)).to be true
- end
-
- it 'only passes through proc :if exposure if it returns truthy value' do
- exposure_options = { if: ->(_, opts) { opts[:true] } }
-
- expect(subject.send(:conditions_met?, exposure_options, true: false)).to be false
- expect(subject.send(:conditions_met?, exposure_options, true: true)).to be true
- end
-
- it 'only passes through hash :unless exposure if any attributes do not match' do
- exposure_options = { unless: { condition1: true, condition2: true } }
-
- expect(subject.send(:conditions_met?, exposure_options, {})).to be true
- expect(subject.send(:conditions_met?, exposure_options, condition1: true)).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true)).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true)).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true)).to be false
- expect(subject.send(:conditions_met?, exposure_options, condition1: false, condition2: false)).to be true
- end
-
- it 'only passes through proc :unless exposure if it returns falsy value' do
- exposure_options = { unless: ->(_, opts) { opts[:true] == true } }
-
- expect(subject.send(:conditions_met?, exposure_options, true: false)).to be true
- expect(subject.send(:conditions_met?, exposure_options, true: true)).to be false
- end
- end
-
describe '::DSL' do
subject { Class.new }
it 'creates an Entity class when called' do
expect(subject).not_to be_const_defined :Entity
@@ -1416,22 +1640,22 @@
it 'is able to define entity traits through DSL' do
subject.entity do
expose :name
end
- expect(subject.entity_class.exposures).not_to be_empty
+ expect(subject.entity_class.root_exposures).not_to be_empty
end
it 'is able to expose straight from the class' do
subject.entity :name, :email
- expect(subject.entity_class.exposures.size).to eq 2
+ expect(subject.entity_class.root_exposures.size).to eq 2
end
- it 'is able to mix field and advanced exposures' do
+ it 'is able to mix field and advanced.root_exposures' do
subject.entity :name, :email do
expose :third
end
- expect(subject.entity_class.exposures.size).to eq 3
+ expect(subject.entity_class.root_exposures.size).to eq 3
end
context 'instance' do
let(:instance) { subject.new }