Skip to content
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

Association DSL generates generic CollectionProxy sig #179

Closed
aergonaut opened this issue Dec 4, 2020 · 4 comments
Closed

Association DSL generates generic CollectionProxy sig #179

aergonaut opened this issue Dec 4, 2020 · 4 comments

Comments

@aergonaut
Copy link

If I have this model

class User < ApplicationRecord
  has_many :api_keys
end

When I run tapioca dsl, the user.rbi that is generated contains this:

module User::GeneratedAssociationMethods
  sig { returns(::ActiveRecord::Associations::CollectionProxy[ApiKey]) }
  def api_keys; end
end

When I run srb tc, Sorbet complains of a type error

$ bundle exec srb tc      
sorbet/rbi/dsl/user.rbi:18: Method [] does not exist on T.class_of(ActiveRecord::Associations::CollectionProxy) https://srb.help/7003
    18 |  sig { returns(::ActiveRecord::Associations::CollectionProxy[ApiKey]) }
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Did you mean:
    sorbet/rbi/gems/activerecord@6.0.3.4.rbi:2989: ActiveRecord::Delegation#[]
    2989 |  def [](*_arg0, &_arg1); end
            ^^^^^^^^^^^^^^^^^^^^^^

I also checked out the sorbet/rbi/gems/activerecord@6.0.3.4.rbi file that was generated when I ran tapioca sync, and in there CollectionProxy is only defined like

class ActiveRecord::Associations::CollectionProxy < ::ActiveRecord::Relation
  # snip
end

So I'm assuming the problem is that the type definition for CollectionProxy is not generic, but the generated user.rbi is assuming it is and trying to pass a type parameter.

This was starting from a code base with neither Sorbet nor Tapioca, running these commands:

$ bundle install
$ bundle exec tapioca init
$ bundle exec tapioca sync
$ bundle exec tapioca dsl

Did I miss some other step?

My version info:

$ bundle exec srb --version    
Sorbet typechecker 0.5.6130 git 8d32fbeb034f9f209e70223af008b5b47f181e6a debug_symbols=true clean=1
$ bundle exec tapioca --version
Tapioca v0.4.10
@aergonaut aergonaut changed the title Association DSL generate generic CollectionProxy sig Association DSL generates generic CollectionProxy sig Dec 4, 2020
@paracycle
Copy link
Member

Hello again!

Indeed, this aspect of Tapioca is, unfortunately not ready for prime-time yet! 😞 The initial implementation of our AR generators reference an internal hack that redefines ActiveRecord::Associations::CollectionProxy as a generic type which takes the model class as a type argument. That is defined in a static RBI file in our Shopify codebase and I'll add it at the end of this message.

We are trying to implement a generator for AR relations that will do this properly, but, that is not ready yet. In the meanwhile, feel free to dump the following in an RBI file and that should get you going:

class ActiveRecord::Associations::CollectionProxy < ::ActiveRecord::Relation
  extend(T::Generic)

  Elem = type_member

  sig { params(block: T.nilable(T.proc.params(arg: Elem).void)).returns(T::Enumerator[Elem]) }
  def each(&block); end

  sig do
    returns(T.untyped)
  end
  def target; end

  sig { void }
  def load_target; end

  sig { void }
  def reload; end

  sig { void }
  def reset; end

  sig { void }
  def reset_scope; end

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

  sig { params(args: T.untyped).returns(Elem) }
  def find(*args); end

  def last(limit = nil); end

  def take(limit = nil); end

  sig do
    params(attributes: ::Hash, block: T.nilable(T.proc.returns(T.untyped))).returns(Elem)
  end
  def build(attributes = {}, &block); end

  sig do
    params(attributes: ::Hash, block: T.nilable(T.proc.returns(T.untyped))).returns(Elem)
  end
  def create(attributes = {}, &block); end

  sig do
    params(attributes: ::Hash, block: T.nilable(T.proc.returns(T.untyped))).returns(Elem)
  end
  def create!(attributes = {}, &block); end

  sig { params(records: T.any(Elem, T::Array[Elem], T::Array[ActiveRecord::Associations::CollectionProxy[Elem]])).void }
  def concat(*records); end

  sig { params(other_array: T.any(T::Array[Elem], T.self_type)).void }
  def replace(other_array); end

  sig { params(dependent: ::Symbol).void }
  def delete_all(dependent = nil); end

  sig { void }
  def destroy_all; end

  sig { params(records: T.any(Elem, T::Array[Elem])).void }
  def delete(*records); end

  sig { params(records: T.any(Elem, T::Array[Elem])).void }
  def destroy(*records); end

  def calculate(operation, column_name); end

  def pluck(*column_names); end

  sig { returns(::Integer) }
  def size; end

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

  sig { params(record: Elem).returns(T::Boolean) }
  def include?(record); end

  def proxy_association; end

  def scope; end

  sig { params(other: T.any(T.self_type, T::Array[Elem])).returns(T::Boolean) }
  def ==(other); end

  sig { params(records: T.any(Elem, T::Array[Elem], T::Array[ActiveRecord::Associations::CollectionProxy[Elem]])).void }
  def <<(*records); end

  sig { params(args: Elem).void }
  def prepend(*args); end

  sig { void }
  def clear; end

  sig { params(fields: T.any(Symbol, String)).returns(ActiveRecord::Associations::CollectionProxy[Elem]) }
  def select(*fields); end
end

@aergonaut
Copy link
Author

Thank you @paracycle for the quick response! I'll try that out and see how it goes. 😁

@paracycle
Copy link
Member

Please let me know how you get on so that I can close this issue or provide more help to get you up and running.

@aergonaut
Copy link
Author

@paracycle Yes, this worked great. Thank you! Please feel free to close.

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

No branches or pull requests

2 participants