spec/grape_entity/entity_spec.rb in grape-entity-0.3.0 vs spec/grape_entity/entity_spec.rb in grape-entity-0.4.0

- old
+ new

@@ -1,8 +1,9 @@ require 'spec_helper' describe Grape::Entity do + let(:fresh_class) { Class.new(Grape::Entity) } context 'class methods' do subject { fresh_class } @@ -12,38 +13,120 @@ subject.expose :name, :email, :location subject.exposures.size.should == 3 end it 'sets the same options for all exposures passed' do - subject.expose :name, :email, :location, :foo => :bar - subject.exposures.values.each{|v| v.should == {:foo => :bar}} + subject.expose :name, :email, :location, documentation: true + subject.exposures.values.each { |v| v.should == { documentation: true } } end end context 'option validation' do it 'makes sure that :as only works on single attribute calls' do - expect{ subject.expose :name, :email, :as => :foo }.to raise_error(ArgumentError) - expect{ subject.expose :name, :as => :foo }.not_to raise_error + 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 can not be used with a block' do - expect { subject.expose :name, :format_with => Proc.new {} do |_| end }.to raise_error(ArgumentError) + 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 end + + it 'makes sure unknown options are not silently ignored' do + expect { subject.expose :name, unknown: nil }.to raise_error ArgumentError + end end context 'with a block' do it 'errors out if called with multiple attributes' do - expect{ subject.expose(:name, :email) do - true - end }.to raise_error(ArgumentError) + expect { subject.expose(:name, :email) { true } }.to raise_error ArgumentError end - it 'sets the :proc option in the exposure options' do - block = lambda{|_| true } - subject.expose :name, &block - subject.exposures[:name][:proc].should == block + it 'references an instance of the entity with :using option' do + module EntitySpec + class SomeObject1 + attr_accessor :prop1 + + def initialize + @prop1 = "value1" + end + end + + class BogusEntity < Grape::Entity + expose :prop1 + end + end + + subject.expose(:bogus, using: EntitySpec::BogusEntity) do |entity| + entity.prop1 = "MODIFIED 2" + entity + end + + object = EntitySpec::SomeObject1.new + value = subject.represent(object).send(:value_for, :bogus) + value.should be_instance_of EntitySpec::BogusEntity + + prop1 = value.send(:value_for, :prop1) + prop1.should == "MODIFIED 2" end + + context 'with parameters passed to the block' do + it 'sets the :proc option in the exposure options' do + block = lambda { |_| true } + subject.expose :name, using: 'Awesome', &block + subject.exposures[:name].should == { proc: block, using: 'Awesome' } + end + + it 'references an instance of the entity without any options' do + subject.expose(:size) { |_| self } + subject.represent(Hash.new).send(:value_for, :size).should be_an_instance_of fresh_class + 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' + end + subject.expose :another_nested, using: 'Awesome' + end + + subject.exposures.should == { + awesome: {}, + awesome__nested: {}, + awesome__nested__moar_nested: { as: 'weee' }, + awesome__another_nested: { using: 'Awesome' } + } + 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 + + subject.represent({}).send(:value_for, :awesome).should == { + nested: "value", + another_nested: "value" + } + end + + it 'is safe if its nested exposures are safe' do + subject.with_options safe: true do + subject.expose :awesome do + subject.expose(:nested) { |_| "value" } + end + subject.expose :not_awesome do + subject.expose :nested + end + end + + valid_keys = subject.represent({}).valid_exposures.keys + valid_keys.include?(:awesome).should == true && \ + valid_keys.include?(:not_awesome).should == false + end + end end context 'inherited exposures' do it 'returns exposures from an ancestor' do subject.expose :name, :email @@ -71,11 +154,11 @@ child_class.exposures[:name].should have_key :proc end end context 'register formatters' do - let(:date_formatter) { lambda {|date| date.strftime('%m/%d/%Y') }} + let(:date_formatter) { lambda { |date| date.strftime('%m/%d/%Y') } } it 'registers a formatter' do subject.format_with :timestamp, &date_formatter subject.formatters[:timestamp].should_not be_nil @@ -87,58 +170,177 @@ child_class.formatters.should == subject.formatters end it 'does not allow registering a formatter without a block' do - expect{ subject.format_with :foo }.to raise_error(ArgumentError) + expect { subject.format_with :foo }.to raise_error ArgumentError end it 'formats an exposure with a registered formatter' do subject.format_with :timestamp do |date| date.strftime('%m/%d/%Y') end - subject.expose :birthday, :format_with => :timestamp + subject.expose :birthday, format_with: :timestamp - model = { :birthday => Time.gm(2012, 2, 27) } - subject.new(mock(model)).as_json[:birthday].should == '02/27/2012' + model = { birthday: Time.gm(2012, 2, 27) } + subject.new(double(model)).as_json[:birthday].should == '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 + + subject.expose(:size, format_with: lambda { |value| self.object.class.to_s }) + subject.represent(object).send(:value_for, :size).should == 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 + + subject.expose(:size, format_with: :size_formatter) + subject.represent(object).send(:value_for, :size).should == object.class.to_s + end end end describe '.with_options' do - it 'should apply the options to all exposures inside' do + it 'raises an error for unknown options' do + block = proc do + with_options(unknown: true) do + expose :awesome_thing + end + end + + expect { subject.class_eval(&block) }.to raise_error ArgumentError + end + + it 'applies the options to all exposures inside' do subject.class_eval do - with_options(:if => {:awesome => true}) do - expose :awesome_thing, :using => 'Awesome' + with_options(if: { awesome: true }) do + expose :awesome_thing, using: 'Awesome' end end - subject.exposures[:awesome_thing].should == {:if => {:awesome => true}, :using => 'Awesome'} + subject.exposures[:awesome_thing].should == { if: { awesome: true }, using: 'Awesome' } end - it 'should allow for nested .with_options' do + it 'allows for nested .with_options' do subject.class_eval do - with_options(:if => {:awesome => true}) do - with_options(:using => 'Something') do + with_options(if: { awesome: true }) do + with_options(using: 'Something') do expose :awesome_thing end end end - subject.exposures[:awesome_thing].should == {:if => {:awesome => true}, :using => 'Something'} + subject.exposures[:awesome_thing].should == { if: { awesome: true }, using: 'Something' } end - it 'should allow for overrides' do + it 'overrides nested :as option' do subject.class_eval do - with_options(:if => {:awesome => true}) do - expose :less_awesome_thing, :if => {:awesome => false} + with_options(as: :sweet) do + expose :awesome_thing, as: :extra_smooth end end - subject.exposures[:less_awesome_thing].should == {:if => {:awesome => false}} + subject.exposures[:awesome_thing].should == { as: :extra_smooth } end + + it "merges nested :if option" do + match_proc = lambda { |obj, opts| true } + + subject.class_eval do + # Symbol + with_options(if: :awesome) do + # Hash + with_options(if: { awesome: true }) do + # Proc + with_options(if: match_proc) do + # Hash (override existing key and merge new key) + with_options(if: { awesome: false, less_awesome: true }) do + expose :awesome_thing + end + end + end + end + end + + subject.exposures[:awesome_thing].should == { + if: { awesome: false, less_awesome: true }, + if_extras: [:awesome, match_proc] + } + end + + it 'merges nested :unless option' do + match_proc = lambda { |obj, opts| true } + + subject.class_eval do + # Symbol + with_options(unless: :awesome) do + # Hash + with_options(unless: { awesome: true }) do + # Proc + with_options(unless: match_proc) do + # Hash (override existing key and merge new key) + with_options(unless: { awesome: false, less_awesome: true }) do + expose :awesome_thing + end + end + end + end + end + + subject.exposures[:awesome_thing].should == { + unless: { awesome: false, less_awesome: true }, + unless_extras: [:awesome, match_proc] + } + end + + it 'overrides nested :using option' do + subject.class_eval do + with_options(using: 'Something') do + expose :awesome_thing, using: 'SomethingElse' + end + end + + subject.exposures[:awesome_thing].should == { using: '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 + subject.exposures[:awesome_thing].should == { using: 'SomethingElse' } + end + + it 'overrides nested :proc option' do + match_proc = lambda { |obj, opts| 'more awesomer' } + + subject.class_eval do + with_options(proc: lambda { |obj, opts| 'awesome' }) do + expose :awesome_thing, proc: match_proc + end + end + + subject.exposures[:awesome_thing].should == { proc: 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 + + subject.exposures[:awesome_thing].should == { documentation: { desc: 'Other description.' } } + end end describe '.represent' do it 'returns a single entity if called with one object' do subject.represent(Object.new).should be_kind_of(subject) @@ -147,20 +349,32 @@ it 'returns a single entity if called with a hash' do subject.represent(Hash.new).should be_kind_of(subject) end it 'returns multiple entities if called with a collection' do - representation = subject.represent(4.times.map{Object.new}) + representation = subject.represent(4.times.map { Object.new }) representation.should be_kind_of Array representation.size.should == 4 - representation.reject{|r| r.kind_of?(subject)}.should be_empty + representation.reject { |r| r.kind_of?(subject) }.should be_empty end - it 'adds the :collection => true option if called with a collection' do - representation = subject.represent(4.times.map{Object.new}) - representation.each{|r| r.options[:collection].should be_true} + it 'adds the collection: true option if called with a collection' do + representation = subject.represent(4.times.map { Object.new }) + representation.each { |r| r.options[:collection].should be_true } end + + it 'returns a serialized hash of a single object if serializable: true' do + subject.expose(:awesome) { |_| true } + representation = subject.represent(Object.new, serializable: true) + representation.should == { awesome: true } + end + + it 'returns a serialized array of hashes of multiple objects if serializable: true' do + subject.expose(:awesome) { |_| true } + representation = subject.represent(2.times.map { Object.new }, serializable: true) + representation.should == [{ awesome: true }, { awesome: true }] + end end describe '.root' do context 'with singular and plural root keys' do before(:each) do @@ -176,33 +390,33 @@ end end context 'with an array of objects' do it 'allows a root element name to be specified' do - representation = subject.represent(4.times.map{Object.new}) + representation = subject.represent(4.times.map { Object.new }) representation.should be_kind_of Hash representation.should have_key 'things' representation['things'].should be_kind_of Array representation['things'].size.should == 4 - representation['things'].reject{|r| r.kind_of?(subject)}.should be_empty + representation['things'].reject { |r| r.kind_of?(subject) }.should be_empty end end context 'it can be overridden' do it 'can be disabled' do - representation = subject.represent(4.times.map{Object.new}, :root=>false) + representation = subject.represent(4.times.map { Object.new }, root: false) representation.should be_kind_of Array representation.size.should == 4 - representation.reject{|r| r.kind_of?(subject)}.should be_empty + representation.reject { |r| r.kind_of?(subject) }.should be_empty end it 'can use a different name' do - representation = subject.represent(4.times.map{Object.new}, :root=>'others') + representation = subject.represent(4.times.map { Object.new }, root: 'others') representation.should be_kind_of Hash representation.should have_key 'others' representation['others'].should be_kind_of Array representation['others'].size.should == 4 - representation['others'].reject{|r| r.kind_of?(subject)}.should be_empty + representation['others'].reject { |r| r.kind_of?(subject) }.should be_empty end end end context 'with singular root key' do @@ -219,14 +433,14 @@ end end context 'with an array of objects' do it 'allows a root element name to be specified' do - representation = subject.represent(4.times.map{Object.new}) + representation = subject.represent(4.times.map { Object.new }) representation.should be_kind_of Array representation.size.should == 4 - representation.reject{|r| r.kind_of?(subject)}.should be_empty + representation.reject { |r| r.kind_of?(subject) }.should be_empty end end end context 'with plural root key' do @@ -240,359 +454,526 @@ end end context 'with an array of objects' do it 'allows a root element name to be specified' do - representation = subject.represent(4.times.map{Object.new}) + representation = subject.represent(4.times.map { Object.new }) representation.should be_kind_of Hash representation.should have_key('things') representation['things'].should be_kind_of Array representation['things'].size.should == 4 - representation['things'].reject{|r| r.kind_of?(subject)}.should be_empty + representation['things'].reject { |r| r.kind_of?(subject) }.should be_empty end 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 - expect{ subject.new }.to raise_error(ArgumentError) - expect{ subject.new(Object.new, {}) }.not_to raise_error + expect { subject.new(Object.new) }.not_to raise_error + expect { subject.new }.to raise_error ArgumentError + expect { subject.new(Object.new, {}) }.not_to raise_error end it 'has attribute readers for the object and options' do entity = subject.new('abc', {}) entity.object.should == 'abc' entity.options.should == {} end end + end context 'instance methods' do - - let(:model){ mock(attributes) } - - let(:attributes) { { - :name => 'Bob Bobson', - :email => 'bob@example.com', - :birthday => Time.gm(2012, 2, 27), - :fantasies => ['Unicorns', 'Double Rainbows', 'Nessy'], - :friends => [ - mock(:name => "Friend 1", :email => 'friend1@example.com', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => []), - mock(:name => "Friend 2", :email => 'friend2@example.com', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => []) - ] - } } - - subject{ fresh_class.new(model) } - describe '#serializable_hash' do + let(:model) { double(attributes) } + let(:attributes) { + { + name: 'Bob Bobson', + email: 'bob@example.com', + birthday: Time.gm(2012, 2, 27), + fantasies: ['Unicorns', 'Double Rainbows', 'Nessy'], + friends: [ + double(name: "Friend 1", email: 'friend1@example.com', fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []), + double(name: "Friend 2", email: 'friend2@example.com', fantasies: [], birthday: Time.gm(2012, 2, 27), friends: []) + ] + } + } + + subject { fresh_class.new(model) } + + describe '#serializable_hash' do it 'does not throw an exception if a nil options object is passed' do - expect{ fresh_class.new(model).serializable_hash(nil) }.not_to raise_error + expect { fresh_class.new(model).serializable_hash(nil) }.not_to raise_error end it 'does not blow up when the model is nil' do fresh_class.expose :name - expect{ fresh_class.new(nil).serializable_hash }.not_to raise_error + expect { fresh_class.new(nil).serializable_hash }.not_to raise_error end - it 'does not throw an exception when an attribute is not found on the object' do - fresh_class.expose :name, :nonexistent_attribute - expect{ fresh_class.new(model).serializable_hash }.not_to raise_error - end + context "with safe option" do + 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 - fresh_class.expose :email, :nonexistent_attribute, :name + it "does not 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 - res.should have_key :email - res.should_not have_key :nonexistent_attribute - res.should have_key :name + res = fresh_class.new(model).serializable_hash + res.should have_key :email + res.should_not have_key :nonexistent_attribute + res.should have_key :name + end + + it "does not expose attributes that don't exist on the object, even with criteria" 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 } + + res = fresh_class.new(model).serializable_hash + res.should have_key :email + res.should_not have_key :nonexistent_attribute + res.should_not have_key :nonexistent_attribute2 + end end - it "does not expose attributes that don't exist on the object, even with criteria" do - fresh_class.expose :email - fresh_class.expose :nonexistent_attribute, :if => lambda { false } - fresh_class.expose :nonexistent_attribute2, :if => lambda { true } + context "without safe option" do + it 'throws an exception when an attribute is not found on the object' do + fresh_class.expose :name, :nonexistent_attribute + expect { fresh_class.new(model).serializable_hash }.to raise_error + end - res = fresh_class.new(model).serializable_hash - res.should have_key :email - res.should_not have_key :nonexistent_attribute - res.should_not have_key :nonexistent_attribute2 + it "exposes attributes that don't exist on the object only when they are generated by a block" do + fresh_class.expose :nonexistent_attribute do |model, _| + "well, I do exist after all" + end + res = fresh_class.new(model).serializable_hash + res.should 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, _| + "I exist, but it is not yet my time to shine" + }, if: lambda { |model, _| false } + res = fresh_class.new(model).serializable_hash + res.should_not have_key :nonexistent_attribute + end end - it "exposes attributes that don't exist on the object only when they are generated by a block" do - fresh_class.expose :nonexistent_attribute do |model, _| + it "exposes attributes that don't exist on the object only when they are generated by a block with options" do + module EntitySpec + class TestEntity < Grape::Entity + end + end + + fresh_class.expose :nonexistent_attribute, using: EntitySpec::TestEntity do |model, _| "well, I do exist after all" end res = fresh_class.new(model).serializable_hash res.should 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, _| + fresh_class.expose :nonexistent_attribute, proc: lambda { |model, _| "I exist, but it is not yet my time to shine" - }, :if => lambda { |model, _| false } + }, if: lambda { |model, _| false } res = fresh_class.new(model).serializable_hash res.should_not have_key :nonexistent_attribute end context '#serializable_hash' do - module EntitySpec class EmbeddedExample def serializable_hash(opts = {}) - { :abc => 'def' } + { abc: 'def' } end end + + class EmbeddedExampleWithHash + def name + "abc" + end + + def embedded + { a: nil, b: EmbeddedExample.new } + end + end + class EmbeddedExampleWithMany def name "abc" end + def embedded - [ EmbeddedExample.new, EmbeddedExample.new ] + [EmbeddedExample.new, EmbeddedExample.new] end end + class EmbeddedExampleWithOne def name "abc" end + def embedded EmbeddedExample.new end end end - + it 'serializes embedded objects which respond to #serializable_hash' do fresh_class.expose :name, :embedded presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithOne.new) - presenter.serializable_hash.should == {:name => "abc", :embedded => {:abc => "def"}} + presenter.serializable_hash.should == { name: "abc", embedded: { abc: "def" } } end it 'serializes embedded arrays of objects which respond to #serializable_hash' do fresh_class.expose :name, :embedded presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithMany.new) - presenter.serializable_hash.should == {:name => "abc", :embedded => [{:abc => "def"}, {:abc => "def"}]} + presenter.serializable_hash.should == { name: "abc", embedded: [{ abc: "def" }, { abc: "def" }] } end - + + it 'serializes embedded hashes of objects which respond to #serializable_hash' do + fresh_class.expose :name, :embedded + presenter = fresh_class.new(EntitySpec::EmbeddedExampleWithHash.new) + presenter.serializable_hash.should == { name: "abc", embedded: { a: nil, b: { abc: "def" } } } + end end - end describe '#value_for' do before do fresh_class.class_eval do expose :name, :email - expose :friends, :using => self + expose :friends, using: self expose :computed do |_, options| options[:awesome] end - expose :birthday, :format_with => :timestamp + expose :birthday, format_with: :timestamp def timestamp(date) date.strftime('%m/%d/%Y') end - expose :fantasies, :format_with => lambda {|f| f.reverse } + expose :fantasies, format_with: lambda { |f| f.reverse } end end it 'passes through bare expose attributes' do subject.send(:value_for, :name).should == attributes[:name] end it 'instantiates a representation if that is called for' do rep = subject.send(:value_for, :friends) - rep.reject{|r| r.is_a?(fresh_class)}.should be_empty + rep.reject { |r| r.is_a?(fresh_class) }.should be_empty rep.first.serializable_hash[:name].should == 'Friend 1' rep.last.serializable_hash[:name].should == 'Friend 2' end context 'child representations' do it 'disables root key name for child representations' do - module EntitySpec class FriendEntity < Grape::Entity root 'friends', 'friend' expose :name, :email end end - + fresh_class.class_eval do - expose :friends, :using => EntitySpec::FriendEntity + expose :friends, using: EntitySpec::FriendEntity end - + rep = subject.send(:value_for, :friends) rep.should be_kind_of Array - rep.reject{|r| r.is_a?(EntitySpec::FriendEntity)}.should be_empty + rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty rep.first.serializable_hash[:name].should == 'Friend 1' rep.last.serializable_hash[:name].should == 'Friend 2' end + it "passes through the proc which returns an array of objects with custom options(:using)" do + module EntitySpec + class FriendEntity < Grape::Entity + root 'friends', 'friend' + expose :name, :email + end + end + + fresh_class.class_eval do + expose :custom_friends, using: EntitySpec::FriendEntity do |user, options| + user.friends + end + end + + rep = subject.send(:value_for, :custom_friends) + rep.should be_kind_of Array + rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty + rep.first.serializable_hash.should == { name: 'Friend 1', email: 'friend1@example.com' } + rep.last.serializable_hash.should == { name: 'Friend 2', email: 'friend2@example.com' } + end + + it "passes through the proc which returns single object with custom options(:using)" do + module EntitySpec + class FriendEntity < Grape::Entity + root 'friends', 'friend' + expose :name, :email + end + end + + fresh_class.class_eval do + expose :first_friend, using: EntitySpec::FriendEntity do |user, options| + user.friends.first + end + end + + rep = subject.send(:value_for, :first_friend) + rep.should be_kind_of EntitySpec::FriendEntity + rep.serializable_hash.should == { name: 'Friend 1', email: 'friend1@example.com' } + end + + it "passes through the proc which returns empty with custom options(:using)" do + module EntitySpec + class FriendEntity < Grape::Entity + root 'friends', 'friend' + expose :name, :email + end + end + + fresh_class.class_eval do + expose :first_friend, using: EntitySpec::FriendEntity do |user, options| + + end + end + + rep = subject.send(:value_for, :first_friend) + rep.should be_kind_of EntitySpec::FriendEntity + rep.serializable_hash.should be_nil + end + it 'passes through custom options' do module EntitySpec class FriendEntity < Grape::Entity root 'friends', 'friend' expose :name - expose :email, :if => { :user_type => :admin } + expose :email, if: { user_type: :admin } end end - + fresh_class.class_eval do - expose :friends, :using => EntitySpec::FriendEntity + expose :friends, using: EntitySpec::FriendEntity end - + rep = subject.send(:value_for, :friends) rep.should be_kind_of Array - rep.reject{|r| r.is_a?(EntitySpec::FriendEntity)}.should be_empty + rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty rep.first.serializable_hash[:email].should be_nil rep.last.serializable_hash[:email].should be_nil - rep = subject.send(:value_for, :friends, { :user_type => :admin }) + rep = subject.send(:value_for, :friends, user_type: :admin) rep.should be_kind_of Array - rep.reject{|r| r.is_a?(EntitySpec::FriendEntity)}.should be_empty + rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty rep.first.serializable_hash[:email].should == 'friend1@example.com' rep.last.serializable_hash[:email].should == 'friend2@example.com' end it 'ignores the :collection parameter in the source options' do module EntitySpec class FriendEntity < Grape::Entity root 'friends', 'friend' expose :name - expose :email, :if => { :collection => true } + expose :email, if: { collection: true } end end - + fresh_class.class_eval do - expose :friends, :using => EntitySpec::FriendEntity + expose :friends, using: EntitySpec::FriendEntity end - - rep = subject.send(:value_for, :friends, { :collection => false }) + + rep = subject.send(:value_for, :friends, collection: false) rep.should be_kind_of Array - rep.reject{|r| r.is_a?(EntitySpec::FriendEntity)}.should be_empty + rep.reject { |r| r.is_a?(EntitySpec::FriendEntity) }.should be_empty rep.first.serializable_hash[:email].should == 'friend1@example.com' rep.last.serializable_hash[:email].should == 'friend2@example.com' end - end it 'calls through to the proc if there is one' do - subject.send(:value_for, :computed, :awesome => 123).should == 123 + subject.send(:value_for, :computed, awesome: 123).should == 123 end it 'returns a formatted value if format_with is passed' do subject.send(:value_for, :birthday).should == '02/27/2012' end it 'returns a formatted value if format_with is passed a lambda' do subject.send(:value_for, :fantasies).should == ['Nessy', 'Double Rainbows', 'Unicorns'] end + + it "tries instance methods on the entity first" do + module EntitySpec + class DelegatingEntity < Grape::Entity + root 'friends', 'friend' + expose :name + expose :email + + private + + def name + "cooler name" + end + end + end + + friend = double("Friend", name: "joe", email: "joe@example.com") + rep = EntitySpec::DelegatingEntity.new(friend) + rep.send(:value_for, :name).should == "cooler name" + rep.send(:value_for, :email).should == "joe@example.com" + end + + context "using" do + before do + module EntitySpec + class UserEntity < Grape::Entity + expose :name, :email + end + end + end + it "string" do + fresh_class.class_eval do + expose :friends, using: "EntitySpec::UserEntity" + end + + rep = subject.send(:value_for, :friends) + rep.should be_kind_of Array + rep.size.should == 2 + rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }.should be_true + end + + it 'class' do + fresh_class.class_eval do + expose :friends, using: EntitySpec::UserEntity + end + + rep = subject.send(:value_for, :friends) + rep.should be_kind_of Array + rep.size.should == 2 + rep.all? { |r| r.is_a?(EntitySpec::UserEntity) }.should be_true + end + end end describe '#documentation' do it 'returns an empty hash is no documentation is provided' do fresh_class.expose :name subject.documentation.should == {} end it 'returns each defined documentation hash' do - doc = {:type => "foo", :desc => "bar"} - fresh_class.expose :name, :documentation => doc - fresh_class.expose :email, :documentation => doc + doc = { type: "foo", desc: "bar" } + fresh_class.expose :name, documentation: doc + fresh_class.expose :email, documentation: doc fresh_class.expose :birthday - subject.documentation.should == {:name => doc, :email => doc} + subject.documentation.should == { name: doc, email: doc } end + + it 'returns each defined documentation hash with :as param considering' do + doc = { type: "foo", desc: "bar" } + fresh_class.expose :name, documentation: doc, as: :label + fresh_class.expose :email, documentation: doc + fresh_class.expose :birthday + + subject.documentation.should == { label: doc, email: doc } + end end describe '#key_for' do it 'returns the attribute if no :as is set' do fresh_class.expose :name - subject.send(:key_for, :name).should == :name + subject.class.send(:key_for, :name).should == :name end it 'returns a symbolized version of the attribute' do fresh_class.expose :name - subject.send(:key_for, 'name').should == :name + subject.class.send(:key_for, 'name').should == :name end it 'returns the :as alias if one exists' do - fresh_class.expose :name, :as => :nombre - subject.send(:key_for, 'name').should == :nombre + fresh_class.expose :name, as: :nombre + subject.class.send(:key_for, 'name').should == :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}} + exposure_options = { if: { condition1: true, condition2: true } } subject.send(:conditions_met?, exposure_options, {}).should be_false - subject.send(:conditions_met?, exposure_options, :condition1 => true).should be_false - subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true).should be_true - subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => true).should be_false - subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true, :other => true).should be_true + subject.send(:conditions_met?, exposure_options, condition1: true).should be_false + subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true).should be_true + subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true).should be_false + subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true).should be_true end it 'looks for presence/truthiness if a symbol is passed' do - exposure_options = {:if => :condition1} + exposure_options = { if: :condition1 } subject.send(:conditions_met?, exposure_options, {}).should be_false - subject.send(:conditions_met?, exposure_options, {:condition1 => true}).should be_true - subject.send(:conditions_met?, exposure_options, {:condition1 => false}).should be_false - subject.send(:conditions_met?, exposure_options, {:condition1 => nil}).should be_false + subject.send(:conditions_met?, exposure_options, condition1: true).should be_true + subject.send(:conditions_met?, exposure_options, condition1: false).should be_false + subject.send(:conditions_met?, exposure_options, condition1: nil).should be_false end it 'looks for absence/falsiness if a symbol is passed' do - exposure_options = {:unless => :condition1} + exposure_options = { unless: :condition1 } subject.send(:conditions_met?, exposure_options, {}).should be_true - subject.send(:conditions_met?, exposure_options, {:condition1 => true}).should be_false - subject.send(:conditions_met?, exposure_options, {:condition1 => false}).should be_true - subject.send(:conditions_met?, exposure_options, {:condition1 => nil}).should be_true + subject.send(:conditions_met?, exposure_options, condition1: true).should be_false + subject.send(:conditions_met?, exposure_options, condition1: false).should be_true + subject.send(:conditions_met?, exposure_options, condition1: nil).should 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: lambda { |_, opts| opts[:true] } } - subject.send(:conditions_met?, exposure_options, :true => false).should be_false - subject.send(:conditions_met?, exposure_options, :true => true).should be_true + subject.send(:conditions_met?, exposure_options, true: false).should be_false + subject.send(:conditions_met?, exposure_options, true: true).should be_true end it 'only passes through hash :unless exposure if any attributes do not match' do - exposure_options = {:unless => {:condition1 => true, :condition2 => true}} + exposure_options = { unless: { condition1: true, condition2: true } } subject.send(:conditions_met?, exposure_options, {}).should be_true - subject.send(:conditions_met?, exposure_options, :condition1 => true).should be_false - subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true).should be_false - subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => true).should be_false - subject.send(:conditions_met?, exposure_options, :condition1 => true, :condition2 => true, :other => true).should be_false - subject.send(:conditions_met?, exposure_options, :condition1 => false, :condition2 => false).should be_true + subject.send(:conditions_met?, exposure_options, condition1: true).should be_false + subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true).should be_false + subject.send(:conditions_met?, exposure_options, condition1: false, condition2: true).should be_false + subject.send(:conditions_met?, exposure_options, condition1: true, condition2: true, other: true).should be_false + subject.send(:conditions_met?, exposure_options, condition1: false, condition2: false).should 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: lambda { |_, options| options[:true] == true } } - subject.send(:conditions_met?, exposure_options, :true => false).should be_true - subject.send(:conditions_met?, exposure_options, :true => true).should be_false + subject.send(:conditions_met?, exposure_options, true: false).should be_true + subject.send(:conditions_met?, exposure_options, true: true).should be_false end end describe '::DSL' do - subject{ Class.new } + subject { Class.new } it 'creates an Entity class when called' do subject.should_not be_const_defined :Entity subject.send(:include, Grape::Entity::DSL) subject.should be_const_defined :Entity end context 'pre-mixed' do - before{ subject.send(:include, Grape::Entity::DSL) } + before { subject.send(:include, Grape::Entity::DSL) } it 'is able to define entity traits through DSL' do subject.entity do expose :name end @@ -611,22 +992,22 @@ end subject.entity_class.exposures.size.should == 3 end context 'instance' do - let(:instance){ subject.new } + let(:instance) { subject.new } describe '#entity' do it 'is an instance of the entity class' do instance.entity.should be_kind_of(subject.entity_class) end it 'has an object of itself' do instance.entity.object.should == instance end - it 'should instantiate with options if provided' do - instance.entity(:awesome => true).options.should == {:awesome => true} + it 'instantiates with options if provided' do + instance.entity(awesome: true).options.should == { awesome: true } end end end end end