rbs icon indicating copy to clipboard operation
rbs copied to clipboard

How to deal a module included by a specified class like ActiveRecord::Base?

Open joker1007 opened this issue 3 years ago • 6 comments

I write a concern module that supposes to be included by ActiveRecord::Base's subclass.

like following,

module A
  extend ActiveSupport::Concern
  included do
    has_many :hoge
  end

  def bar
    save
  end
end

In such a case, module A supposes to have ActiveRecord::Base methods. But, in the current RBS, developers must write all ActiveRecord::Base method signatures by themselves. Because module cannot inherit class and class cannot be included (extended).

I want a helper syntax to include method signatures of other classes whether are not modules or interfaces.

For example:

module A
  included_by ActiveRecord::Base # define method signatures with ActiveRecord::Base type
end

joker1007 avatar Sep 14 '22 07:09 joker1007

How about to use module self types?

# a.rbs
module A : ActiveRecord::Base
  extend ActiveSupport::Concern
end

It will supports A#bar signature.


As for #included, I'm still looking for a solution, but self type binding may help. It works with a few patches at hand. (need rbs 2.7.0.pre.1 and steep master)

module ActiveSupport
  module Concern
    def included: () { () [self: singleton(ActiveRecord::Base)] -> void } -> void
                | ...
  end
end

module A : ActiveRecord::Base
  extend ActiveSupport::Concern
end

However, this approach causes all ActiveSupport::Concerns#included to expect ActiveRecord::Base.

ksss avatar Sep 15 '22 01:09 ksss

Thanks!! It is very similar to what I want. I solved many check errors with self-type-binding

And, I have one more point.

ActiveSupport::Concern provides ClassMethods module technique.

module A
  module ClassMethods
    def find_by_hoge
    end
  end
end

I don't know how to bind ClassMethods with singleton(ActiveRecord::Base) or singleton(A) If you have a solution, could you please let me know?

joker1007 avatar Sep 16 '22 09:09 joker1007

@joker1007

I'm not sure if I correctly catch your question: One of the possible workarounds to support ClassMethods pattern is explicitly extending it.

class ActiveRecord::Base
  include A
  extend A::ClassMethods   # Explicitly extend it because RBS doesn't support `included` hook
end

soutaro avatar Sep 30 '22 04:09 soutaro

@soutaro Perhaps @joker1007 is asking how to specify the inner context of the ClassMethods module using self module type?(Sorry I don't know the answer...)

module A : ActiveRecord::Base
  module ClassMethods : ???
    def find_by_hoge(hoge)
      find_by(hoge: hoge) # want to check type of `find_by`
    end
  end
end

ksss avatar Oct 03 '22 04:10 ksss

@ksss Thanks! That's correct!

I want to check ClassMethods module itself and ClassMethods supposes to have class methods of ActiveRecord::Base like find_by, find_or_initialize, and ActiveRecord::Relation methods.

However, the current type checking by RBS cannot find those method signatures, so I think I need to copy the type definitions or write them myself. It is a certain amount of work.

@soutaro Is there any solution to define method signature easily?

joker1007 avatar Oct 07 '22 06:10 joker1007

@joker1007 We don't have a straightforward way to do it. You can define an interface, that would help defining the requirements on the module.

module A : ActiveRecord::Base
  interface _WithFindBy[Relation < ActiveRecord::Relation]
    def find_by: (**untyped) -> Relation
  end

  module ClassMethods[Relation < ActiveRecord::Relation] : _WithFindBy[Relation]
    def find_by_hoge(hoge): ...
  end
end

But, it doesn't look great. Looks too complicated...

We may need to let the module self types to be singleton type.

module A : ActiveRecord::Base
  module ClassMethods : singleton(ActiveRecord::Base)
    def find_by_hoge(hoge): ...
  end
end

soutaro avatar Nov 17 '22 02:11 soutaro