From 30aa9703b53c2e70f532d933511c1bd80f208c88 Mon Sep 17 00:00:00 2001 From: Vladimir Kochnev Date: Mon, 3 Aug 2015 12:00:32 +0300 Subject: [PATCH] Fixes issue with class reopening and inheriting. Exposures and formatters should appear in inherited classes even if added after inheriting. --- .rubocop_todo.yml | 10 +++---- lib/grape_entity/entity.rb | 13 +++++++++ spec/grape_entity/entity_spec.rb | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9d0ce88f..6618152b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-08-02 19:30:25 +0300 using RuboCop version 0.31.0. +# on 2015-08-07 13:25:47 +0300 using RuboCop version 0.31.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -7,18 +7,18 @@ # Offense count: 6 Metrics/AbcSize: - Max: 33 + Max: 36 # Offense count: 2 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 198 + Max: 208 # Offense count: 3 Metrics/CyclomaticComplexity: Max: 11 -# Offense count: 208 +# Offense count: 209 # Configuration parameters: AllowURI, URISchemes. Metrics/LineLength: Max: 146 @@ -26,7 +26,7 @@ Metrics/LineLength: # Offense count: 8 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 28 + Max: 31 # Offense count: 5 Metrics/PerceivedComplexity: diff --git a/lib/grape_entity/entity.rb b/lib/grape_entity/entity.rb index e455323d..08c2eb08 100644 --- a/lib/grape_entity/entity.rb +++ b/lib/grape_entity/entity.rb @@ -103,11 +103,16 @@ class << self # Returns all formatters that are registered for this and it's ancestors # @return [Hash] of formatters attr_accessor :formatters + attr_accessor :inherited_entities end + @inherited_entities = [] + def self.inherited(subclass) subclass.root_exposure = root_exposure.try(:dup) || build_root_exposure subclass.formatters = formatters.try(:dup) || {} + inherited_entities << subclass + subclass.inherited_entities = [] end # This method is the primary means by which you will declare what attributes @@ -173,6 +178,10 @@ def self.expose(*args, &block) @nesting_stack.pop end end + + inherited_entities.each do |entity| + entity.expose(*args, &block) + end end def self.build_root_exposure @@ -264,6 +273,10 @@ def self.documentation def self.format_with(name, &block) fail ArgumentError, 'You must pass a block for formatters' unless block_given? formatters[name.to_sym] = block + + inherited_entities.each do |entity| + entity.format_with(name, &block) + end end # This allows you to set a root element name for your representation. diff --git a/spec/grape_entity/entity_spec.rb b/spec/grape_entity/entity_spec.rb index 62a95ef5..292fb9ff 100644 --- a/spec/grape_entity/entity_spec.rb +++ b/spec/grape_entity/entity_spec.rb @@ -272,6 +272,47 @@ class Parent < Person 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 + + describe 'parent class reopening' do + it 'is supported' do + subject.expose :name + child_class = Class.new(subject) + subject.expose :email + + object = OpenStruct.new(name: 'bar', email: 'foo@bar') + expected = { name: 'bar', email: 'foo@bar' } + + expect(subject.represent(object, serializable: true)).to eq(expected) + expect(child_class.represent(object, serializable: true)).to eq(expected) + end + + it 'puts exposures in the right order' do + subject.expose :x + child_class = Class.new(subject) do + expose :z + end + subject.expose :y + object = { + x: 1, + y: 2, + z: 3 + } + expect(child_class.represent(object, serializable: true).keys).to eq([:x, :y, :z]) + end + + it 'just prepends parent class exposures to the inherited class' do + subject.expose :x + child_class = Class.new(subject) do + expose :y, proc: -> (_obj, _opts) { 'specific' } + end + subject.expose :y + object = { + x: 1, + y: 2 + } + expect(child_class.represent(object, serializable: true)).to eq(x: 1, y: 'specific') + end + end end context 'register formatters' do @@ -290,6 +331,13 @@ class Parent < Person expect(child_class.formatters).to eq subject.formatters end + it 'inherits formatters from ancestors with reopening' do + child_class = Class.new(subject) + subject.format_with :timestamp, &date_formatter + + expect(child_class.formatters).to eq subject.formatters + end + it 'does not allow registering a formatter without a block' do expect { subject.format_with :foo }.to raise_error ArgumentError end