-
Notifications
You must be signed in to change notification settings - Fork 2
Add integer conversion to Strata::Money #234
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
base: main
Are you sure you want to change the base?
Add integer conversion to Strata::Money #234
Conversation
|
@lorenyu Would you mind taking a look at this? |
lorenyu
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion on an alternative approach
| # Returns the amount as an Integer in cents | ||
| # | ||
| # @return [Integer] The cents amount | ||
| def to_i | ||
| cents | ||
| end | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry about adding this coercion method because it can lead to bugs if the developer didn't realize that money casts to the cents amount. I can see a very reasonable developer assuming that money_amount + 1 would add one dollar to the amount
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only counter is that a dev is going to need to know about cents anyways, just because of how Strata::Money is setup.
In the doc comment example for making a new Money object, you have to pass cents into the initializer. Also, the only settable attribute is cents.
money = Strata::Money.new(1250) # $12.50 in cents
money.cents = 625 # $6.25 in centsBasically, since everything revolves around cents for money anyways, my mind immediately thinks that to_i would give me cents.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's only true if the dev explicitly constructs Money objects. But with the SDK they don't have to. They can use the generator to generate the column which if they don't look closely they won't know if it's storing a decimal dollar amount or an integer cents amount, and they can use form builder's f.money (which doesn't exists yet but it should eventually) to collect money as dollars since that's more intuitive from a user perspective. To implement f.money we'd do something like render <input name="attribute_name[dollar_amount]" type="text"> which would send over something like { foo: { dollar_amount: 12.75 } }, which is converted to cents here. Then at the end of the day they'll have money objects to work with and never need to construct them. Even in tests we'd probably offer built-in SDK money generators.
Also, even if one dev on a project understands how money works under the hood, it's possible another dev that just works with the models that already have money objects on them and is implementing new business logic and/or new views may not know about the internals of how money works.
All is to say I'm somewhat nervous about this, especially given the fact that money is so important to get right (calculating incorrect payment amonuts is a pretty serious bug in the real world)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an aside, if we want to do type coercion, the way to do that I believe is to define a coerce method rather than to_i which may allow the Money object to be used in places that accept integers that don't make sense (e.g. array indices)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know (the coerce method). Another reason we might want a coerce method is for things like validations. I just ran into a problem with:
class EarnedIncomeActivity < Activity
include Strata::Attributes
strata_attribute :earned_income, :money
validates :earned_income, presence: true, numericality: { greater_than: 0 } # <-- numericality becomes a problem
endThe numericality built-in validator won't work because it can't coerce the income to be a number @lorenyu
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an interesting use case. Curious, does it work to do
class EarnedIncomeActivity < Activity
include Strata::Attributes
strata_attribute :earned_income, :money
validates :earned_income, presence: true, numericality: { greater_than: Strata::Money.ZERO }
end
where Money.ZERO is defined as
class Money
ZERO = new(cents: 0)
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And arguably we'd want to use the comparison validator like
validates :earned_income, comparison: { greater_than: Money.ZERO }
if we don't think of Money as a number. (Tangentially, we're only ever dealing with USD but conceptually the Money type depends on currency so we can't convert it to a number without knowing the currency conversion)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding to_i, prefer to add a zero money constant and use that for summing e.g.
ZERO = new(0).freeze
then you can do [money1, money2, money3].sum(Strata::Money.ZERO)
(may want to add to the docs that this is how you sum an array of moneys)
Changes
Context
This will assist in the completion of TSS-414.
Helps solve a situation where I would want to use earned income as an integer value, such as:

Testing
CI