Search code examples
ruby-on-railssorbet

Sorbet can't find `validates` method when including `ActiveModel::Model`


I'm using ActiveModel to validate some form objects in my Ruby on Rails. A simplified reproducible example looks like this:

# typed: true

class Form
  include ActiveModel::Model
  
  # ...

  validates :name, presence: true
end

The issue is, Sorbet complains that validates does not exist on T.class_of(Form). Even though the RBI files are properly generated for the ActiveModel::Model module.


Solution

  • The reason why you are getting this error is because it is really hard for Sorbet to understand the mechanics of what ActiveSupport::Concern does.

    What is happening here is that when include ActiveModel::Model is called, it then includes ActiveModel::Validations. But since both ActiveModel::Model and ActiveModel::Validations are concerns, both ActiveModel::Model::ClassMethods and ActiveModel::Validations::ClassMethods are added to Form using extend. It is the ActiveModel::Validations::ClassMethods that provides the validates method, and that extend is what Sorbet cannot see statically.

    First of all, the basic srb tooling has no knowledge of ActiveSupport::Concern and will not generate proper mixes_in_class_methods calls to make Sorbet aware of the ClassMethods that are also in play. Moreover, even if that was generated, it would only apply at one level of include and would fail in this case.

    The workaround is to explicitly add an extend ActiveModel::Validations::ClassMethods in Form, but that is ugly.

    The best solution is to switch over to Tapioca tooling to generate proper RBI output for concerns. We will also soon start generating a solution for nested concerns like this, which should solve this properly.