-
Notifications
You must be signed in to change notification settings - Fork 131
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
ActiveRecord relations' each
method block argument is T.untyped
#2034
Comments
Adding these lines to my # https://github.com/Shopify/tapioca/issues/2034
ActiveRecord::Delegation.undef_method(:each) |
each
method block argument is T.untyped
Hi Mark, I think this list of delegated methods in That module is included after I think the fix here might be as easy as adding the correct sig to class ActiveRecord::Relation
sig { returns(T::Boolean) }
def blank?; end
+ # adapted from https://github.com/sorbet/sorbet/blob/64b1468/rbi/core/enumerable.rbi#L19-L27
+ sig do
+ abstract
+ .params(blk: T.proc.params(arg0: Elem).returns(BasicObject))
+ .returns(T.untyped)
+ end
+ sig { returns(T.self_type) }
+ def each(&blk); end
end That would be a great first contribution. Would you like to give it a shot? |
Sure, I'll give it a shot this week. I've been slammed with deadlines so I can't guarantee it but it sounds simple enough and may make it easier for me to contribute other fixes later. My workaround ended up breaking the usage of the .each method with no arguments anyway, since there was no concrete implementation of .each in the RBI, and Sorbet's Enumerable each sig returns T.self_type if used with no block. |
I think the second overload above instead of |
@amomchilov I have a PR open here to address this Shopify/rbi-central#290 |
@marknuzz Approved and merged! Could you please verify this locally, and confirm that it works before we close this? |
@amomchilov Yes I have confirmed that the issue is fixed, working correctly for both the chained enumerator and non chained cases. |
P.S. I would like to contribute to more type annotations but there's some questions I have. Is there a mailing list or group for discussion or should I ask about it in relevant issues/PRs like this Shopify/rbi-central#166 |
Hi there. This change raised us some errors, and just want to understand if it is or not expected. Example with dummy code: queue_counts = Delayed::Job.select('count(*) as job_count, field').group('field')
queue_counts.each do |queue_count|
...
queue_count.job_count
...
end Error given when running
To address this issue across multiple projects, we developed a generalized shim like this: # typed: strict
class ActiveRecord::Relation
sig { abstract.params(blk: T.proc.params(arg0: T.untyped).returns(BasicObject)).returns(T.untyped) }
sig { abstract.returns(T::Enumerator[T.untyped]) }
def each(&blk); end
end Was this an expected output of these changes? |
@danielveloso09 I don't think that this change should be blamed for the issue. If you use Tapioca considers If your model is defined by your application, you can use the attribute class method to define a virtual attribute, like described in this issue If the model is not defined by your application, you can add something like this to an initializer before running tapioca dsl (just make sure there's no name collisions, and I'm not 100% sure if this is the optimal solution, it's just an idea): Delayed::Job.class_eval do
attribute :job_count
attribute :field
end Let me know if that works or not |
Maybe I was not clear with the examples I gave and even by mentioning virtual attributes 😅 Lets assume that in some specific Use Case your business logic requires some "complex" query to be done on top of an ActiveRecord model, and than you would need to iterate each record. Example models: class Table1 < ActiveRecord; end Example of query you want to generate: SELECT
count(*) as counter,
field,
STRING_AGG(name, ',') as names,
(CASE WHEN active == true THEN 'ACTIVE' ELSE 'INACTIVE' END) as status
FROM table1
GROUP BY field, status Note: I know this query might not make much sense, but is just for the purpose of having an example Example of ActiveRecord code to build the query: results = Table1.select(
'count(*) as counter',
'field',
"STRING_AGG(table2.name, ',') as names",
"(CASE WHEN active == true THEN 'ACTIVE' ELSE 'INACTIVE' END) as status"
).group('field', 'status') If we need to iterate the active record collection and use any of the fields built in the query: ...
results.each do |result|
...
mapped_results[result.fielld] = {
counter: result.counter,
names: result.names,
status: result.status
}
...
end With the current annotation this iteration would "complain" about Hope to have made it clear with these new examples. Assuming the above, is this annotation change expected to raise the error? |
@danielveloso09 Thank you for the clarification. I will do my best to give my opinion:
|
Calling
#each
on aPrivateAssociationRelation
,PrivateCollectionProxy
, etc. will give an untyped block argument. However,#each_with_index
will give a typed argument. This is difficult to work around (adding a line of code with aT.cast
for each time#each
is used, is not a workaround at all).These are classes which are also valid
Enumerable
s, but theeach
method on these classes will use the untypedActiveRecord::Delegation
methods from therbi/gems
path, and not a path defined inrbi/dsl
, or even sorbet'sT::Enumerable
class. This causes the block argument to be untyped, which causes a lot of missed type checking!!The text was updated successfully, but these errors were encountered: