Alex Ghiculescu
Alex Ghiculescu

Reputation: 7540

Sorbet: how do you add a signature for a dynamically generated method?

I'm using https://github.com/kenn/active_flag and https://github.com/chanzuckerberg/sorbet-rails

This is what its rbi looks like:

module ActiveFlag
  extend ActiveSupport::Concern
end
class ActiveFlag::Definition
  def column; end
  def human(key, options = nil); end
  def humans; end
  def initialize(column, keys, klass); end
  def keys; end
  def maps; end
  def pairs; end
  def set_all!(key); end
  def to_array(integer); end
  def to_i(arg); end
  def to_value(instance, integer); end
  def unset_all!(key); end
end
class ActiveFlag::Railtie < Rails::Railtie
end
class ActiveFlag::Value < Set
  def method_missing(symbol, *args, &block); end
  def raw; end
  def set!(key, options = nil); end
  def set(key); end
  def set?(key); end
  def to_human; end
  def to_s; end
  def unset!(key, options = nil); end
  def unset(key); end
  def unset?(key); end
  def with(instance, definition); end
end
module ActiveFlag::ClassMethods
  def flag(column, keys); end
end
class ActiveRecord::Base
  extend ActiveFlag::ClassMethods
end

The last bit (extending AR::Base) I added, the rest srb rbi gems generated automatically.

To actually use Active Flag, I then do this in my model:

flag :visible_to, [:employee, :manager, :admin]

visible_to is an integer column. Sorbet Rails has already typed it as such:

  sig { returns(Integer) }
  def visible_to; end

  sig { params(value: Integer).void }
  def visible_to=(value); end

  sig { returns(T::Boolean) }
  def visible_to?; end

I could change this definition, but it's an autogenerated file and I assume the changes will get lost. So the next thing I tried was adding a sig directly above where the method gets used:

  sig { returns(ActiveFlag::Value) }
  def visible_to; end
  flag :visible_to, [:employee, :manager, :admin]

The problem here is that Sorbet fails because the method I've "defined" doesn't return anything. Which I know is fine, because it'll get overriden when flag is called (it uses define_method internally), but I don't know how to communicate this to Sorbet.

Returning value that does not conform to method result type https://srb.help/7005
    54 |  def visible_to; end
          ^^^^^^^^^^^^^^^^^^^
  Expected ActiveFlag::Value
    Method visible_to has return type ActiveFlag::Value
    54 |  def visible_to; end
          ^^^^^^^^^^^^^^
  Got NilClass originating from:
    54 |  def visible_to; e

So my question is. What's the best way to tell Sorbet that this method will return an ActiveFlag::Value once it gets defined, ideally without making a manual change to an autogenerated file?

btw. I tried to see how types for enum in Rails are handled... it doesn't look like that's been done on sorbet-typed yet. Possibly for the same reason.

Upvotes: 1

Views: 2483

Answers (2)

hdoan
hdoan

Reputation: 377

You could implement a custom plugin for sorbet-rails to generate methods added by active_flag and remove the sigs generated wrongly. Here is the documentation for it:

https://github.com/chanzuckerberg/sorbet-rails/blob/master/README.md#extending-model-generation-task-with-custom-plugins

Upvotes: 0

wsp260
wsp260

Reputation: 36

When you want to override a generated rbi file you can create a second rbi file for the same class in your workspace and move the methods in there. Sorbet will handle merging multiple definitions files for you. We keep these files in a separate sorbet/custom directory next to the generated files; others keep the rbi files next to the rb files in their application directory.

As to enums, Sorbet does not natively support enum literal types so that's likely why.

Upvotes: 2

Related Questions