Skip to content

fix activerecord sum methods #814

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

Merged
merged 1 commit into from
Mar 25, 2025
Merged

Conversation

rhiroe
Copy link
Contributor

@rhiroe rhiroe commented Mar 7, 2025

When a block is given, it is delegated to Enumerable#sum. See Enumerable#sum for reference.

Since it is more type-friendly for the types of initial_value, the block's return value, and the method's return value to match, I will accept some imprecision and unify all types as T.

When no block is given, the calculate method processes the result, so the return value is not necessarily an Integer.

https://github.com/rails/rails/blob/v6.0.6.1/activerecord/lib/active_record/relation/calculations.rb#L127

Copy link

github-actions bot commented Mar 7, 2025

@rhiroe Thanks for your contribution!

Please follow the instructions below for each change.
See also: https://github.com/ruby/gem_rbs_collection/blob/main/docs/CONTRIBUTING.md

Available commands

You can use the following commands by commenting on this PR.

  • /merge: Merge this PR if CI passes

activerecord

You changed RBS files for an existing gem.
You need to get approval from the reviewers of this gem.

@hibariya, @ksss, @Little-Rubyist, @tk0miya, please review this pull request.
If this change is acceptable, please make a review comment including APPROVE from here.
Screen Shot 2024-03-19 at 14 13 36

After that, the PR author or the reviewers can merge this PR.
Just comment /merge to merge this PR.

@rhiroe
Copy link
Contributor Author

rhiroe commented Mar 7, 2025

I’m a bit confused after receiving a comment outside of the PR that the return value in the case of a block is not necessarily T.

@ksss
Copy link
Collaborator

ksss commented Mar 7, 2025

How about delegating to Enumerable#sum?

https://github.com/ruby/rbs/blob/1209f49fd9073374d5660e8d9fc25ce2fc5c1af0/core/enumerable.rbs#L1806-L1809

@rhiroe rhiroe force-pushed the fix/activerecord/sum branch 2 times, most recently from 6e4e96d to e33cee7 Compare March 7, 2025 23:56
Copy link
Collaborator

@ksss ksss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I welcome the accuracy of type information, but not the inconvenience.

It is likely that adjusting the results based on the column type for each model would be the optimal approach, but in most cases, the type would be either Integer or Float.

What specific problem does this change solve for you?

Comment on lines 434 to 435
def sum: (?untyped? column_name) -> untyped
| [T] () { (Model) -> T } -> (Integer | T)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent of my comment was to use overloading.
I don't know if it will work.

Suggested change
def sum: (?untyped? column_name) -> untyped
| [T] () { (Model) -> T } -> (Integer | T)
def sum: (?untyped? column_name) -> untyped
| ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn’t realize that overloading could be used here, but that makes perfect sense. I'll check it out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to result in an InvalidOverloadMethodError. 😢

@rhiroe
Copy link
Contributor Author

rhiroe commented Mar 8, 2025

It is likely that adjusting the results based on the column type for each model would be the optimal approach, but in most cases, the type would be either Integer or Float.

I agree.

What specific problem does this change solve for you?

The issue I'm facing is that summing Float values results in an Integer.

@rhiroe rhiroe force-pushed the fix/activerecord/sum branch 2 times, most recently from ea5c246 to 34f877d Compare March 10, 2025 04:25
Copy link
Collaborator

@ksss ksss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally agree with cases that involve a block.

For cases without a block, let me think about it a bit more.

| () { (Model) -> Integer } -> Integer
def sum: (?untyped? column_name) -> untyped
| [T] () { (Model) -> T } -> (Integer | T)
| [U] (U initial_value) { (Model) -> untyped } -> U
Copy link
Collaborator

@ksss ksss Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value does not necessarily match initial_value.

User.sum(0) { |user| user.decimal_column } #=> BigDecimal

Also, if there are zero records, the type of initial_value seems to be returned.

User.sum(0) { |user| user.decimal_column } #=> Integer(0)
User.sum(0.0) { |user| user.decimal_column } #=> Float(0.0)

If you value practicality,

Suggested change
| [U] (U initial_value) { (Model) -> untyped } -> U
| [T] (untyped initial_value) { (Model) -> T } -> T

If you value accuracy,

Suggested change
| [U] (U initial_value) { (Model) -> untyped } -> U
| [T, U] (T initial_value) { (Model) -> U } -> (T | U)

@ksss
Copy link
Collaborator

ksss commented Mar 11, 2025

@rhiroe

Here’s my thought on cases without a block:

In conclusion, I believe untyped is the best choice.

To express it completely accurately, the sum method would need to be overridden for each column.

class User < ActiveRecord::Base
  def self.sum: (:id) -> Integer
              | (:hight) -> Float
              | ...
end

A simpler approach would be to use -> (String | Integer | Float | BigDecimal)?, which would be sufficient.

However, neither seems particularly convenient to me.
Ultimately, annotating it directly in the code seems like the most reasonable approach.

sum_of_hight = User.sum(:hight) #: Float

Therefore, I think untyped is the best option.


For cases with a block and an initial_value, I believe one of the methods I proposed would be appropriate. What do you think?

@rhiroe
Copy link
Contributor Author

rhiroe commented Mar 11, 2025

Thank you for your suggestion. +1.

I believe the type for cases with initial_value should be [T] (untyped initial_value) { (Model) -> T } -> T. While this may lack precision, it is more type-friendly if the types of initial_value, the block's return value, and the method's return value match, and I believe this is how it should be implemented. Therefore, I think it makes sense to prioritize usability over strict accuracy.

Therefore, I’m considering defining the type as follows:

def sum: (?untyped? column_name) -> untyped
       | [T] (?T initial_value) { (Model) -> T } -> T

At this point, I don’t see the need to differentiate the type based on the presence of initial_value. If a concrete issue arises from this approach, we can reconsider it at that time.

This is my current thought on the matter. What do you think?

@ksss
Copy link
Collaborator

ksss commented Mar 12, 2025

[T] (?T initial_value) { (Model) -> T } -> T looks great to me!

@rhiroe rhiroe marked this pull request as draft March 13, 2025 07:48
@rhiroe rhiroe force-pushed the fix/activerecord/sum branch from 34f877d to c9ad1f5 Compare March 13, 2025 11:10
When a block is given, it is delegated to Enumerable#sum. See `Enumerable#sum` for reference.

Since it is more type-friendly for the types of `initial_value`, the block's return value, and the method's return value to match, I will accept some imprecision and unify all types as `T`.

When no block is given, the calculate method processes the result, so the return value is not necessarily an Integer.

https://github.com/rails/rails/blob/v6.0.6.1/activerecord/lib/active_record/relation/calculations.rb#L127
@rhiroe rhiroe force-pushed the fix/activerecord/sum branch from 0c805ab to 6bc88df Compare March 25, 2025 05:58
@rhiroe rhiroe marked this pull request as ready for review March 25, 2025 05:58
Copy link

@rhiroe Thanks for your contribution!

Please follow the instructions below for each change.
See also: https://github.com/ruby/gem_rbs_collection/blob/main/docs/CONTRIBUTING.md

Available commands

You can use the following commands by commenting on this PR.

  • /merge: Merge this PR if CI passes

activerecord

You changed RBS files for an existing gem.
You need to get approval from the reviewers of this gem.

@hibariya, @ksss, @Little-Rubyist, @tk0miya, please review this pull request.
If this change is acceptable, please make a review comment including APPROVE from here.
Screen Shot 2024-03-19 at 14 13 36

After that, the PR author or the reviewers can merge this PR.
Just comment /merge to merge this PR.

@rhiroe rhiroe requested a review from ksss March 25, 2025 06:00
Copy link
Collaborator

@ksss ksss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE

Copy link

Thanks for your review, @ksss!

@rhiroe, @ksss This PR is ready to be merged.
Just comment /merge to merge this PR.

@rhiroe
Copy link
Contributor Author

rhiroe commented Mar 25, 2025

/merge

@github-actions github-actions bot merged commit b20c7f2 into ruby:main Mar 25, 2025
7 checks passed
@rhiroe rhiroe deleted the fix/activerecord/sum branch March 25, 2025 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants