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

Conversation

dm1try
Copy link
Member

@dm1try dm1try commented Dec 19, 2021

The problem: When some Grape application is mounted to Rails the default Rails autoloading rules cannot be applied to the custom validators because the validator class name is not involved when we use the params DSL, ex. requires :id, custom: true but the autoloader relies on a class name to a file path mapping(Custom => some/path/custom.rb).

This PR solves the mentioned problem for Rails >= 6.0 where the default autoloader is zeitwerk.
It solves the problem in two steps:

  1. we allow to define the callback for loading a validator class when the validator is not "registered", so users can create own autoloading mechanism instead of using an explicit require each time.
Grape.config.on_uknown_validator << ->(validator_name) {
   require "validators/#{validator_name}"
   ::Validators.const_get(validator_name.camelcase)
 }
# validators/custom.rb
module Validators
      class Custom < Grape::Validations::Validators::Base
      ...
# app/api.rb
  ...
  requires :id, custom: true
  1. we add the Railtie to be able to hook into a default Rails autoloader(zeitwerk), we check if it's enabled first, then add a specific code for lookup a validator class name based on its DSL name(ex. custom => Grape::Validations::Validators::Custom) and add a hook for de-registering validators on reloading(mostly related to a development environment, where the files are lazy loaded/unloaded). The implementation can be certainly extended for different loaders/Rails versions.

the example, to use custom: true in params DSL
you should put the implementation in app/lib/grape/validations/validator/custom.rb of your Rails app(it looks like a legit way)

# app/lib/grape/validations/validator/custom.rb
module Grape
  module Validations
    module Validators
      class Custom < Base
        def validate_param!(attr_name, params)
          params[attr_name] = 'my_validator_did_this'
        end
      end
    end
  end
end

ref #1178
supersedes #1186

  • acceptance test (maybe some bash script :) not sure)
  • changelog
  • make rubocop happy

@dm1try dm1try requested a review from dblock December 19, 2021 23:50
@dm1try dm1try force-pushed the custom_validators_with_zeitwerk branch 2 times, most recently from 4088f4e to 46e86d0 Compare December 20, 2021 18:14
setup Railtie that joins Grape and Rails main autoloader

for example, to use "custom: true" in params DSL
you should put the implementation in app/lib/grape/validations/validator/custom.rb
@dm1try dm1try force-pushed the custom_validators_with_zeitwerk branch from 46e86d0 to 7a1a549 Compare December 21, 2021 00:16
Copy link
Member

@dblock dblock left a comment

Choose a reason for hiding this comment

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

I haven't worked with Rails for a long time, so I think I'm missing a lot of context.

I see we're injecting Rails::Railtie early. That alone deserves its own PR as it clearly will have some impact on Grape loaded with Rails. I would like to understand the implications, see updated documentation, possibly integration tests and understand why it's awesome.

Next, what's wrong with Grape current custom validators? Why would I want Rails in the way?
Sounds like we just want to auto-load validators from Rails validators?

Finally, why and what is Zeitwerk and why should Grape care?

@dm1try dm1try changed the title Custom validators with zeitwerk(Rails) Support custom validators autoloading in Rails >= 6 Dec 21, 2021
@dm1try
Copy link
Member Author

dm1try commented Dec 21, 2021

@dblock good points, I updated the description.

I see we're injecting Rails::Railtie early. That alone deserves its own PR as it clearly will have some impact on Grape loaded with Rails. I would like to understand the implications, see updated documentation, possibly integration tests and understand why it's awesome.

I could split the PR, but the parts do not worth much separately.

Next, what's wrong with Grape current custom validators? Why would I want Rails in the way? Sounds like we just want to auto-load validators from Rails validators?

I think, it's about the consistency. When you are working with Grape on Rails and all your code is loaded implicitly but not custom validators code it feels inconsistent and raises the issues like #1178.

Finally, why and what is Zeitwerk and why should Grape care?

yeah) the code could be separated to own gem for sure.

actually, I do not care much about this PR. I recently found some incomplete PRs in my Github profile and it was the try to complete some of them) not a big deal at all. just some Ruby practice.

Copy link
Member

@dblock dblock left a comment

Choose a reason for hiding this comment

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

Thanks @dm1try, let's get this over the line?

  • Add an integration with Rails 6 into https://github.com/ruby-grape/grape/tree/master/spec/integration that has a custom validator.
  • I'd like the naming to be validator, but let me know if you have strong opinions why it shouldn't be that short. It's easy to name something and it's really hard to change it, so worth another conversation.

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.

@@ -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)

@dm1try
Copy link
Member Author

dm1try commented Dec 28, 2021

I'd like the naming to be validator, but let me know if you have strong opinions why it shouldn't be that short. It's easy to name something and it's really hard to change it, so worth another conversation.

in my opinion, the name should point that this is some callback/-s but not just some property.
so either on_something(when something have happened) or something_missing(when something have missed, like Ruby's const_missing) are more preferable options.

@dblock
Copy link
Member

dblock commented Dec 28, 2021

I'd like the naming to be validator, but let me know if you have strong opinions why it shouldn't be that short. It's easy to name something and it's really hard to change it, so worth another conversation.

in my opinion, the name should point that this is some callback/-s but not just some property. so either on_something(when something have happened) or something_missing(when something have missed, like Ruby's const_missing) are more preferable options.

Ok, I'm cool with it. LMK when this is rebased and ready to merge.

@dblock
Copy link
Member

dblock commented Mar 29, 2022

Interested in finishing this @dm1try ?

@dm1try
Copy link
Member Author

dm1try commented Apr 2, 2022

@dblock nope, let's close it for now.
anyway this issue should be resolved in conjunction with other issues related to the new Rails autoloader(ex. #2238) and maybe separated as a gem.

@dm1try dm1try closed this Apr 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants