{"id":26779212,"url":"https://github.com/infinum/money_with_date","last_synced_at":"2025-04-18T03:11:32.108Z","repository":{"id":43824668,"uuid":"459121045","full_name":"infinum/money_with_date","owner":"infinum","description":"Extension for the money gem which adds dates to Money objects. ","archived":false,"fork":false,"pushed_at":"2024-09-06T14:53:37.000Z","size":50,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-11T17:11:00.292Z","etag":null,"topics":["currency","gem","money","money-rails","rails","ruby"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/money_with_date","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/infinum.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-02-14T10:46:23.000Z","updated_at":"2025-02-18T14:27:25.000Z","dependencies_parsed_at":"2024-06-22T07:14:56.388Z","dependency_job_id":"f5c7f18e-aebe-43f3-adef-7c65ed37bbdb","html_url":"https://github.com/infinum/money_with_date","commit_stats":{"total_commits":14,"total_committers":2,"mean_commits":7.0,"dds":0.0714285714285714,"last_synced_commit":"758a4c1dbf0ca8e4f4314acbd42358aa1887afa7"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinum%2Fmoney_with_date","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinum%2Fmoney_with_date/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinum%2Fmoney_with_date/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinum%2Fmoney_with_date/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/infinum","download_url":"https://codeload.github.com/infinum/money_with_date/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249418628,"owners_count":21268470,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["currency","gem","money","money-rails","rails","ruby"],"created_at":"2025-03-29T06:15:19.871Z","updated_at":"2025-04-18T03:11:32.089Z","avatar_url":"https://github.com/infinum.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# money_with_date\n\nA Ruby library which extends the popular [money](https://github.com/RubyMoney/money) and [money-rails](https://github.com/RubyMoney/money-rails) gems with support for dated Money objects.\n\nDated Money objects are useful in situations where you have to exchange money between currencies based on historical exchange rates, and you'd like to keep date information on the Money object itself.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'money_with_date'\n```\n\nAnd then execute:\n\n    $ bundle install\n\nOr install it yourself:\n\n    $ gem install money_with_date\n\nThat's it! All your Money objects now have a date attribute.\n\n## Usage\n\n```ruby\nMoney.default_bank = MoneyWithDate::Bank::VariableExchange.new(MoneyWithDate::RatesStore::Memory.new)\n# the gem provides subclasses of Money::Bank::VariableExchange and Money::RatesStore::Memory\n# which can save historical exchange rates\n\nMoney.add_rate \"USD\", \"EUR\", 0.9, \"2020-01-01\"\nMoney.add_rate \"CHF\", \"EUR\", 0.96, Date.today\n\nMoney.new(100, \"EUR\", date: Date.today) + Money.new(100, \"USD\", date: \"2020-01-01\")\n# =\u003e Money.new(190, \"EUR\", date: Date.today)\n# the second Money object is exchanged to EUR with the exchange rate on 1/1/2020\n\nMoney.default_date = -\u003e { Date.today }\n# new Money objects use the default date if \"date\" parameter is omitted in Money.new\n\ntransactions = [\n  Money.from_amount(2, \"CHF\"),\n  Money.from_amount(1, \"USD\", date: \"2020-01-01\"),\n  Money.from_amount(2, \"EUR\")\n]\n\ntransactions.map { |money| money.exchange_to(:eur) }.sum\n# =\u003e Money.from_amount(4.82, \"EUR\")\n# 2 CHF == 1,92 EUR on today's date\n# 1 USD == 0,9 EUR on 1/1/2020\n# 1,92 + 0,9 + 2 == 4,82\n\n# In Rails, a date column can be used to control the date of the Money object:\nclass Product \u003c ActiveRecord::Base\n  monetize :amount_cents, with_model_date: :published_on\nend\n\nproduct = Product.new(amount_cents: 100, published_on: \"2020-01-01\")\n\nproduct.amount.date\n# =\u003e \"2020-01-01\"\n```\n\nWhen you install and require the gem, all Money objects will automatically have a date attribute.\n\nThe default date for a Money object is read from the configuration option `Money.default_date` (more about that [here](#default_date)).\n\nYou can also override the default date when creating a Money object:\n```ruby\nmoney = Money.new(100, :usd, date: \"2020-01-01\")\n\nmoney.date # =\u003e \"2020-01-01\"\n```\n\nThe type of the `date` param should either be a Date object or something which can be coerced into Date (e.g. Time object, a string which can be parsed as date). The following examples are all valid ways of setting a date:\n```ruby\nMoney.new(100, :usd, date: Date.today)\n\nMoney.new(100, :usd, date: Time.now) # converted to Date with the `#to_date` method\n\nMoney.new(100, :usd, date: \"2022-02-15\") # converted to Date with `Date.parse`\n```\n\n`date` argument which cannot be coerced into a Date will result in an `ArgumentError`.\n\nIt is also possible to copy Money objects and change their date with `Money#with_date`:\n```ruby\nmoney = Money.from_cents(100, :eur, date: \"2022-02-15\")\n\nnew_money = money.with_date(\"2020-01-01\")\n\nnew_money.cents # =\u003e 100\nnew_money.date # =\u003e \"2020-01-01\"\nmoney.date # =\u003e \"2022-02-15\" (the original object is unchanged)\n```\n\nIn addition to `Money.new`, the `date` param can also be passed to the following methods:\n```ruby\nMoney.from_amount(10.00, :usd, date: Date.today)\n\nMoney.from_cents(1000, :usd, date: Date.today)\n```\n\n### Currency Exchange\n\nThis gem supports all existing bank implementations which do not support historical exchange rates. By default, when you require `money_with_date` in your project, existing currency exchange logic won't be affected.\n\nTo enable historical currency exchanges, the bank and rates store objects you use must support an additional `date` param in order to associate an exchange rate with a specific date.\n\nThis gem provides subclasses of money's default bank and rates store (variable exchange, in-memory store), which have been extended to support historical exchange rates:\n```ruby\nMoney.default_bank = MoneyWithDate::Bank::VariableExchange.new(MoneyWithDate::RatesStore::Memory.new)\n```\n\nTo save a historical exchange rate, use `Money.add_rate` and supply a date param:\n```ruby\nMoney.add_rate \"EUR\", \"USD\", 1.1, Date.today\n```\n\nCurrency exchange then works like this:\n```ruby\nMoney.add_rate \"EUR\", \"USD\", 1.05, Date.today - 1\nMoney.add_rate \"EUR\", \"USD\", 1.1, Date.today\nMoney.add_rate \"EUR\", \"USD\", 1.2, Date.today + 1\n\nmoney = Money.new(100, \"EUR\", date: Date.today + 1)\n\nmoney.exchange_to(\"USD\").cents == 120 # =\u003e true\n```\n\nTo retrieve a historical exchange rate from a bank, invoke `Bank#get_rate` with a date param:\n```ruby\nMoney.default_bank.get_rate(\"EUR\", \"USD\", Date.today - 1) # =\u003e 1.05\n```\n\nThe same rules that apply to the date param when initializing a Money object also apply here: it can be a Date object or anything that can be coerced into a Date object (Time, String).\n\n### Exchange rate stores\n\nThis gem provides an in-memory store for historical exchange rates: `MoneyWithDate::RatesStore::Memory`. This class is very similar to `Money::RatesStore::Memory`, only an additional `date` parameter has been added to methods where necessary.\n\nYou can also implement your own store, but it has to follow this interface:\n```ruby\n# Add new exchange rate.\n# @param [String] iso_from Currency ISO code. ex. 'USD'\n# @param [String] iso_to Currency ISO code. ex. 'CAD'\n# @param [Numeric] rate Exchange rate. ex. 0.0016\n# @param [Date] date Exchange rate date. ex. Date.today\n#\n# @return [Numeric] rate.\ndef add_rate(iso_from, iso_to, rate, date); end\n\n# Get rate. Must be idempotent. i.e. adding the same rate must not produce duplicates.\n# @param [String] iso_from Currency ISO code. ex. 'USD'\n# @param [String] iso_to Currency ISO code. ex. 'CAD'\n# @param [Date] date Exchange rate date. ex. Date.today\n#\n# @return [Numeric] rate.\ndef get_rate(iso_from, iso_to, date); end\n\n# Iterate over rate tuples (iso_from, iso_to, rate)\n#\n# @yieldparam iso_from [String] Currency ISO string.\n# @yieldparam iso_to [String] Currency ISO string.\n# @yieldparam rate [Numeric] Exchange rate.\n# @yieldparam date [Date] Exchange rate date.\n#\n# @return [Enumerator]\n#\n# @example\n#   store.each_rate do |iso_from, iso_to, rate, date|\n#     puts [iso_from, iso_to, rate, date].join\n#   end\ndef each_rate(\u0026block); end\n\n# Wrap store operations in a thread-safe transaction\n# (or IO or Database transaction, depending on your implementation)\n#\n# @yield [n] Block that will be wrapped in transaction.\n#\n# @example\n#   store.transaction do\n#     store.add_rate('USD', 'CAD', 0.9, Date.today)\n#     store.add_rate('USD', 'CLP', 0.0016, Date.today)\n#   end\ndef transaction(\u0026block); end\n\n# Serialize store and its content to make Marshal.dump work.\n#\n# Returns an array with store class and any arguments needed to initialize the store in the current state.\n\n# @return [Array] [class, arg1, arg2]\ndef marshal_dump; end\n```\n\n### Usage With Rails\n\nIn case you're using `money-rails`, its `monetize` helper will also be extended to provide options for setting the date on monetized attributes. There are three ways you can set the date on a monetized attribute:\n\n#### `with_model_date` option\n\nUse this option when you want to use a table column to set the date on a monetized attribute. For example, if you have a table:\n```ruby\n# Table name: products\n#\n#  id                  :integer      not null, primary key\n#  amount_cents        :integer      not null\n#  published_on        :date\n#  created_at          :timestamp    not null\n#\n```\nand you want to use `published_on` column to set the date on the monetized `amount_cents`, do this:\n```ruby\nclass Product \u003c ActiveRecord::Base\n  monetize :amount_cents, with_model_date: :published_on\nend\n```\nwhich will cause the following:\n```ruby\nproduct = Product.new(amount_cents: 100, published_on: \"2020-01-01\")\n\nproduct.amount.date # =\u003e \"2020-01-01\"\n```\n\nIf the value of the `with_model_date` column is `nil`, the date on the Money object will fall back to `Money.default_date`.\n\n#### `with_date` option\n\nThis option can be used when you want to either hard-code a date for a monetized attribute, or you want to set the date dynamically. If you want to hard-code the date, pass a concrete value:\n```ruby\nclass Product \u003c ActiveRecord::Base\n  monetize :amount_cents, with_date: \"2020-01-01\"\nend\n```\nAll `Product#amount` Money objects will now have the same date: 1/1/2020.\n\nYou can also set the date on the Money object dynamically by using a callable:\n```ruby\nclass Product \u003c ActiveRecord::Base\n  monetize :amount_cents, with_date: -\u003e(product) { product.published_on || product.created_at }\nend\n```\nThe callable should accept a single param: the ActiveRecord object.\n\nIn case the callable resolves to nil, the date on the Money object will fall back to `Money.default_date`.\n\n#### `Money.default_date_column`\n\nIf you don't supply either of the above options to `monetize`, the gem will search for a default column to set the date on a monetized attribute. By default, this column is `created_at`. That means that if your class looks like this:\n```ruby\nclass Product \u003c ActiveRecord::Base\n  monetize :amount_cents\nend\n```\nthe following will happen:\n```ruby\nproduct = Product.new(amount_cents: 100, created_at: \"2020-01-01\")\n\nproduct.amount.date # =\u003e \"2020-01-01\"\n```\n\nIn case the default column doesn't exist, or its value is nil, the date on the Money object will fall back to `Money.default_date`.\n\nYou can also override the default column, or even disable it, which you can find out how to do [here](#default_date_column).\n\n## Configuration\n\nThe gem provides a couple of configuration options.\n\n### default_date\n\nWith the `money_with_date` gem installed, _all_ Money objects have a date. If you don't supply a date when creating a Money object, a default date will be assigned.\n\nYou can override the default date:\n```ruby\nMoney.default_date = -\u003e { Date.today }\n```\nand fetch it like this:\n```ruby\nMoney.default_date # outputs today's date\n```\n\nThe default date can be either a callable or a concrete value.\nIf you set a callable, `Money.default_date` will call the callable and return its value.\n\nBy default, `Money.default_date` is set to:\n```ruby\nMoney.default_date = -\u003e { Date.current }\n```\nif `Date.current` exists (which is the case in Rails projects). If it doesn't exist, the default date is set to:\n```ruby\nMoney.default_date = -\u003e { Date.today }\n```\n\n### date_determines_equality\n\nBased on your use case, you might or might not want the `date` attribute to affect whether two Money objects are equal. For example, in some scenarios it makes sense that:\n```ruby\nMoney.new(100, :usd, date: \"2010-12-31\") == Money.new(100, :usd, date: \"2020-01-01\")\n```\nreturns `true` (e.g. if you care only about money amounts), while in others it makes sense that it returns `false`.\n\nBy default, the date on the Money object **doesn't** affect the equality of the object to other Money objects (in the above scenario, the expression would return `true`).\n\nHowever, if you need to, you can tell the gem to look at the date when comparing Money objects, so that the above expression would return `false`. You can achieve that by setting:\n```ruby\nMoney.date_determines_equality = true\n```\n\nSetting this will affect the following Money methods: `#hash`, `#eql?` and `#\u003c=\u003e`.\n\n### default_date_column\n\nIf you use `money-rails` and `monetize`, but don't supply either `with_date` or `with_model_date` options, the gem will try to find a default column to set the date on the monetized attribute.\n\nThe default table column for setting the date on Money is `created_at`.\n\nIf you'd like to use a different default column, set it with:\n```ruby\n# config/initializers/money.rb\nMoney.default_date_column = :my_date_column\n```\n\nIf the column value is nil, or the column doesn't exist on the table, the date will fall back to `Money.default_date`.\n\nIf you don't want the gem to use a default column for setting the date, set this:\n```ruby\nMoney.default_date_column = nil\n```\n\n## Supported Versions\n\n`money_with_date` has the following version requirements:\n- Ruby: **\u003e= 3.0.0**\n- money: **\u003e= 6.14.0** and **\u003c= 6.19.0**\n- money-rails: **1.15.0**\n\nThe gem has been tested against all possible combinations of supported Ruby, Rails, money, and money-rails versions:\n- Ruby: `3.0`, `3.1`, `3.2` and `3.3`\n- Rails: `~\u003e 6.1.0`, `~\u003e 7.0.0`, `~\u003e 7.1.0` and `~\u003e 7.2.0`\n- money: `6.14.0`, `6.14.1`, `6.16.0`, `6.17.0`, `6.18.0` and `6.19.0`\n- money-rails: `1.15.0`\n\nThe following combinations have been excluded from the test matrix because of incompatibility:\n- Ruby `3.0` with Rails `~\u003e 7.2.0`\n\nIn addition to running its own test suite, the CI for this gem also runs [money's](https://github.com/RubyMoney/money/tree/main/spec) and [money-rails's](https://github.com/RubyMoney/money-rails/tree/main/spec) test suites with this gem loaded, to prevent regressions. This has been achieved by cloning their test suites from GitHub and requiring this gem in their spec files. For technical information, check the CI [workflow](.github/workflows/ci.yml).\n\n## Compatibility\n\nThis gem overrides money's and money-rails's public and _private_ APIs. As such, the gem can break with any new release of either of those gems if their API changes. To ensure breakages don't happen, the gem has been locked only to those versions of money and money-rails which have been fully tested for regressions.\n\nThe minimum supported versions are 6.14.0 for money and 1.15.0 for money-rails. Versions older than those cannot be supported as the API in older versions of both gems cannot be extended to provide the functionality which this gem provides.\n\n### Positional bank parameter\n\nUp until version 6.14.0 of money, Money constructor accepted only three positional arguments (amount, currency, and bank):\n```ruby\nMoney.new(1000, :usd, Money.default_bank)\n```\n\nFrom version 6.14.0, the constructor accepts two positional arguments and optional keyword arguments which can be used to override the default bank:\n```ruby\nMoney.new(1000, :usd, bank: Money.default_bank)\n```\n\nFor backwards-compatibility reasons, in version 6.14.0 and above, you can use either of those approaches to override the default bank.\n\nHowever, the old approach isn't compatible with this gem because it doesn't allow us to provide a date argument without modifying the constructor.\n\nSo, if you want to use this gem, but are currently overriding the default bank with a positional argument, you'll have to refactor your code to use the new approach.\n\nNote that, even if you don't refactor the code, the gem will still work, but all Money objects created that way will be assigned the default date.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo run RuboCop, execute `bundle exec rake rubocop`.\n\nTo run unit tests, execute `bundle exec rake spec:unit`. Unit tests can be run with different versions of money, money-rails, and Rails, which you can specify as Rake task arguments. For example, if you want to run unit tests on money 6.14.0, money-rails 1.15.0, and Rails 6.1.4.6, execute: `bundle exec rake \"spec:unit[6.14.0, 1.15.0, 6.1.4.6]\"`.\n\nTo run money regression tests, execute `bundle exec rake spec:money`. You can also run them with a specific version of money: `bundle exec rake \"spec:money[6.14.1]\"`.\n\nTo run money-rails regression tests, execute `bundle exec rake spec:money_rails`. The task also accepts a money-rails version argument: `bundle exec rake \"spec:money_rails[1.15.0]\"`.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/infinum/money_with_date. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/infinum/money_with_date/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the MoneyWithDate project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/infinum/money_with_date/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfinum%2Fmoney_with_date","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finfinum%2Fmoney_with_date","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfinum%2Fmoney_with_date/lists"}