Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

delete a reversible stackable values class #1953

Merged
merged 2 commits into from
Jan 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#### Features

* Your contribution here.
* [#1953](https://github.com/ruby-grape/grape/pull/1953): Delete a reversible stackable values class - [@dnesteryuk](https://github.com/dnesteryuk).
* [#1949](https://github.com/ruby-grape/grape/pull/1949): Add support for Ruby 2.7 - [@nbulaj](https://github.com/nbulaj).
* [#1948](https://github.com/ruby-grape/grape/pull/1948): Relax `dry-types` dependency version - [@nbulaj](https://github.com/nbulaj).
* [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx).
Expand Down
1 change: 0 additions & 1 deletion lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ module Util
eager_autoload do
autoload :InheritableValues
autoload :StackableValues
autoload :ReverseStackableValues
autoload :InheritableSetting
autoload :StrictHashConfiguration
autoload :Registrable
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/dsl/request_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def rescue_from(*args, &block)
:base_only_rescue_handlers
end

namespace_reverse_stackable handler_type, Hash[args.map { |arg| [arg, handler] }]
namespace_stackable handler_type, Hash[args.map { |arg| [arg, handler] }]
end

namespace_stackable(:rescue_options, options)
Expand Down
15 changes: 8 additions & 7 deletions lib/grape/dsl/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,18 @@ def namespace_stackable(key, value = nil)
get_or_set :namespace_stackable, key, value
end

def namespace_reverse_stackable(key, value = nil)
get_or_set :namespace_reverse_stackable, key, value
end

def namespace_stackable_with_hash(key)
settings = get_or_set :namespace_stackable, key, nil
return if settings.blank?
settings.each_with_object({}) { |value, result| result.deep_merge!(value) }
end

def namespace_reverse_stackable_with_hash(key)
settings = get_or_set :namespace_reverse_stackable, key, nil
settings = get_or_set :namespace_stackable, key, nil
return if settings.blank?

result = {}
settings.each do |setting|
settings.reverse_each do |setting|
setting.each do |field, value|
result[field] ||= value
end
Expand Down Expand Up @@ -171,7 +168,11 @@ def within_namespace(&_block)
# the superclass's :inheritable_setting.
def build_top_level_setting
Grape::Util::InheritableSetting.new.tap do |setting|
if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API
# Doesn't try to inherit settings from +Grape::API::Instance+ which also responds to
# +inheritable_setting+, however, it doesn't contain any user-defined settings.
# Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+
# in the chain for every endpoint.
if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a bug fix here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I forgot to mention this one. Grape::API doesn't make use of Grape::Util::InheritableSetting, but Grape::API::Instance does.

irb(main):006:0> Grape::API.respond_to?(:inheritable_setting)
=> false
irb(main):007:0> Grape::API::Instance.respond_to?(:inheritable_setting)
=> true

Thus, there is no reason to expect Grape::API.

The condition was added when Grape::API worked with Grape::Util::InheritableSetting.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that part, but without digging into details it's a code change has a side effect. So either we're missing a spec or the entire if is unnecessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It didn't cause big harm, however, a test for catching the regression makes sense 👍 Please, have a look! 🙂

setting.inherit_from superclass.inheritable_setting
end
end
Expand Down
20 changes: 2 additions & 18 deletions lib/grape/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -386,24 +386,8 @@ def run_filters(filters, type = :other)
extend post_extension if post_extension
end

def befores
namespace_stackable(:befores) || []
end

def before_validations
namespace_stackable(:before_validations) || []
end

def after_validations
namespace_stackable(:after_validations) || []
end

def afters
namespace_stackable(:afters) || []
end

def finallies
namespace_stackable(:finallies) || []
%i[befores before_validations after_validations afters finallies].each do |meth_id|
define_method(meth_id) { namespace_stackable(meth_id) }
end

def validations
Expand Down
8 changes: 2 additions & 6 deletions lib/grape/util/inheritable_setting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Util
# and inheritable values (see InheritableValues and StackableValues).
class InheritableSetting
attr_accessor :route, :api_class, :namespace
attr_accessor :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable
attr_accessor :namespace_inheritable, :namespace_stackable
attr_accessor :parent, :point_in_time_copies

# Retrieve global settings.
Expand All @@ -31,7 +31,6 @@ def initialize
# used with a mount, or should every API::Class be a separate namespace by default?
self.namespace_inheritable = InheritableValues.new
self.namespace_stackable = StackableValues.new
self.namespace_reverse_stackable = ReverseStackableValues.new

self.point_in_time_copies = []

Expand All @@ -54,7 +53,6 @@ def inherit_from(parent)

namespace_inheritable.inherited_values = parent.namespace_inheritable
namespace_stackable.inherited_values = parent.namespace_stackable
namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable
self.route = parent.route.merge(route)

point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent }
Expand All @@ -72,7 +70,6 @@ def point_in_time_copy
new_setting.namespace = namespace.clone
new_setting.namespace_inheritable = namespace_inheritable.clone
new_setting.namespace_stackable = namespace_stackable.clone
new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone
new_setting.route = route.clone
new_setting.api_class = api_class

Expand All @@ -93,8 +90,7 @@ def to_hash
route: route.clone,
namespace: namespace.to_hash,
namespace_inheritable: namespace_inheritable.to_hash,
namespace_stackable: namespace_stackable.to_hash,
namespace_reverse_stackable: namespace_reverse_stackable.to_hash
namespace_stackable: namespace_stackable.to_hash
}
end
end
Expand Down
18 changes: 0 additions & 18 deletions lib/grape/util/reverse_stackable_values.rb

This file was deleted.

1 change: 1 addition & 0 deletions lib/grape/util/stackable_values.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def initialize(*_args)
@frozen_values = {}
end

# Even if there is no value, an empty array will be returned.
def [](name)
return @frozen_values[name] if @frozen_values.key? name

Expand Down
3 changes: 2 additions & 1 deletion lib/grape/validations/attributes_iterator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def initialize(validator, scope, params)
@scope = scope
@attrs = validator.attrs
@original_params = scope.params(params)
@array_given = @original_params.is_a?(Array)
@params = Array.wrap(@original_params)
end

Expand All @@ -30,7 +31,7 @@ def do_each(params_to_process, parent_indicies = [], &block)
end

if @scope.type == Array
next unless @original_params.is_a?(Array) # do not validate content of array if it isn't array
next unless @array_given # do not validate content of array if it isn't array

# fill current and parent scopes with correct array indicies
parent_scope = @scope.parent
Expand Down
10 changes: 6 additions & 4 deletions lib/grape/validations/params_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ def configuration
# @return [Boolean] whether or not this entire scope needs to be
# validated
def should_validate?(parameters)
return false if @optional && (params(parameters).blank? || all_element_blank?(parameters))
return false unless meets_dependency?(params(parameters), parameters)
scoped_params = params(parameters)

return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
return false unless meets_dependency?(scoped_params, parameters)
return true if parent.nil?
parent.should_validate?(parameters)
end
Expand Down Expand Up @@ -446,8 +448,8 @@ def options_key?(type, key, validations)
validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil?
end

def all_element_blank?(parameters)
params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
def all_element_blank?(scoped_params)
scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?)
end

# Validators don't have access to each other and they don't need, however,
Expand Down
6 changes: 5 additions & 1 deletion lib/grape/validations/types/build_coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ def self.cache_instance(type, method, strict, &_block)
end

def self.cache_key(type, method, strict)
[type, method, strict].compact.map(&:to_s).join('_')
[type, method, strict].each_with_object(+'') do |val, memo|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be _ to avoid fo_obar and foo_bar type situations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess joining '_' is unnecessary since the key is meant for "machines" here. Do you think it is necessary? 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we 100% sure that type/method/strict don't have the situation like type=fo + method=obar vs. type=foo + method=bar, it would generate the same key. So I think better safe than sorry.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I've changed it.

next if val.nil?

memo << '_' << val.to_s
end
end

instance_variable_set(:@__cache, {})
Expand Down
6 changes: 6 additions & 0 deletions spec/grape/api/instance_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,10 @@ def app
expect(last_response.body).to eq 'success'
end
end

context 'top level setting' do
it 'does not inherit settings from the superclass (Grape::API::Instance)' do
expect(an_instance.top_level_setting.parent).to be_nil
end
end
end
10 changes: 5 additions & 5 deletions spec/grape/dsl/request_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,34 +161,34 @@ def self.imbue(key, value)

describe 'list of exceptions is passed' do
it 'sets hash of exceptions as rescue handlers' do
expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => nil)
expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => nil)
expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
subject.rescue_from StandardError
end

it 'rescues only base handlers if rescue_subclasses: false option is passed' do
expect(subject).to receive(:namespace_reverse_stackable).with(:base_only_rescue_handlers, StandardError => nil)
expect(subject).to receive(:namespace_stackable).with(:base_only_rescue_handlers, StandardError => nil)
expect(subject).to receive(:namespace_stackable).with(:rescue_options, rescue_subclasses: false)
subject.rescue_from StandardError, rescue_subclasses: false
end

it 'sets given proc as rescue handler for each key in hash' do
rescue_handler_proc = proc {}
expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc)
expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc)
expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
subject.rescue_from StandardError, rescue_handler_proc
end

it 'sets given block as rescue handler for each key in hash' do
rescue_handler_proc = proc {}
expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc)
expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc)
expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
subject.rescue_from StandardError, &rescue_handler_proc
end

it 'sets a rescue handler declared through :with option for each key in hash' do
with_block = -> { 'hello' }
expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => an_instance_of(Proc))
expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => an_instance_of(Proc))
expect(subject).to receive(:namespace_stackable).with(:rescue_options, {})
subject.rescue_from StandardError, with: with_block
end
Expand Down
23 changes: 0 additions & 23 deletions spec/grape/util/inheritable_setting_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ module Util
settings.namespace[:namespace_thing] = :namespace_foo_bar
settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar
settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar
settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar
settings.route[:route_thing] = :route_foo_bar
end
end
Expand All @@ -24,7 +23,6 @@ module Util
settings.namespace[:namespace_thing] = :namespace_foo_bar_other
settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar_other
settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar_other
settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar_other
settings.route[:route_thing] = :route_foo_bar_other
end
end
Expand Down Expand Up @@ -122,16 +120,6 @@ module Util
end
end

describe '#namespace_reverse_stackable' do
it 'works with reverse stackable values' do
expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]

subject.inherit_from other_parent

expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar_other]
end
end

describe '#route' do
it 'sets a value until the next route' do
subject.route[:some_thing] = :foo_bar
Expand Down Expand Up @@ -198,14 +186,6 @@ module Util
expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]
end

it 'decouples namespace reverse stackable values' do
expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]

subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :other_thing
expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq %i[other_thing namespace_reverse_stackable_foo_bar]
expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]
end

it 'decouples route values' do
expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar

Expand All @@ -224,7 +204,6 @@ module Util
subject.namespace[:namespace_thing] = :namespace_foo_bar
subject.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar
subject.namespace_stackable[:namespace_stackable_thing] = [:namespace_stackable_foo_bar]
subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = [:namespace_reverse_stackable_foo_bar]
subject.route[:route_thing] = :route_foo_bar

expect(subject.to_hash).to include(global: { global_thing: :global_foo_bar })
Expand All @@ -233,8 +212,6 @@ module Util
namespace_inheritable_thing: :namespace_inheritable_foo_bar
})
expect(subject.to_hash).to include(namespace_stackable: { namespace_stackable_thing: [:namespace_stackable_foo_bar, [:namespace_stackable_foo_bar]] })
expect(subject.to_hash).to include(namespace_reverse_stackable:
{ namespace_reverse_stackable_thing: [[:namespace_reverse_stackable_foo_bar], :namespace_reverse_stackable_foo_bar] })
expect(subject.to_hash).to include(route: { route_thing: :route_foo_bar })
end
end
Expand Down
Loading