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

Type the rails find method #1145

Closed
wants to merge 1 commit into from

Conversation

richard-thatch
Copy link
Contributor

Motivation

For find_by, we have no problem inferring that it will return
T.nilable(constant_name), however we eject from trying to type find. Per
https://github.com/rails/rails/blob/33100212244efbd1adf21587e1e37090fdd3591d/activerecord/lib/active_record/relation/finder_methods.rb#L13-L19
(current main revision at time of authoriship)

We should be able to provide something a bit better, even if developers
have to T.cast when they know they're not getting an array back.

I have zero idea if there was a specific reason for T.untyped in this
regard, but I thought I'd contribute this as a suggestion if this is
tenable, or a question if it is not so I can better understand.

Motivation

Implementation

Instead of having ActiveRecord::Base::find return T.untyped, have it return the constant it belongs to similar to find_by, or an array of those constants per the documentation and code on rails/rails

Tests

I added the test I thought relevant, the tests take a while so I'll check branch CI for anything else I've missed.

For find_by, we have no problem inferring that it will return
T.nilable(constant_name), however we eject from trying to type find. Per
https://github.com/rails/rails/blob/33100212244efbd1adf21587e1e37090fdd3591d/activerecord/lib/active_record/relation/finder_methods.rb#L13-L19
(current main revision at time of authoriship)

We should be able to provide something a bit better, even if developers
have to T.cast when they know they're not getting an array back.

I have _zero_ idea if there was a specific reason for T.untyped in this
regard, but I thought I'd contribute this as a suggestion if this is
tenable, or a question if it is not so I can better understand.
@richard-thatch richard-thatch requested a review from a team as a code owner September 1, 2022 22:22
@rafaelfranca
Copy link
Member

See #1143

Duplicate of #1089

@richard-thatch richard-thatch deleted the typed_finder branch September 1, 2022 22:25
@richard-thatch
Copy link
Contributor Author

Thanks Rafael!

@rafaelfranca
Copy link
Member

Thank you for the pull request. I wonder if we have a better way to document this decision. Maybe we should document in the manual for this generator and expose the manual in the generated rbi?

@richard-thatch
Copy link
Contributor Author

I did read the manual, and I did do a quick search before asking and looking (sorry I didn't find it haha). Could also be an inline comment on the generator itself where the method is defined.

I think because it's so odd, including it in the RBI isn't such a bad idea! But I don't know if that's a current practice.

@rafaelfranca
Copy link
Member

This info isn't in the manual though, so maybe we should start adding it there?

@richard-thatch
Copy link
Contributor Author

richard-thatch commented Sep 1, 2022

Just to avoid necro-ing the other thread really quick. I did expect this to introduce a type error when using Model.find(1). The PR you linked noted that this was not ideal (I agree, re the future overloaded types stuff), but I was prepared to have to do the following:

Original sorbet run

What I would expect to have to do: sorbet run

I know that this isn't ideal, but we have to do exactly this for ActionController::Parameters, as it can return three different types. Is the decision here that this extra friction isn't worth having the value typed?

I guess in this case we can still cast T.untyped to Model to get our typing? Maybe that's the message to be put in the manual if we don't want to force the cast? E.g. (reductively paraphrasing here) "If you need a strong type from #find, you can cast from untyped like so.."

user = T.cast(User.find(1), User)

It's not a big deal, but mostly it's the method lookup from the LSP that matters the most in this case.

@rafaelfranca
Copy link
Member

I think ActiveRecord::Base.find is way more likely to be used in the form of 1 single records than in array, so adding friction in the most common case seems disproportional, when compared with the parameters case.

But you are right that cast is the answer here, and even with T.untyped you can cast to get the LSP method lookup and I think it we should document that as well in the manual.

@richard-thatch
Copy link
Contributor Author

Yep! Cool, if you like I can write up a thing for the manual.

@rafaelfranca
Copy link
Member

Please go ahead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants