-
Notifications
You must be signed in to change notification settings - Fork 136
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
PrivateRelation
needs to be aware of class methods
#2119
PrivateRelation
needs to be aware of class methods
#2119
Conversation
7258764
to
3a2a911
Compare
3a2a911
to
38397d9
Compare
Thanks for your contribution, but this is not something Tapioca should be doing. Class methods work on relations because they are delegated to (using In the absence of such annotations, Tapioca should not be lying about class methods on models being defined as methods on relation classes. |
Thanks for your reply @paracycle. I'm confused by your comment:
Isn't this also true for scopes? And yet, Tapioca does generate these method definitions for scopes? I do agree that an annotation like How is Shopify working around the absence of types for this extremely common pattern? |
No. Scopes and class methods are treated fundamentally differently by Active Record. Scope methods and class methods look like they are equivalent, but they have fundamental difference, the main one being that scope methods always guarantee that a relation is returned, and thus, that you can chain them in a relation chain, whereas class methods don't. You can see that scopes are added as relation methods to the You can also run the following code and see that the scope is added to the relations module but the class method isn't: ❯ ruby -ractive_record -e "class Foo < ActiveRecord::Base; scope :foo, -> {}; def self.bar = 42; end; p Foo.const_get('GeneratedRelationMethods').instance_methods(false)"
[:foo]
You can either use |
Technically, a scope does not guarantee that a relation is returned. If a scope returns an integer, it'll return an integer. However, it does return a relation if the block returns
Neither of those are very appealing options. I'd either effectively lose all type safety related to relations, or I'd be manually curating a set of shims that this PR is capable of generating automatically. Is the fact that those methods aren't technically defined on the relation really important? I mean, technically What are the downsides of defining these methods on the relation? |
Alternatively you could convert class methods to scopes. My suggestions work at Shopify scale, so I am not sure why they wouldn't work for you. Additionally, what we are doing here is gradual typing. Nothing in your codebase will be fully typed, so I would caution to expect full typing guarantees from all of your codebase.
Yes, it is important. You can see how these are created here: https://github.com/rails/rails/blob/a0dfc671ad719c0fc13344c00fc62f395883ab69/activerecord/lib/active_record/relation/delegation.rb#L34-L44 ❯ irb
irb> require "active_record"
#=> true
irb> class Foo < ActiveRecord::Base; end
#=> nil
irb> Foo.const_get("ActiveRecord_Relation")
#=> Foo::ActiveRecord_Relation
irb> Foo.const_get("ActiveRecord_AssociationRelation")
#=> Foo::ActiveRecord_AssociationRelation
irb> Foo.const_get("ActiveRecord_Associations_CollectionProxy")
#=> Foo::ActiveRecord_Associations_CollectionProxy
It would cause much more churn for RBI files, with very little additional benefit. Any time someone extends a module in a model class (which happens all the time with concerns that have class methods), all the methods from that module will be regenerated on the model's relation modules needlessly. |
@paracycle what's the suggested workaround for dynamic scopes? I took a look at the edit: looks like the edit2: And that worked ✨ |
Motivation
See #2118
Implementation
This PR looks up the available class methods and copies them to the generated relation modules using
create_method_from_def
.One possible issue with this PR is how it might interact with other compilers. For example, ActiveRecord's enum defines a class method
.statuses
. The enum compiler will define a class method for this enum with types, but this compiler will also define that class method without types.A better solution would be to define a module
GeneratedClassMethods
andinclude
it explicitly inPrivateRelation
. I'm not totally sure how to achieve this.Tests
This PR includes tests.