⬆️ ⬇️

How to count other people's money

For the rail, a million and one tutorials have already been written about what to do if you suddenly have to write an application that works with money.



Usually it all comes down to tips not to use Float, use Decimal, there are all sorts of transactions and so on. And in most cases, these tips are enough for the developer to feel dry and comfortable.



Did you come across a situation where, say, an application should serve the inhabitants of more than one country?

')









Here is a hypothetical situation: the application is a subscription service for onaniz fans, for example, uh, paid cartoons. We have each cartoon worth a certain decimal amount of rubles, which is stored in the database. And the user has a certain decimal amount of rubles that he can spend on viewing. And everything is cool until we decide that it would be beneficial to show cartoons to the citizens of Ukraine. Ukrainians, too, love to watch cartoons and spend money. Here it is only inconvenient for citizens to have an account in rubles. Because they are paid salary in hryvnias, and on the card they probably have hryvnias. And in general, every time it is in your mind to translate how much each ruble cartoon will cost, somehow sadly.



Money





And here in the shining armor of the savior of the day gem money taxiing out. He knows just what he needs:

 # 5  dollars = Money.new(500, 'USD') # 10 ! euros = Money.new(1000, 'EUR') # #   euros > dollars # => true #  euros.exchange_to('USD') # => #<Money cents:1408 currency:USD> #   !!11 Money.new(1000, 'USD') + Money.new(1000, 'EUR') # => #<Money cents:2408 currency:USD> 




In general, beauty and only.



The only thing that saddens the holiday is a very nontrivial mechanism for connecting to rail models. Something like this:



 # Gemfile gem 'money' gem 'google_currency', :require => 'money/bank/google_currency' # cartoon.rb class Cartoon < ActiveRecord::Base composed_of :price, :class_name => 'Money', :mapping => [[ 'price_in_cents', 'cents' ], [ 'currency', 'currency_as_string' ]], :constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) }, :converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") } end # migration create_table :cartoons do |t| # ... t.integer :price_in_cents, :default => 0, :null => false t.string :price_currency, :limit => 3, :null => false # ... end 




And it is only in one model. And still it is necessary not to forget the initializer to write, so that the currencies into each other are converted:

 # intializers/money.rb Money.default_bank = Money::Bank::GoogleCurrency.new 




Counterfeit





In short, when I copy-paste this code into a third model, the thought came to my bright mind to write a simple hem that slightly simplified the whole thing. Well, I wrote.



It is called counterfeit .



It works just like felt boots:

 # Gemfile gem 'counterfeit' # cartoon.rb class Cartoon < ActiveRecord::Base has_counterfeit :price #        # :currency => 'RUB' #    ,     . # :currency_attribute => :price_currency, # :amount_attribute => :price_in_cents end # migration create_table :cartoons do |t| # ... t.money :price # ... end 




Google as an exchanger is put down by itself, but only at the first unsuccessful attempt to convert currencies with a standard convector. A standard one needs to prescribe courses with his hands - you are unlikely to do this, right?



In general, if you ever have to write an application with paid cartoons, try counterfeit. And you will find bugs - write, otherwise I already launched into production.



But wait, there is more



Something told me that very few people would type the word counterfeit the first time without errors, so a magic alias was made specifically for these guys:

 class Cartoon < ActiveRecord::Base has_money :price #   , ? end 

Source: https://habr.com/ru/post/122022/



All Articles