Skip to content

Support custom validators autoloading in Rails >= 6 #2204

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

Closed
Closed
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
27 changes: 10 additions & 17 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2021-12-05 16:55:50 UTC using RuboCop version 1.23.0.
# on 2021-12-20 18:08:03 UTC using RuboCop version 1.23.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
Expand All @@ -20,7 +20,7 @@ Lint/AmbiguousBlockAssociation:
Exclude:
- 'spec/grape/dsl/routing_spec.rb'

# Offense count: 41
# Offense count: 40
# Configuration parameters: AllowedMethods.
# AllowedMethods: enums
Lint/ConstantDefinitionInBlock:
Expand Down Expand Up @@ -72,7 +72,7 @@ Metrics/BlockLength:
# Offense count: 9
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 298
Max: 305

# Offense count: 30
# Configuration parameters: IgnoredMethods.
Expand Down Expand Up @@ -173,13 +173,7 @@ RSpec/EmptyExampleGroup:
- 'spec/grape/dsl/configuration_spec.rb'
- 'spec/grape/validations/attributes_iterator_spec.rb'

# Offense count: 1
# Cop supports --auto-correct.
RSpec/EmptyLineAfterSubject:
Exclude:
- 'spec/grape/dsl/logger_spec.rb'

# Offense count: 500
# Offense count: 501
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
Max: 57
Expand Down Expand Up @@ -225,7 +219,7 @@ RSpec/IteratedExpectation:
Exclude:
- 'spec/grape/middleware/formatter_spec.rb'

# Offense count: 90
# Offense count: 84
RSpec/LeakyConstantDeclaration:
Enabled: false

Expand All @@ -250,16 +244,16 @@ RSpec/MissingExampleGroupArgument:
Exclude:
- 'spec/grape/middleware/exception_spec.rb'

# Offense count: 755
# Offense count: 756
RSpec/MultipleExpectations:
Max: 16

# Offense count: 11
# Offense count: 32
# Configuration parameters: AllowSubject.
RSpec/MultipleMemoizedHelpers:
Max: 10

# Offense count: 2118
# Offense count: 2121
# Configuration parameters: IgnoreSharedExamples.
RSpec/NamedSubject:
Enabled: false
Expand Down Expand Up @@ -318,13 +312,12 @@ RSpec/SubjectStub:
- 'spec/grape/middleware/stack_spec.rb'
- 'spec/grape/parser_spec.rb'

# Offense count: 25
# Offense count: 24
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles:
Exclude:
- 'spec/grape/api_spec.rb'
- 'spec/grape/dsl/inside_route_spec.rb'
- 'spec/grape/dsl/logger_spec.rb'
- 'spec/grape/integration/rack_sendfile_spec.rb'
- 'spec/grape/middleware/formatter_spec.rb'
- 'spec/grape/validations/multiple_attributes_iterator_spec.rb'
Expand Down Expand Up @@ -364,7 +357,7 @@ Style/OptionalBooleanParameter:
- 'lib/grape/validations/types/primitive_coercer.rb'
- 'lib/grape/validations/types/set_coercer.rb'

# Offense count: 146
# Offense count: 145
# Cop supports --auto-correct.
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Expand Down
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

* [#2196](https://github.com/ruby-grape/grape/pull/2196): Add support for `passwords_hashed` param for `digest_auth` - [@lHydra](https://github.com/lhydra).
* [#2204](https://github.com/ruby-grape/grape/pull/2204): Support custom validators autoloading in Rails 6 - [@dm1try](https://github.com/dm1try).
* Your contribution here.

#### Fixes
Expand Down
2 changes: 1 addition & 1 deletion lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,5 @@ module ServeStream
require 'grape/validations/validators/all_or_none'
require 'grape/validations/types'
require 'grape/validations/validator_factory'

require 'grape/railtie' if defined?(Rails::Railtie)
require 'grape/version'
2 changes: 2 additions & 0 deletions lib/grape/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Config
class Configuration
ATTRIBUTES = %i[
param_builder
on_unknown_validator
].freeze

attr_accessor(*ATTRIBUTES)
Expand All @@ -15,6 +16,7 @@ def initialize

def reset
self.param_builder = Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
self.on_unknown_validator = []
end
end

Expand Down
26 changes: 26 additions & 0 deletions lib/grape/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Grape
class Railtie < Rails::Railtie
if Rails.version.to_f >= 6.0
initializer 'grape, setup zeitwerk loader for custom validators' do |_app|
Grape.config.on_unknown_validator << method(:on_grape_unknown_validator) if Rails.autoloaders.zeitwerk_enabled?
end
end

def on_grape_unknown_validator(name)
Copy link
Member

Choose a reason for hiding this comment

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

If the config option is validator, this also can just be validator since it exists in the Grape::Railtie namespace.

validator = ::Grape::Validations::Validators.const_get(name.camelize)

unless Rails.application.config.eager_load
# support reloading validators in development
Rails.autoloaders.main.on_unload(validator.to_s) do
::Grape::Validations.deregister_validator(name)
end
end

validator
rescue NameError
nil
end
end
end
10 changes: 9 additions & 1 deletion lib/grape/validations/params_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ def check_incompatible_option_values(default, values, except_values, excepts)
end

def validate(type, options, attrs, doc_attrs, opts)
validator_class = Validations.validators[type.to_s]
validator_class = find_validator_class(type.to_s)

raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class

Expand Down Expand Up @@ -497,6 +497,14 @@ def document_attribute(attrs, doc_attrs)
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
@api.document_attribute(full_attrs, doc_attrs)
end

def find_validator_class(name)
Copy link
Member

Choose a reason for hiding this comment

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

def validator(name)

Validations.validators[name] ||
::Grape.config.on_unknown_validator.find do |callback|
result = callback.call(name)
break result if result
end
end
end
end
end
37 changes: 37 additions & 0 deletions spec/grape/validations/params_scope_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1188,4 +1188,41 @@ def initialize(value)
end
end
end

context 'with unknown validator' do
it 'raises an error' do
expect do
subject.params do
optional :id, unknown: true
end
end.to raise_error(Grape::Exceptions::UnknownValidator)
end

context 'with a set callback' do
before do
Grape.config.on_unknown_validator << lambda { |_validator_name|
Class.new(Grape::Validations::Validators::Base) do
def validate_param!(attr_name, params)
params[attr_name] = 'custom_validator_implementation'
end
end
}
end

after do
Grape.config.on_unknown_validator.clear
end

it 'calls the callback' do
subject.params do
optional :id, unknown: true
end
subject.get('test') { params[:id] }

get 'test', id: 'test_validator'
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('custom_validator_implementation')
end
end
end
end