spec/grape_entity/entity_spec.rb in grape-entity-0.4.5 vs spec/grape_entity/entity_spec.rb in grape-entity-0.4.6
- old
+ new
@@ -68,18 +68,18 @@
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 = lambda { |_| true }
+ block = ->(_) { true }
subject.expose :name, using: 'Awesome', &block
expect(subject.exposures[:name]).to eq(proc: block, using: 'Awesome')
end
it 'references an instance of the entity without any options' do
subject.expose(:size) { |_| self }
- expect(subject.represent(Hash.new).send(:value_for, :size)).to be_an_instance_of fresh_class
+ expect(subject.represent({}).send(: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
@@ -89,33 +89,33 @@
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: {},
+ awesome__nested: { nested: true },
+ awesome__nested__moar_nested: { as: 'weee', nested: true },
+ awesome__another_nested: { using: 'Awesome', nested: true }
)
end
it 'represents the exposure as a hash of its nested 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(
- nested: 'value',
- another_nested: 'value'
+ nested: 'value',
+ another_nested: 'value'
)
end
it 'does not represent nested exposures whose conditions are not met' do
subject.expose :awesome do
- subject.expose(:condition_met, if: lambda { |_, _| true }) { |_| 'value' }
- subject.expose(:condition_not_met, if: lambda { |_, _| false }) { |_| 'value' }
+ 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')
end
@@ -127,17 +127,17 @@
subject.expose(:deeply_exposed_attr) { |_| 'value' }
end
end
expect(subject.represent({}).serializable_hash).to eq(
- awesome: {
- nested: 'value',
- another_nested: 'value',
- second_level_nested: {
- deeply_exposed_attr: 'value'
- }
- }
+ awesome: {
+ nested: 'value',
+ another_nested: 'value',
+ second_level_nested: {
+ deeply_exposed_attr: 'value'
+ }
+ }
)
end
it 'complex nested attributes' do
class ClassRoom < Grape::Entity
@@ -160,26 +160,26 @@
class Parent < Person
expose(:children, using: 'Student') { |_| [{}, {}] }
end
expect(ClassRoom.represent({}).serializable_hash).to eq(
- parents: [
- {
- user: { in_first: 'value' },
- children: [
- { user: { in_first: 'value', user_id: 'value', display_id: 'value' } },
- { user: { in_first: 'value', user_id: 'value', display_id: 'value' } }
- ]
- },
- {
- user: { in_first: 'value' },
- children: [
- { user: { in_first: 'value', user_id: 'value', display_id: 'value' } },
- { user: { in_first: 'value', user_id: 'value', display_id: 'value' } }
- ]
- }
- ]
+ parents: [
+ {
+ user: { in_first: 'value' },
+ children: [
+ { user: { in_first: 'value', user_id: 'value', display_id: 'value' } },
+ { user: { in_first: 'value', user_id: 'value', display_id: 'value' } }
+ ]
+ },
+ {
+ user: { in_first: 'value' },
+ children: [
+ { user: { in_first: 'value', user_id: 'value', display_id: 'value' } },
+ { user: { in_first: 'value', user_id: 'value', display_id: 'value' } }
+ ]
+ }
+ ]
)
end
it 'is safe if its nested exposures are safe' do
subject.with_options safe: true do
@@ -188,15 +188,18 @@
end
subject.expose :not_awesome do
subject.expose :nested
end
end
-
- valid_keys = subject.represent({}).valid_exposures.keys
-
- expect(valid_keys.include?(:awesome)).to be true
- expect(valid_keys.include?(:not_awesome)).to be false
+ expect(subject.represent({}, serializable: true)).to eq(
+ awesome: {
+ nested: 'value'
+ },
+ not_awesome: {
+ nested: nil
+ }
+ )
end
end
end
context 'inherited exposures' do
@@ -226,11 +229,11 @@
expect(child_class.exposures[:name]).to have_key :proc
end
end
context 'register formatters' do
- let(:date_formatter) { lambda { |date| date.strftime('%m/%d/%Y') } }
+ let(:date_formatter) { ->(date) { date.strftime('%m/%d/%Y') } }
it 'registers a formatter' do
subject.format_with :timestamp, &date_formatter
expect(subject.formatters[:timestamp]).not_to be_nil
@@ -257,22 +260,22 @@
model = { birthday: Time.gm(2012, 2, 27) }
expect(subject.new(double(model)).as_json[:birthday]).to eq '02/27/2012'
end
it 'formats an exposure with a :format_with lambda that returns a value from the entity instance' do
- object = Hash.new
+ object = {}
- subject.expose(:size, format_with: lambda { |_value| self.object.class.to_s })
+ 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
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 = Hash.new
+ object = {}
subject.expose(:size, format_with: :size_formatter)
expect(subject.represent(object).send(:value_for, :size)).to eq object.class.to_s
end
end
@@ -294,28 +297,17 @@
expect(child_class.exposures).to eq(name: {})
expect(subject.exposures).to eq(name: {}, email: {})
end
- # the following 2 behaviors are testing because it is not most intuitive and could be confusing
- context 'when called from the parent class' do
- it 'remove from parent and all child classes that have not locked down their attributes with an .exposures call' do
+ 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: {})
- end
-
- it 'remove from parent and do not remove from child classes that have locked down their attributes with an .exposures call' do
- subject.expose :name, :email
- child_class = Class.new(subject)
- child_class.exposures
- subject.unexpose :email
-
- expect(subject.exposures).to eq(name: {})
expect(child_class.exposures).to eq(name: {}, email: {})
end
end
end
end
@@ -362,11 +354,11 @@
expect(subject.exposures[:awesome_thing]).to eq(as: :extra_smooth)
end
it 'merges nested :if option' do
- match_proc = lambda { |_obj, _opts| true }
+ match_proc = ->(_obj, _opts) { true }
subject.class_eval do
# Symbol
with_options(if: :awesome) do
# Hash
@@ -381,17 +373,17 @@
end
end
end
expect(subject.exposures[:awesome_thing]).to eq(
- if: { awesome: false, less_awesome: true },
- if_extras: [:awesome, match_proc]
+ if: { awesome: false, less_awesome: true },
+ if_extras: [:awesome, match_proc]
)
end
it 'merges nested :unless option' do
- match_proc = lambda { |_, _| true }
+ match_proc = ->(_, _) { true }
subject.class_eval do
# Symbol
with_options(unless: :awesome) do
# Hash
@@ -406,12 +398,12 @@
end
end
end
expect(subject.exposures[:awesome_thing]).to eq(
- unless: { awesome: false, less_awesome: true },
- unless_extras: [:awesome, match_proc]
+ unless: { awesome: false, less_awesome: true },
+ unless_extras: [:awesome, match_proc]
)
end
it 'overrides nested :using option' do
subject.class_eval do
@@ -431,14 +423,14 @@
end
expect(subject.exposures[:awesome_thing]).to eq(using: 'SomethingElse')
end
it 'overrides nested :proc option' do
- match_proc = lambda { |_obj, _opts| 'more awesomer' }
+ match_proc = ->(_obj, _opts) { 'more awesomer' }
subject.class_eval do
- with_options(proc: lambda { |_obj, _opts| 'awesome' }) do
+ 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)
@@ -459,11 +451,11 @@
it 'returns a single entity if called with one object' do
expect(subject.represent(Object.new)).to be_kind_of(subject)
end
it 'returns a single entity if called with a hash' do
- expect(subject.represent(Hash.new)).to be_kind_of(subject)
+ expect(subject.represent({})).to be_kind_of(subject)
end
it 'returns multiple entities if called with a collection' do
representation = subject.represent(4.times.map { Object.new })
expect(representation).to be_kind_of Array
@@ -504,10 +496,168 @@
subject.expose(:awesome)
expect do
subject.represent(Object.new, serializable: true)
end.to raise_error(NoMethodError, /missing attribute `awesome'/)
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)
+ 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)
+ representation = subject.represent(OpenStruct.new, except: [:phone], serializable: true)
+ expect(representation).to eq(id: nil, name: nil)
+ 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],
+ except: [:phone], serializable: true)
+ expect(representation).to eq(name: nil)
+ end
+
+ context 'with strings or symbols passed to only and except' do
+ let(:object) { OpenStruct.new(user: {}) }
+
+ before do
+ user_entity = Class.new(Grape::Entity)
+ user_entity.expose(:id, :name, :email)
+
+ subject.expose(:id, :name, :phone, :address)
+ subject.expose(:user, using: user_entity)
+ end
+
+ it 'can specify "only" option attributes as strings' do
+ representation = subject.represent(object, only: ['id', 'name', { 'user' => ['email'] }], serializable: true)
+ expect(representation).to eq(id: nil, name: nil, user: { email: nil })
+ end
+
+ it 'can specify "except" option attributes as strings' do
+ representation = subject.represent(object, except: ['id', 'name', { 'user' => ['email'] }], serializable: true)
+ expect(representation).to eq(phone: nil, address: nil, user: { id: nil, name: nil })
+ end
+
+ it 'can specify "only" option attributes as symbols' do
+ representation = subject.represent(object, only: [:name, :phone, { user: [:name] }], serializable: true)
+ expect(representation).to eq(name: nil, phone: nil, user: { name: nil })
+ end
+
+ it 'can specify "except" option attributes as symbols' do
+ representation = subject.represent(object, except: [:name, :phone, { user: [:name] }], serializable: true)
+ expect(representation).to eq(id: nil, address: nil, user: { id: nil, email: nil })
+ end
+
+ it 'can specify "only" attributes as strings and symbols' do
+ representation = subject.represent(object, only: [:id, 'address', { user: [:id, 'name'] }], serializable: true)
+ expect(representation).to eq(id: nil, address: nil, user: { id: nil, name: nil })
+ end
+
+ 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
+ end
+
+ it 'can specify children attributes with only' do
+ user_entity = Class.new(Grape::Entity)
+ 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)
+ 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)
+ user_entity.expose(:id, :name, :email)
+
+ subject.expose(:id, :name, :phone)
+ subject.expose(:user, using: user_entity)
+
+ representation = subject.represent(OpenStruct.new(user: {}), except: [:phone, { user: [:id] }], serializable: true)
+ expect(representation).to eq(id: nil, name: nil, user: { name: nil, email: nil })
+ end
+
+ it 'can specify children attributes with mixed only and except' do
+ user_entity = Class.new(Grape::Entity)
+ user_entity.expose(:id, :name, :email, :address)
+
+ 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]],
+ 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
+ it 'returns only specified fields' do
+ 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)
+ 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)
+ expect(representation).to eq(id: nil, name: nil)
+ end
+ end
+
+ 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)
+ expect(representation).to eq(id: nil, title: nil)
+ end
+
+ it 'does not return fields specified in the except option' do
+ subject.expose(:id)
+ subject.expose(:name, as: :title)
+ subject.expose(:phone, as: :phone_number)
+
+ representation = subject.represent(OpenStruct.new, condition: true, except: [:phone_number], serializable: true)
+ expect(representation).to eq(id: nil, title: nil)
+ end
+ end
+
+ context 'attribute that is an entity itself' do
+ it 'returns correctly the children entity attributes' do
+ user_entity = Class.new(Grape::Entity)
+ user_entity.expose(:id, :name, :email)
+
+ nephew_entity = Class.new(Grape::Entity)
+ nephew_entity.expose(:id, :name, :email)
+
+ 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)
+ expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil })
+ end
+ end
+ end
end
describe '.present_collection' do
it 'make the objects accessible' do
subject.present_collection true
@@ -621,10 +771,34 @@
expect(representation['things'].size).to eq 4
expect(representation['things'].reject { |r| r.is_a?(subject) }).to be_empty
end
end
end
+
+ context 'inheriting from parent entity' do
+ before(:each) do
+ subject.root 'things', 'thing'
+ end
+
+ it 'inherits single root' do
+ child_class = Class.new(subject)
+ representation = child_class.represent(Object.new)
+ expect(representation).to be_kind_of Hash
+ expect(representation).to have_key 'thing'
+ expect(representation['thing']).to be_kind_of(child_class)
+ end
+
+ it 'inherits array root root' do
+ child_class = Class.new(subject)
+ representation = child_class.represent(4.times.map { Object.new })
+ expect(representation).to be_kind_of Hash
+ expect(representation).to have_key('things')
+ expect(representation['things']).to be_kind_of Array
+ expect(representation['things'].size).to eq 4
+ expect(representation['things'].reject { |r| r.is_a?(child_class) }).to be_empty
+ end
+ end
end
describe '#initialize' do
it 'takes an object and an optional options hash' do
expect { subject.new(Object.new) }.not_to raise_error
@@ -675,35 +849,46 @@
it 'does not throw an exception when an attribute is not found on the object' do
fresh_class.expose :name, :nonexistent_attribute, safe: true
expect { fresh_class.new(model).serializable_hash }.not_to raise_error
end
- it "does not expose attributes that don't exist on the object" do
+ it 'exposes values of private method calls' do
+ some_class = Class.new do
+ define_method :name do
+ true
+ end
+ private :name
+ 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
fresh_class.expose :email, :nonexistent_attribute, :name, safe: true
res = fresh_class.new(model).serializable_hash
expect(res).to have_key :email
- expect(res).not_to have_key :nonexistent_attribute
+ expect(res).to have_key :nonexistent_attribute
expect(res).to have_key :name
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
end
- it "does not expose attributes that don't exist on the object, even with criteria" do
+ it "does expose attributes that don't exist on the object as nil if criteria is true" do
fresh_class.expose :email
- fresh_class.expose :nonexistent_attribute, safe: true, if: lambda { false }
- fresh_class.expose :nonexistent_attribute2, safe: true, if: lambda { true }
+ fresh_class.expose :nonexistent_attribute, safe: true, if: ->(_obj, _opts) { false }
+ fresh_class.expose :nonexistent_attribute2, safe: true, if: ->(_obj, _opts) { true }
res = fresh_class.new(model).serializable_hash
expect(res).to have_key :email
expect(res).not_to have_key :nonexistent_attribute
- expect(res).not_to have_key :nonexistent_attribute2
+ expect(res).to have_key :nonexistent_attribute2
end
end
context 'without safe option' do
it 'throws an exception when an attribute is not found on the object' do
@@ -718,13 +903,13 @@
res = fresh_class.new(model).serializable_hash
expect(res).to have_key :nonexistent_attribute
end
it 'does not expose attributes that are generated by a block but have not passed criteria' do
- fresh_class.expose :nonexistent_attribute, proc: lambda { |_model, _opts|
- 'I exist, but it is not yet my time to shine'
- }, if: lambda { |_model, _opts| false }
+ fresh_class.expose :nonexistent_attribute,
+ proc: ->(_model, _opts) { 'I exist, but it is not yet my time to shine' },
+ if: ->(_model, _opts) { false }
res = fresh_class.new(model).serializable_hash
expect(res).not_to have_key :nonexistent_attribute
end
end
@@ -740,13 +925,13 @@
res = fresh_class.new(model).serializable_hash
expect(res).to have_key :nonexistent_attribute
end
it 'does not expose attributes that are generated by a block but have not passed criteria' do
- fresh_class.expose :nonexistent_attribute, proc: lambda { |_, _|
- 'I exist, but it is not yet my time to shine'
- }, if: lambda { |_, _| false }
+ fresh_class.expose :nonexistent_attribute,
+ proc: ->(_, _) { 'I exist, but it is not yet my time to shine' },
+ if: ->(_, _) { false }
res = fresh_class.new(model).serializable_hash
expect(res).not_to have_key :nonexistent_attribute
end
context '#serializable_hash' do
@@ -821,11 +1006,11 @@
def timestamp(date)
date.strftime('%m/%d/%Y')
end
- expose :fantasies, format_with: lambda { |f| f.reverse }
+ 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]
@@ -1069,10 +1254,50 @@
fresh_class.expose :email, documentation: doc
fresh_class.expose :birthday
expect(subject.documentation).to eq(label: doc, email: doc)
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)
+ child_class.expose :email, documentation: doc
+
+ expect(fresh_class.documentation).to eq(name: doc)
+ expect(child_class.documentation).to eq(name: doc, email: doc)
+ end
+
+ it 'obeys unexposed attributes in subclass' do
+ doc = { type: 'foo', desc: 'bar' }
+ fresh_class.expose :name, documentation: doc
+ fresh_class.expose :email, documentation: doc
+ child_class = Class.new(fresh_class)
+ child_class.unexpose :email
+
+ expect(fresh_class.documentation).to eq(name: doc, email: doc)
+ expect(child_class.documentation).to eq(name: doc)
+ end
+
+ it 'obeys re-exposed attributes in subclass' do
+ doc = { type: 'foo', desc: 'bar' }
+ fresh_class.expose :name, documentation: doc
+ fresh_class.expose :email, documentation: doc
+
+ child_class = Class.new(fresh_class)
+ child_class.unexpose :email
+
+ nephew_class = Class.new(child_class)
+ new_doc = { type: 'todler', descr: '???' }
+ nephew_class.expose :email, documentation: new_doc
+
+ 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
@@ -1118,11 +1343,11 @@
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: lambda { |_, opts| opts[:true] } }
+ 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
@@ -1136,10 +1361,10 @@
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: lambda { |_, options| options[:true] == true } }
+ 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