├── .github └── workflows │ └── ruby.yml ├── .gitignore ├── .rspec ├── CHANGELOG.md ├── CONTRIBUTORS ├── Gemfile ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── config └── locales │ ├── money.da.yml │ ├── money.en.yml │ └── money.ru.yml ├── gemfiles ├── mongoid6.gemfile ├── mongoid7.gemfile ├── rails5.gemfile ├── rails6.1.gemfile └── rails7.0.gemfile ├── lib ├── generators │ ├── money_rails │ │ └── initializer_generator.rb │ └── templates │ │ └── money.rb ├── money-rails.rb └── money-rails │ ├── active_job │ └── money_serializer.rb │ ├── active_model │ └── validator.rb │ ├── active_record │ ├── migration_extensions │ │ ├── options_extractor.rb │ │ ├── schema_statements.rb │ │ ├── schema_statements_pg_rails4.rb │ │ ├── table.rb │ │ └── table_pg_rails4.rb │ └── monetizable.rb │ ├── configuration.rb │ ├── engine.rb │ ├── errors.rb │ ├── helpers │ └── action_view_extension.rb │ ├── hooks.rb │ ├── money.rb │ ├── mongoid │ ├── money.rb │ └── two.rb │ ├── rails_admin.rb │ ├── railtie.rb │ ├── test_helpers.rb │ └── version.rb ├── money-rails.gemspec └── spec ├── active_job └── money_serializer_spec.rb ├── active_record ├── migration_extensions │ ├── schema_statements_spec.rb │ └── table_spec.rb └── monetizable_spec.rb ├── configuration_spec.rb ├── dummy ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── config │ │ │ └── manifest.js │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ └── application_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── mailers │ │ └── .gitkeep │ ├── models │ │ ├── .gitkeep │ │ ├── dummy_product.rb │ │ ├── priceable.rb │ │ ├── product.rb │ │ ├── service.rb │ │ └── transaction.rb │ └── views │ │ └── layouts │ │ └── application.html.erb ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── money.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ ├── en-GB.yml │ │ ├── en-US.yml │ │ ├── en.yml │ │ └── it.yml │ ├── mongoid.yml │ └── routes.rb ├── db │ ├── migrate │ │ ├── 20120331190108_create_products.rb │ │ ├── 20120402080348_add_bonus_cents_to_product.rb │ │ ├── 20120524052716_create_services.rb │ │ ├── 20120528181002_create_transactions.rb │ │ ├── 20120528210103_create_dummy_products.rb │ │ ├── 20120607210247_add_column_that_allows_nil.rb │ │ ├── 20120712202655_add_sale_price_cents_to_product.rb │ │ ├── 20130124023419_add_price_in_a_range_cents_to_products.rb │ │ ├── 20140110194016_add_validates_method_amount_cents_to_products.rb │ │ ├── 20141005075025_add_aliased_attr_to_products.rb │ │ ├── 20150107061030_add_delivery_fee_cents_and_restock_fee_cents_to_product.rb │ │ ├── 20150126231442_add_reduced_price_to_products.rb │ │ ├── 20150213234410_add_special_price_to_products.rb │ │ ├── 20150217222612_add_lambda_price_to_products.rb │ │ ├── 20150303222230_add_skip_validation_price_cents_to_products.rb │ │ └── 20151026220420_add_optional_amount_to_transactions.rb │ ├── schema.rb │ └── structure.sql ├── lib │ └── assets │ │ └── .gitkeep ├── log │ └── .gitkeep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── favicon.ico └── script │ └── rails ├── helpers ├── action_view_extension_spec.rb └── form_helper_spec.rb ├── money_spec.rb ├── mongoid ├── mongoid_spec.rb └── two_spec.rb ├── spec_helper.rb ├── support └── database_cleaner.rb └── test_helpers_spec.rb /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake 6 | # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby 7 | 8 | name: Ruby 9 | 10 | on: 11 | push: 12 | branches: [ main ] 13 | pull_request: 14 | branches: [ main ] 15 | 16 | jobs: 17 | test: 18 | 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | ruby-version: ['3.0', '3.1', '3.2', '3.3'] 23 | mongodb-version: ['4.4'] 24 | sqlite-version: ['3.0'] 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up Ruby 29 | # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, 30 | # change this to (see https://github.com/ruby/setup-ruby#versioning): 31 | uses: ruby/setup-ruby@v1 32 | with: 33 | ruby-version: ${{ matrix.ruby-version }} 34 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 35 | 36 | - name: Start MongoDB 37 | uses: supercharge/mongodb-github-action@1.3.0 38 | with: 39 | mongodb-version: ${{ matrix.mongodb-version }} 40 | 41 | - name: Run tests 42 | run: bundle exec rake 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # multiple gemfiles 2 | gemfiles/*.lock 3 | Gemfile.lock 4 | 5 | # RVM / rbenv version files 6 | .rvmrc 7 | .rbenv-version 8 | .ruby-version 9 | 10 | # dummy dbs 11 | *.sqlite3 12 | 13 | # rcov generated 14 | coverage 15 | 16 | # rdoc generated 17 | rdoc 18 | 19 | # yard generated 20 | doc 21 | .yardoc 22 | 23 | # bundler 24 | .bundle 25 | vendor/bundle 26 | bin 27 | 28 | # logging 29 | *.log 30 | 31 | # jeweler generated 32 | pkg 33 | 34 | ## MAC OS 35 | .DS_Store 36 | 37 | ## TEXTMATE 38 | *.tmproj 39 | tmtags 40 | 41 | ## EMACS 42 | *~ 43 | \#* 44 | .\#* 45 | 46 | ## VIM 47 | *.swp 48 | 49 | ## Rubymine 50 | .idea 51 | 52 | ## PROJECT::GENERAL 53 | *.rbc 54 | 55 | ## PROJECT::SPECIFIC 56 | .rbx 57 | 58 | /tmp 59 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | - Allow monetizing methods with kwargs 6 | - Fix money_only_cents for negative money 7 | 8 | ## 1.15.0 9 | 10 | - Bump money version to ~> 6.16 11 | - Tweak to invalid currency message 12 | 13 | ## 1.14.1 14 | 15 | - Fix invalid_currency error definition 16 | - Add Russian translation for errors 17 | - Loosen money version to ~> 6.13 18 | - Loosen monetize version to ~> 1.9 19 | 20 | ## 1.14.0 21 | 22 | - Tweaks to support Ruby 3.0 23 | 24 | ## 1.13.4 25 | 26 | - Fix validator race condition 27 | - Add Danish translation for errors 28 | - Change Money fields to Decimal in Rails Admin 29 | - Run hooks after active_record.initialize_database 30 | - Add optional currency argument to "#currency_symbol" helper 31 | - Rails 6.1 support 32 | 33 | ## 1.13.3 34 | 35 | - Add Money#to_hash for JSON serialization 36 | - Update initializer template with #locale_backend config 37 | - Rollback support for remove_monetize / remove_money DB helpers 38 | - Rails 6 support 39 | 40 | ## 1.13.2 41 | 42 | - Make validation compatible with Money.locale_backend 43 | 44 | ## 1.13.1 45 | 46 | - Add a guard clause for "blank form" input (Mongoid) 47 | - Do not add extra errors in case attribute is not a number 48 | - Use Money.locale_backend instead of Money.use_i18n 49 | - Add money_only_cents helper method 50 | 51 | ## 1.13.0 52 | 53 | - Bump money version to ~> 6.13.0 54 | 55 | ## 1.12.0 56 | 57 | - Bump money version to ~> 6.12.0 58 | - Bump monetize version to ~> 1.9.0 59 | - BREAKING CHANGE: Fix to rounding logic in this version from 1.11.0 can cause breaking changes for those relying on the incorrect behavior. See [the issue](https://github.com/RubyMoney/money-rails/issues/608) for details. 60 | 61 | ## 1.11.0 62 | 63 | - Bump money version to ~> 6.11.0 64 | - Add test helper for with_model_currency 65 | - Fix empty validation errors from being assigned 66 | 67 | ## 1.10.0 68 | 69 | - Bump money version to ~> 6.10.0 70 | - Optimize reading of the attribute when `allow_nil` is set to true 71 | 72 | ## 1.9.0 73 | 74 | - Allow the use of money-rails with plan ActiveRecord (without Rails) 75 | - Remove translation of postfix for the amount column (always use _cents by default) 76 | - Push Monetize dependency from 1.6.0 to 1.7.0 77 | 78 | ## 1.8.0 79 | 80 | - Ruby 2.4 support 81 | - Upgrade Money dependency from 6.7 to 6.8.1 82 | - Upgrade Monetize dependency from 1.4.0 to 1.6.0 83 | - Raise `MoneyRails::Error` instead of exposing Money and Monetize errors 84 | 85 | ## 1.7.0 86 | 87 | - Rails 5 support 88 | - Mongoid 5 support 89 | - Do not convert Mongoid money fields from nil to zero 90 | - Refactor `#monetize` method 91 | 92 | ## 1.6.2 93 | 94 | - Fix attribute order possibly affecting the value of monetized attribute 95 | - Add support for RailsAdmin (use :money type) 96 | - Raise error when gem was unable to infer monetized attribute name 97 | - Revert decimal mark and thousands separator addition, since formatting should depend on country and locale, instead of currency 98 | 99 | ## 1.6.1 100 | 101 | - View helper now respects currency decimal mark and thousands separator 102 | - Fix error when monetizing with attribute's name 103 | - Fix mime-types dependency issue with ruby 1.9 104 | - Fix issue with gem not updating automatically inferred currency column 105 | 106 | ## 1.6.0 107 | 108 | - Update Money and Monetize gem reqs. 109 | 110 | ## 1.5.0 111 | 112 | - Respect Money.use_i18n when validating. 113 | - Include attribute in validation messages like Rails does. 114 | - Respect `raise_error_on_money_parsing` before raising a MoneyRails::ActiveRecord::Monetizable::ReadOnlyCurrencyException. 115 | 116 | ## 1.4.1 117 | 118 | - validator was looking for monetizable_attributes using a symbol key, when the keys are all strings. Asserted string key values in rspec and changed validator to look for a string key. 119 | - make monetized_attribute hash comparison order independent 120 | - Isolate class used for the monetized_attributes tests to prevent cross-contamination 121 | - rename `format_with_settings` method to `format` 122 | - add gem tasks 123 | 124 | ## 1.4.0 125 | 126 | - Fix validation failing when both superclass and subclass use monetize macros and any of them validates any field 127 | - Extract db adapter without open connection on load 128 | - Add support for field currency values to be determined by lambda. 129 | - Simplify validation options 130 | - Test for skipping validations separately from each other 131 | - Instead of requiring either the PG version or the non, always require the PG version and only fail to require the non when using PG, that way monetize will always work and money is supported for backwards compat. This way you can have a system with sqlite for dev and pg for production, for instance, and things still work. 132 | - Refactor monetized_attributes 133 | - updating db/schema.rb 134 | - DRYing migration extensions 135 | - Testing against latest ruby version 136 | - Include postgres specific code also when adaptor = postgis. 137 | - chore(add read only exception class) 138 | - tiny schema change 139 | 140 | ## 1.3.0 141 | 142 | - Use currency_column[:postfix] to automatically determine currency column. 143 | - Replacing getter method with attr_reader. 144 | - Support the `disambiguate` option on humanized_money helper. 145 | - Restore mongoid functionality on Rails < 4.0. 146 | - Support multiple attributes w/ one call to `monetize` for AR. 147 | - Add `add_monetize` and `remove_monetize` migration helpers, to fix a naming 148 | clash introduced by the Rails 4.2 Postgres adapter Use correct amount for 149 | validator when subunit is set directly. 150 | - Fix store_accessor compatibility. 151 | - Use `public_send` instead of `send` throughout the `monetize` method. 152 | 153 | ## 1.2.0 154 | 155 | - Fixing tests which were broken on Rails 4.2. 156 | - Add Rails 4.2 spec and update money dependency to 6.5.0. 157 | 158 | ## 1.1.0 159 | 160 | - Update dependencies to money 6.4.x and monetize 1.0.x. 161 | - Make subunit setter (e.g. `#price_cents=`) set the `before_type_cast...` 162 | variable. (Fixes validation errors.) 163 | - use HashWithIndifferentAccess instead of Hash for 164 | ActiveRecord::Base::monetized_attributes 165 | - Let the 'monetize' test helper work when testing against the model's class, 166 | as well as an instance of that class. 167 | - Remove additional underscore in postfix comment 168 | - Rescue UnknownCurrency within ActiveRecord 169 | - Upgrade specs to RSpec 3 170 | - Use #respond_to? instead of #try? when monetizing an aliased attribute. 171 | - Allow aliased attributes to be monetized 172 | - Fix compatibility issue with Rails 4.2 173 | - Allow empty string as thousands separator. 174 | - Allow using a lambda to set default_currency 175 | 176 | ## 1.0.0 177 | 178 | - Refactoring MoneyRails::ActiveModel::MoneyValidator#validate_each 179 | - Update dependencies to money 6.2.x and monetize 0.4.x. 180 | - Rescue from unknown currency errors on mongoization 181 | - Add specs for Mongoize with invalid currency 182 | - Add dedicated gemfiles and specs for Mongoid 3 and 4 183 | - Show actual value of Money object in validation error message 184 | 185 | ## 0.12.0 186 | - Add allow_nil chain for monetize test helper 187 | - Add testing tasks for rails 4.1 188 | 189 | ## 0.11.0 190 | - Helpers respect no_cents_if_whole configuration option (GH-157) 191 | 192 | ## 0.10.0 193 | - Depend on Money gem version ~> 6.1.1 194 | - Depend on monetize gem version ~> 0.3.0 195 | - Set mongoized value of cents to float 196 | - Fix validation error with whitespace between currency symbol and amount 197 | - Add raise_error_on_money_parsing configuration option (default is false) 198 | - Add rounding mode configuration option (default is ROUND_HALF_UP) 199 | - Rescue ArgumentError with invalid values in mongoid (GH-114) 200 | - Compatiblity between ActiveModel::Validations and MoneyValidator 201 | - Raise error when trying to change model based currency 202 | 203 | ## 0.9.0 204 | - Depend on Money gem version ~> 6.0.0 205 | - Add disable_validation option for skipping validations on monetized attributes 206 | - Remove implicit usage of 'currency' as a default currency column 207 | - Options to humanized_money_with_symbol are passed to humanized_money 208 | - Fix mongoization of Money when using infinite_precision 209 | - Add testing tasks for rails 4.x 210 | - Fix issue with Numeric values in MoneyValidator (GH-83). 211 | - Fix test helper 212 | - Fix issue with money validator (GH-102). 213 | - Change validation logic. Support Subunit and Money field 214 | validation with NumericalityValidator options. 215 | Moreover, now Money objects are normalized and pass through 216 | all validation steps. 217 | - Add support for the global configuration of the sign_before_setting formatting option. 218 | 219 | ## 0.8.1 220 | - Remove unnecessary files from gem build. 221 | - Add options to ActionView helpers that enable the usage of any of the rules ::Money.format allows. 222 | - Fix "setting amount_column for default_currency" to only accept 223 | postfix for default_currency with subunit. 224 | - Add mongoid 4 support. 225 | 226 | ## 0.8.0 (yanked) 227 | - Added defaults for amount and currency columns in database schema, based on the default currency. 228 | - Use a better default subunit_unit name (choose the value of column.postfix set in the config). 229 | - Began support of Rails 4. 230 | - Added global settings for money object formatted output (:no_cents_if_whole, :symbol options). 231 | - Enhanced money validator. 232 | - Added ability to use numericality validations on monetize (GH-70). 233 | - Fixed error caused by ActiveSupport::HashWithIndifferentAccess 234 | (GH-62). 235 | - Added money-rails test helper (rspec matcher). 236 | 237 | ## 0.7.1 238 | - Fix error when instantiating new model in mongoid extension (GH-60) 239 | 240 | ## 0.7.0 241 | - Added custom validator for Money fields (GH-36) 242 | - Added mongodb service test support for travis CI 243 | - Fixed issue with current value assignment in text_field tags (GH-37) 244 | - Fixed concatination of subunit_name and name (to be just a joins) to 245 | prevent an infinite loop 246 | - From now on MoneyRails is an Engine (not only a Railtie)! 247 | This means it can use all the extra stuff such as localized files, 248 | attached models etc. 249 | - Updated Money dependency version (Now we depend on 5.1.x) 250 | - Allow immediate subclasses to inherit monetized_attributes 251 | - Stopped support for MRI < 1.9.2 252 | - Fixed issue related to symbolized keys in Mongoid (GH-40) 253 | - Added add_money/remove_money & t.money/t.remove_money methods for ActiveRecord migrations 254 | 255 | ## 0.6.0 256 | - Added basic support for Mongoid >= 3.0. 257 | - Allow class methods to be monetized (ActiveRecord only - GH-34) 258 | 259 | ## 0.5.0 260 | 261 | - Refactored instance currency implementation. Now, instance currency 262 | is permitted to override the field currency, if they are both non-nil 263 | values. (GH-23) 264 | - Replaced deprecated composed_of with a custom implementation for 265 | activerecord. (GH-20) 266 | - Refactored testing structure to support multiple ORMs/ODMS. 267 | - Added Mongoid 2.x basic support. It uses serialization 268 | (a differrent approach than activerecord for now). (GH-19) 269 | 270 | ## 0.4.0 271 | 272 | - Provide ActionView helpers integration. 273 | - Map blank value assignments (for monetized fields) to nil. 274 | - Allow nil values for monetized fields. (GH-15) 275 | - Reworked support for ORM adaptation. (GH-16) 276 | 277 | ## 0.3.1 278 | 279 | - Fix bug with string assignment to monetized field. (GH-11) 280 | 281 | ## 0.3.0 282 | 283 | - Add support for model-wise currency. 284 | - Fix conversion of monetized attribute value, whether a currency 285 | table column exists or not. 286 | - Add configuration options for currency exchange (config.add_rate, 287 | config.default_bank) 288 | 289 | ## 0.2.0 290 | 291 | - Add new generator to install money.rb initializer 292 | - Create a structure to enable better ORM/ODM integration 293 | - Add configuration for numericality validation of monetized fields 294 | - Add support for custom currency registration 295 | 296 | ## 0.1.0 297 | 298 | - Use better names for args :target_name, :field_currency, 299 | :model_currency. From now on, :as, :with_currency and :with_model_currency should 300 | be used instead of the old ones which are deprecated. (GH-6) 301 | 302 | ## 0.0.1 303 | 304 | - Hello World 305 | 306 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Alegzander 2 | Andreas Loupasakis 3 | Andrew Griffith 4 | Anthony Smith 5 | Ben Pickles 6 | Borna Novak 7 | Bradley Schaefer 8 | Brandon Beacher 9 | Brian Armstrong 10 | Bryan Rite 11 | Carlos Hernandez 12 | Carlos Hernández 13 | Clarke Brunsdon 14 | Colin Bartlett 15 | Dan Beaulieu 16 | Danil Pismenny 17 | David Pelaez 18 | Deepak Kannan 19 | Derek Bender 20 | Dmitriy Dudkin 21 | Esdras Mayrink 22 | Felipe Rodrigues 23 | Fotos Georgiadis 24 | François KLINGLER 25 | Garrett Lancaster 26 | Gonzalo Rodríguez-Baltanás Díaz 27 | Graham Pengelly 28 | Hosam Aly 29 | Jacob Vosmaer 30 | James Chen 31 | Jamie Dyer 32 | Joe Frambach 33 | John Wood 34 | Kevin Sołtysiak 35 | Kirill Shamardin 36 | Michael J. 37 | Mike Boone 38 | Nick 39 | Nobu Funaki 40 | Oliver Morgan 41 | Paolo Dona 42 | Paul Wittmann 43 | Ralf S. 44 | Richard Norton 45 | Rémi Prévost 46 | Shane Emmons 47 | Simone Carletti 48 | Thomas Wright 49 | Xenor Chang 50 | Yury Kaliada 51 | avbrychak 52 | ntudor 53 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | platforms :jruby do 6 | gem "activerecord-jdbc-adapter" 7 | gem "activerecord-jdbcsqlite3-adapter" 8 | gem "jruby-openssl" 9 | end 10 | 11 | platforms :ruby do 12 | gem "sqlite3" 13 | end 14 | 15 | platform :mri do 16 | # gem "ruby-prof", "~> 0.11.2" 17 | 18 | case RUBY_VERSION 19 | when /^1.9/ 20 | gem 'debugger' 21 | end 22 | end 23 | 24 | group :development do 25 | gem "pry" 26 | gem 'rb-inotify', '~> 0.9' 27 | gem 'guard' 28 | gem 'guard-rspec' 29 | gem 'guard-rails' 30 | end 31 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard 'rspec' do 5 | watch(%r{^spec/.+_spec\.rb$}) 6 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 7 | watch('spec/spec_helper.rb') { "spec" } 8 | 9 | # Rails example 10 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 11 | watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 12 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } 13 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" } 14 | watch('config/routes.rb') { "spec/routing" } 15 | watch('app/controllers/application_controller.rb') { "spec/controllers" } 16 | 17 | # Capybara features specs 18 | watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" } 19 | 20 | # Turnip features and steps 21 | watch(%r{^spec/acceptance/(.+)\.feature$}) 22 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } 23 | end 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2012 Andreas Loupasakis 4 | Copyright (c) 2025 Shane Emmons 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RubyMoney - Money-Rails 2 | 3 | [![Gem Version](https://badge.fury.io/rb/money-rails.svg)](http://badge.fury.io/rb/money-rails) 4 | [![Ruby](https://github.com/RubyMoney/money-rails/actions/workflows/ruby.yml/badge.svg)](https://github.com/RubyMoney/money-rails/actions/workflows/ruby.yml) 5 | [![License](http://img.shields.io/:license-mit-green.svg?style=flat)](http://opensource.org/licenses/MIT) 6 | 7 | ## Introduction 8 | 9 | This library provides integration of the [money](http://github.com/Rubymoney/money) gem with Rails. 10 | 11 | Use `monetize` to specify which fields you want to be backed by 12 | Money objects and helpers provided by the [money](http://github.com/Rubymoney/money) 13 | gem. 14 | 15 | Currently, this library is in active development mode, so if you would 16 | like to have a new feature, feel free to open a new issue 17 | [here](https://github.com/RubyMoney/money-rails/issues). You are also 18 | welcome to contribute to the project. 19 | 20 | ## Installation 21 | 22 | Add this line to your application's Gemfile: 23 | 24 | ```ruby 25 | gem 'money-rails', '~> 1.12' 26 | ``` 27 | 28 | And then execute: 29 | 30 | ```sh 31 | $ bundle 32 | ``` 33 | 34 | Or install it yourself using: 35 | 36 | ```sh 37 | $ gem install money-rails 38 | ``` 39 | 40 | You can also use the money configuration initializer: 41 | 42 | ```sh 43 | $ rails g money_rails:initializer 44 | ``` 45 | 46 | There, you can define the default currency value and set other 47 | configuration parameters for the rails app. 48 | 49 | Without Rails in rack-based applications, call during initialization: 50 | 51 | ```ruby 52 | MoneyRails::Hooks.init 53 | ``` 54 | 55 | ## Usage 56 | 57 | ### ActiveRecord 58 | 59 | #### Usage example 60 | 61 | For example, we create a Product model which has an integer column called 62 | `price_cents` and we want to handle it using a `Money` object instead: 63 | 64 | ```ruby 65 | class Product < ActiveRecord::Base 66 | monetize :price_cents 67 | end 68 | ``` 69 | 70 | Now each Product object will also have an attribute called `price` which 71 | is a `Money` object, and can be used for money comparisons, conversions etc. 72 | 73 | In this case the name of the money attribute is created automagically by removing the 74 | `_cents` suffix from the column name. 75 | 76 | If you are using another database column name, or you prefer another name for the 77 | money attribute, then you can provide an `as` argument with a string value to the 78 | `monetize` macro: 79 | 80 | ```ruby 81 | monetize :discount_subunit, as: "discount" 82 | ``` 83 | 84 | Now the model objects will have a `discount` attribute which is a `Money` 85 | object, wrapping the value of the `discount_subunit` column with a Money 86 | instance. 87 | 88 | #### Migration helpers 89 | 90 | If you want to add a money field to a product model you can use the `add_monetize` helper. 91 | This helper can be customized inside a `MoneyRails.configure` block. You should customize 92 | the `add_monetize` helper to match the most common use case and utilize it across all 93 | migrations. 94 | 95 | ```ruby 96 | class MonetizeProduct < ActiveRecord::Migration 97 | def change 98 | add_monetize :products, :price 99 | 100 | # OR 101 | 102 | change_table :products do |t| 103 | t.monetize :price 104 | end 105 | end 106 | end 107 | ``` 108 | 109 | Another example, where the currency column is not included: 110 | 111 | ```ruby 112 | class MonetizeItem < ActiveRecord::Migration 113 | def change 114 | add_monetize :items, :price, currency: { present: false } 115 | end 116 | end 117 | ``` 118 | 119 | Notice: Default value of currency field, generated by migration’s helper, is 120 | USD. To override these defaults, you need change the `default_currency` in an 121 | initializer and run migrations. 122 | 123 | The `add_monetize` helper is reversible, so you can use it inside `change` 124 | migrations. If you’re writing separate `up` and `down` methods, you can use the 125 | `remove_monetize` helper. 126 | 127 | #### Allow nil values 128 | 129 | If you want to allow `nil` and/or blank values to a specific monetized field, 130 | you can use the `:allow_nil` parameter: 131 | 132 | ```ruby 133 | # in Product model 134 | monetize :optional_price_cents, allow_nil: true 135 | 136 | # in Migration 137 | def change 138 | add_monetize :products, 139 | :optional_price, 140 | amount: { null: true, default: nil }, 141 | currency: { null: true, default: nil } 142 | end 143 | 144 | # now blank assignments are permitted 145 | product.optional_price = nil 146 | product.save # returns without errors 147 | product.optional_price # => nil 148 | product.optional_price_cents # => nil 149 | ``` 150 | 151 | #### Allow large numbers 152 | 153 | If you foresee that you will be saving large values (range is -2147483648 to 154 | +2147483647 for Postgres), increase your integer column limit to `bigint`: 155 | 156 | ```ruby 157 | def change 158 | change_column :products, :price_cents, :integer, limit: 8 159 | end 160 | ``` 161 | 162 | #### Numericality validation options 163 | 164 | You can also pass along 165 | [numericality validation options](http://guides.rubyonrails.org/active_record_validations.html#numericality) 166 | such as this: 167 | 168 | ```ruby 169 | monetize :price_in_a_range_cents, 170 | allow_nil: true, 171 | numericality: { 172 | greater_than_or_equal_to: 0, 173 | less_than_or_equal_to: 10000 174 | } 175 | ``` 176 | 177 | Or, if you prefer, you can skip validations entirely for the attribute. This is 178 | useful if chosen attributes are aggregate methods and you wish to avoid executing 179 | them on every record save. 180 | 181 | ```ruby 182 | monetize :price_in_a_range_cents, disable_validation: true 183 | ``` 184 | 185 | You can also skip validations independently from each other by simply passing `false` 186 | to the validation you are willing to skip, like this: 187 | 188 | ```ruby 189 | monetize :price_in_a_range_cents, numericality: false 190 | ``` 191 | 192 | And you can also use `subunit_numericality` for subunit: 193 | 194 | ```ruby 195 | monetize :price_in_a_range_cents, 196 | allow_nil: true, 197 | subunit_numericality: { 198 | greater_than_or_equal_to: 0, 199 | less_than_or_equal_to: 100_00 200 | } 201 | ``` 202 | 203 | ### Mongoid 2.x and 3.x 204 | 205 | `Money` is available as a field type to supply during a field definition: 206 | 207 | ```ruby 208 | class Product 209 | include Mongoid::Document 210 | 211 | field :price, type: Money 212 | end 213 | 214 | obj = Product.new 215 | # => # 216 | 217 | obj.price 218 | # => nil 219 | 220 | obj.price = Money.new(100, 'EUR') 221 | # => # 222 | 223 | obj.price 224 | #=> # 225 | 226 | obj.save 227 | # => true 228 | 229 | obj 230 | # => # 231 | 232 | obj.price 233 | #=> # 234 | 235 | ## You can access the money hash too: 236 | obj[:price] 237 | # => {cents: 100, currency_iso: "EUR"} 238 | ``` 239 | 240 | The usual options on `field` as `index`, `default`, ..., are available. 241 | 242 | ### Method conversion 243 | 244 | Method return values can be monetized in the same way attributes are monetized. For example: 245 | 246 | ```ruby 247 | class Transaction < ActiveRecord::Base 248 | monetize :price_cents 249 | monetize :tax_cents 250 | monetize :total_cents 251 | 252 | def total_cents 253 | price_cents + tax_cents 254 | end 255 | end 256 | ``` 257 | 258 | Now each `Transaction` object has a method called `total` which returns a `Money` object. 259 | 260 | ### Currencies 261 | 262 | money-rails supports a set of options to handle currencies for your 263 | monetized fields. The default option for every conversion is to use 264 | the global default currency of the Money library, as given in the configuration 265 | initializer of money-rails: 266 | 267 | ```ruby 268 | # config/initializers/money.rb 269 | MoneyRails.configure do |config| 270 | # set the default currency 271 | config.default_currency = :usd 272 | end 273 | ``` 274 | 275 | For a complete list of available currencies: [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) 276 | 277 | If you need to set the default currency on a per-request basis, such as in a 278 | multi-tenant application, you may use a lambda to lazy-load the default currency 279 | from a field in a configuration model called `Tenant` in this example: 280 | 281 | ```ruby 282 | # config/initializers/money.rb 283 | MoneyRails.configure do |config| 284 | # set the default currency based on client configuration 285 | config.default_currency = -> { Tenant.current.default_currency } 286 | end 287 | ``` 288 | 289 | Be aware that this **does not work in Rails 7+**, as the lambda is evaluated 290 | immediately, and therefore requires your model to be already loaded. 291 | Workarounds include wrapping the initialization in 292 | `ActiveSupport::Reloader.to_prepare`, or creating a function that rescues 293 | unloaded constants with an initialization-time default, and running that in your lambda. 294 | 295 | In many cases this is not enough, so there are some other options to 296 | meet your needs. 297 | 298 | #### Model Currency 299 | 300 | You can override the global default currency within a specific ActiveRecord 301 | model using the `register_currency` macro: 302 | 303 | ```ruby 304 | # app/models/product.rb 305 | class Product < ActiveRecord::Base 306 | # Use EUR as model level currency 307 | register_currency :eur 308 | 309 | monetize :discount_subunit, as: "discount" 310 | monetize :bonus_cents 311 | end 312 | ``` 313 | 314 | Now `product.discount` and `product.bonus` will return a `Money` object using 315 | EUR as their currency, instead of the default USD. 316 | 317 | (This is not available in Mongoid). 318 | 319 | #### Attribute Currency (`:with_currency`) 320 | 321 | By passing the option `:with_currency` to the `monetize` macro call, 322 | with a currency code (symbol or string) or a callable object (object that responds 323 | to the `call` method) that returns a currency code, as its value, you can define 324 | a currency in a more granular way. This will let you attach the given currency 325 | only to the specified monetized model attribute (allowing you to, for example, 326 | monetize different attributes of the same model with different currencies). 327 | 328 | This allows you to override both the model level and the global default 329 | currencies: 330 | 331 | ```ruby 332 | # app/models/product.rb 333 | class Product < ActiveRecord::Base 334 | # Use EUR as the model level currency 335 | register_currency :eur 336 | 337 | monetize :discount_subunit, as: "discount" 338 | monetize :bonus_cents, with_currency: :gbp 339 | end 340 | ``` 341 | 342 | In this case `product.bonus` will return a Money object with GBP as its 343 | currency, whereas `product.discount.currency.to_s # => EUR` 344 | 345 | As mentioned earlier you can use an object that responds to the method `call` 346 | and accepts the model instance as a parameter. That means you can use a `Proc` 347 | or `lambda` (we would recommend `lambda` over `Proc` because of their 348 | [different control flow characteristics](https://stackoverflow.com/questions/1740046/whats-the-difference-between-a-proc-and-a-lambda-in-ruby)) 349 | or even define a separate `class` with an instance or class method (maybe even a 350 | `module`) to return the currency code: 351 | 352 | ```ruby 353 | class DeliveryFee 354 | def call(product) 355 | # some logic here that will return a currency code 356 | end 357 | end 358 | 359 | module OptionalPrice 360 | def self.call(product) 361 | # some logic here that will return a currency code 362 | end 363 | end 364 | 365 | class Product < ActiveRecord::Base 366 | monetize :price_cents, with_currency: ->(_product) { :gbp } 367 | monetize :delivery_fee_cents, with_currency: DeliveryFee.new 368 | monetize :optional_price_cents, with_currency: OptionalPrice 369 | end 370 | ``` 371 | 372 | #### Instance Currencies 373 | 374 | All the previous options do not require any extra model fields to hold 375 | the currency values. If the currency of a field will vary from 376 | one model instance to another, then you should add a column called `currency` 377 | to your database table and pass the option `with_model_currency` 378 | to the `monetize` macro. 379 | 380 | money-rails will use this knowledge to override the model level and global 381 | default values. Non-nil instance currency values also override attribute 382 | currency values, so they have the highest precedence. 383 | 384 | ```ruby 385 | class Transaction < ActiveRecord::Base 386 | # This model has a separate currency column 387 | attr_accessible :amount_cents, :currency, :tax_cents 388 | 389 | # Use model level currency 390 | register_currency :gbp 391 | 392 | monetize :amount_cents, with_model_currency: :currency 393 | monetize :tax_cents, with_model_currency: :currency 394 | end 395 | 396 | # Now instantiating with a specific currency overrides 397 | # the model and global currencies 398 | t = Transaction.new(amount_cents: 2500, currency: "CAD") 399 | t.amount == Money.new(2500, "CAD") # true 400 | ``` 401 | 402 | ### Configuration parameters 403 | 404 | You can handle a bunch of configuration params through `money.rb` initializer: 405 | 406 | ```ruby 407 | MoneyRails.configure do |config| 408 | # To set the default currency 409 | # 410 | # config.default_currency = :usd 411 | 412 | # Set default bank object 413 | # 414 | # Example: 415 | # config.default_bank = EuCentralBank.new 416 | 417 | # Add exchange rates to current money bank object. 418 | # (The conversion rate refers to one direction only) 419 | # 420 | # Example: 421 | # config.add_rate "USD", "CAD", 1.24515 422 | # config.add_rate "CAD", "USD", 0.803115 423 | 424 | # To handle the inclusion of validations for monetized fields 425 | # The default value is true 426 | # 427 | # config.include_validations = true 428 | 429 | # Default ActiveRecord migration configuration values for columns: 430 | # 431 | # config.amount_column = { prefix: '', # column name prefix 432 | # postfix: '_cents', # column name postfix 433 | # column_name: nil, # full column name (overrides prefix, postfix and accessor name) 434 | # type: :integer, # column type 435 | # present: true, # column will be created 436 | # null: false, # other options will be treated as column options 437 | # default: 0 438 | # } 439 | # 440 | # config.currency_column = { prefix: '', 441 | # postfix: '_currency', 442 | # column_name: nil, 443 | # type: :string, 444 | # present: true, 445 | # null: false, 446 | # default: 'USD' 447 | # } 448 | 449 | # Register a custom currency 450 | # 451 | # Example: 452 | # config.register_currency = { 453 | # priority: 1, 454 | # iso_code: "EU4", 455 | # name: "Euro with subunit of 4 digits", 456 | # symbol: "€", 457 | # symbol_first: true, 458 | # subunit: "Subcent", 459 | # subunit_to_unit: 10000, 460 | # thousands_separator: ".", 461 | # decimal_mark: "," 462 | # } 463 | 464 | # Specify a rounding mode 465 | # Any one of: 466 | # 467 | # BigDecimal::ROUND_UP, 468 | # BigDecimal::ROUND_DOWN, 469 | # BigDecimal::ROUND_HALF_UP, 470 | # BigDecimal::ROUND_HALF_DOWN, 471 | # BigDecimal::ROUND_HALF_EVEN, 472 | # BigDecimal::ROUND_CEILING, 473 | # BigDecimal::ROUND_FLOOR 474 | # 475 | # set to BigDecimal::ROUND_HALF_EVEN by default 476 | # 477 | # config.rounding_mode = BigDecimal::ROUND_HALF_UP 478 | 479 | # Set default money format globally. 480 | # Default value is nil meaning "ignore this option". 481 | # Example: 482 | # 483 | # config.default_format = { 484 | # no_cents_if_whole: nil, 485 | # symbol: nil, 486 | # sign_before_symbol: nil 487 | # } 488 | 489 | # Set whether an error should be raised when parsing money values 490 | # This includes assigning to a monetized field with the wrong currency 491 | # Default value is false 492 | # 493 | # config.raise_error_on_money_parsing = true 494 | end 495 | ``` 496 | 497 | * `default_currency`: Set the default (application wide) currency (USD is the default) 498 | * `include_validations`: Permit the inclusion of a `validates_numericality_of` 499 | validation for each monetized field (the default is true) 500 | * `register_currency`: Register one custom currency. This option can be 501 | used more than once to set more custom currencies. The value should be 502 | a hash of all the necessary key/value pairs (important keys: `:priority`, `:iso_code`, 503 | `:name`, `:symbol`, `:symbol_first`, `:subunit`, `:subunit_to_unit`, 504 | `:thousands_separator`, `:decimal_mark`). 505 | * `add_rate`: Provide custom exchange rate for currencies in one direction only! This 506 | rate is added to the attached bank object. 507 | * `default_bank`: The default bank object holding exchange rates etc. 508 | (https://github.com/RubyMoney/money#currency-exchange) 509 | * `default_format`: Force `Money#format` to use these options for formatting. 510 | * `amount_column`: Provide values for the amount column (holding the fractional part of a money object). 511 | * `currency_column`: Provide default values or even disable (`present: false`) the currency column. 512 | * `rounding_mode`: Set `Money.rounding_mode` to one of the BigDecimal constants. 513 | * `raise_error_on_money_parsing`: Set whether errors should be raised when parsing money values 514 | 515 | ### Helpers 516 | 517 | _For examples below, `@money_object == `_ 518 | 519 | | Helper | Result | 520 | |:--- |:--- | 521 | | `currency_symbol` | `$` | 522 | | `humanized_money @money_object` | 6.50 | 523 | | `humanized_money_with_symbol @money_object` | $6.50 | 524 | | `money_without_cents @money_object` | 6 | 525 | | `money_without_cents_and_with_symbol @money_object` | $6 | 526 | | `money_only_cents @money_object` | 50 | 527 | 528 | #### `no_cents_if_whole` configuration param 529 | 530 | `humanized_money` and `humanized_money_with_symbol` will not render the cents part if it contains only zeros, unless `config.no_cents_if_whole` is set to `false` in the `money.rb` configuration (default: true). 531 | Note that the `config.default_format` will be overwritten by `config.no_cents_if_whole`. 532 | So `humanized_money` will ignore `config.default_format = { no_cents_if_whole: false }` if you don't set `config.no_cents_if_whole = false`. 533 | 534 | ### Testing 535 | 536 | If you use Rspec there is a test helper implementation. 537 | Just write `require "money-rails/test_helpers"` in `spec_helper.rb`. 538 | 539 | #### The `monetize` matcher 540 | 541 | ```ruby 542 | is_expected.to monetize(:price) 543 | ``` 544 | 545 | This will ensure that a column called `price_cents` is being monetized. 546 | 547 | ```ruby 548 | is_expected.to monetize(:price).allow_nil 549 | ``` 550 | 551 | By using `allow_nil` you can specify money attributes that accept nil values. 552 | 553 | ```ruby 554 | is_expected.to monetize(:price).as(:discount_value) 555 | ``` 556 | 557 | By using `as` chain you can specify the exact name to which a monetized 558 | column is being mapped. 559 | 560 | ```ruby 561 | is_expected.to monetize(:price).with_currency(:gbp) 562 | ``` 563 | 564 | By using the `with_currency` chain you can specify the expected currency 565 | for the chosen money attribute. (You can also combine all the chains.) 566 | 567 | ```ruby 568 | is_expected.to monetize(:price).with_model_currency(:currency) 569 | ``` 570 | 571 | By using the `with_model_currency` chain you can specify the attribute that 572 | contains the currency to be used for the chosen money attribute. 573 | 574 | For examples on using the test_helpers look at 575 | [test_helpers_spec.rb](https://github.com/RubyMoney/money-rails/blob/master/spec/test_helpers_spec.rb) 576 | 577 | ## Supported ORMs/ODMs 578 | 579 | * ActiveRecord (>= 3.x) 580 | * Mongoid (>= 2.x) 581 | 582 | ## Supported Ruby interpreters 583 | 584 | * MRI Ruby >= 2.6 585 | 586 | You can see a full list of the currently supported interpreters in 587 | [ruby.yml](https://github.com/RubyMoney/money-rails/blob/main/.github/workflows/ruby.yml) 588 | 589 | ## Contributing 590 | 591 | ### Steps 592 | 593 | 1. Fork the repo 594 | 2. Run the tests 595 | 3. Make your changes 596 | 4. Test your changes 597 | 5. Create a Pull Request 598 | 599 | ### How to run the tests 600 | 601 | Our tests are executed with several ORMs - see `Rakefile` for details. To install all required gems run `rake spec:all` That command will take care of installing all required gems for all the different Gemfiles and then running the test suite with the installed bundle. 602 | 603 | You can also run the test suite against a specific ORM or Rails version, `rake -T` will give you an idea of the possible task (take a look at the tasks under the spec: namespace). 604 | 605 | If you are testing against mongoid, make sure to have the mongod process running before executing the suite, (E.g. `sudo mongod --quiet`) 606 | 607 | ## Maintainers 608 | 609 | * Andreas Loupasakis (https://github.com/alup) 610 | * Shane Emmons (https://github.com/semmons99) 611 | * Simone Carletti (https://github.com/weppos) 612 | 613 | ## License 614 | 615 | [MIT License](https://github.com/RubyMoney/money-rails/blob/main/LICENSE). Copyright 2023 RubyMoney. 616 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | require 'bundler/gem_tasks' 6 | 7 | begin 8 | Bundler.setup(:default, :development) 9 | rescue Bundler::BundlerError => e 10 | $stderr.puts e.message 11 | $stderr.puts "Run `bundle install` to install missing gems" 12 | exit e.status_code 13 | end 14 | 15 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__) 16 | GEMFILES_PATH = 'gemfiles/*.gemfile'.freeze 17 | 18 | load 'rails/tasks/engine.rake' if File.exist?(APP_RAKEFILE) 19 | 20 | require 'rake' 21 | require 'rspec/core/rake_task' 22 | 23 | RSpec::Core::RakeTask.new 24 | 25 | task default: "spec:all" 26 | task test: :spec 27 | task spec: :prepare_test_env 28 | 29 | desc "Prepare money-rails engine test environment" 30 | task :prepare_test_env do 31 | Rake.application['app:db:drop:all'].invoke 32 | Rake.application['app:db:create'].invoke if Rails::VERSION::MAJOR >= 5 33 | Rake.application['app:db:migrate'].invoke 34 | Rake.application['app:db:test:prepare'].invoke 35 | end 36 | 37 | def run_with_gemfile(gemfile) 38 | Bundler.with_original_env do 39 | begin 40 | sh "BUNDLE_GEMFILE='#{gemfile}' bundle install --quiet" 41 | Rake.application['app:db:create'].invoke 42 | Rake.application['app:db:test:prepare'].invoke 43 | sh "BUNDLE_GEMFILE='#{gemfile}' bundle exec rake spec" 44 | ensure 45 | Rake.application['app:db:drop:all'].execute 46 | end 47 | end 48 | end 49 | 50 | namespace :spec do 51 | frameworks_versions = {} 52 | 53 | Dir[GEMFILES_PATH].each do |gemfile| 54 | file_name = File.basename(gemfile, '.gemfile') 55 | framework, version = file_name.split(/(\d+)/) 56 | major, minor = version.split(//) 57 | 58 | # Ruby 3 exclusions 59 | if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0') 60 | # Rails 5 does not support ruby-3.0.0 https://github.com/rails/rails/issues/40938#issuecomment-751569171 61 | # Mongoid gem does not yet support ruby-3.0.0 https://github.com/mongodb/mongoid#compatibility 62 | next if framework == 'mongoid' || (framework == 'rails' && version == "5") 63 | end 64 | 65 | # Skip Rails 7 unless the Ruby version is at least 2.7 66 | if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7') 67 | next if framework == 'rails' && version == "7" 68 | end 69 | 70 | 71 | frameworks_versions[framework] ||= [] 72 | frameworks_versions[framework] << file_name 73 | 74 | desc "Run Tests against #{framework} #{[major, minor].compact.join('.')}" 75 | task(file_name) { run_with_gemfile gemfile } 76 | end 77 | 78 | frameworks_versions.each do |framework, versions| 79 | desc "Run Tests against all supported #{framework} versions" 80 | task framework => versions 81 | end 82 | 83 | desc 'Run Tests against all ORMs' 84 | task all: frameworks_versions.keys 85 | end 86 | 87 | desc "Update CONTRIBUTORS file" 88 | task :contributors do 89 | sh "git shortlog -s | awk '{ print $2 \" \" $3 }' > CONTRIBUTORS" 90 | end 91 | -------------------------------------------------------------------------------- /config/locales/money.da.yml: -------------------------------------------------------------------------------- 1 | da: 2 | errors: 3 | messages: 4 | invalid_currency: skal være en gyldig valuta (f.eks. '100', '5%{decimal}24', eller '123%{thousands}456%{decimal}78'). Fik %{currency} 5 | -------------------------------------------------------------------------------- /config/locales/money.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | messages: 4 | invalid_currency: has invalid format (must be '100', '5%{decimal}24', or '123%{thousands}456%{decimal}78'). Got %{currency} 5 | -------------------------------------------------------------------------------- /config/locales/money.ru.yml: -------------------------------------------------------------------------------- 1 | ru: 2 | errors: 3 | messages: 4 | invalid_currency: имеет неверный формат (должно быть '100', '5%{decimal}24', или '123%{thousands}456%{decimal}78'). Получено %{currency} 5 | -------------------------------------------------------------------------------- /gemfiles/mongoid6.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'mongoid', '~> 6.0' 4 | rails_version = ['~> 5.1'] 5 | rails_version << '< 5.2.4.1' if RUBY_VERSION < '2.3' # temporary fix until 5.2.4.2 is released 6 | gem 'rails', rails_version 7 | gem 'i18n', '~> 0.7' 8 | 9 | platforms :jruby do 10 | gem "activerecord-jdbc-adapter" 11 | gem "activerecord-jdbcsqlite3-adapter" 12 | gem "jruby-openssl" 13 | end 14 | 15 | platforms :ruby do 16 | gem "sqlite3", "~> 1.3.6" 17 | end 18 | 19 | gemspec path: '../' 20 | -------------------------------------------------------------------------------- /gemfiles/mongoid7.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'mongoid', '~> 7.0' 4 | rails_version = ['~> 5.1'] 5 | rails_version << '< 5.2.4.1' if RUBY_VERSION < '2.3' # temporary fix until 5.2.4.2 is released 6 | gem 'rails', rails_version 7 | gem 'i18n', '~> 0.7' 8 | 9 | platforms :jruby do 10 | gem "activerecord-jdbc-adapter" 11 | gem "activerecord-jdbcsqlite3-adapter" 12 | gem "jruby-openssl" 13 | end 14 | 15 | platforms :ruby do 16 | gem "sqlite3", "~> 1.3.6" 17 | end 18 | 19 | gemspec path: '../' 20 | -------------------------------------------------------------------------------- /gemfiles/rails5.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | rails_version = ['~> 5.1'] 4 | rails_version << '< 5.2.4.1' if RUBY_VERSION < '2.3' # temporary fix until 5.2.4.2 is released 5 | gem 'rails', rails_version 6 | gem 'i18n', '< 1.5' 7 | 8 | platforms :jruby do 9 | gem "activerecord-jdbc-adapter" 10 | gem "activerecord-jdbcsqlite3-adapter" 11 | gem "jruby-openssl" 12 | end 13 | 14 | platforms :ruby do 15 | gem "sqlite3", "~> 1.3.6" 16 | end 17 | 18 | gemspec path: '../' 19 | -------------------------------------------------------------------------------- /gemfiles/rails6.1.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '~> 6.1.0' 4 | gem 'i18n', '~> 1.6' 5 | 6 | platforms :jruby do 7 | gem "activerecord-jdbc-adapter" 8 | gem "activerecord-jdbcsqlite3-adapter" 9 | gem "jruby-openssl" 10 | end 11 | 12 | platforms :ruby do 13 | gem "sqlite3", "~> 1.4" 14 | end 15 | 16 | gemspec path: '../' 17 | -------------------------------------------------------------------------------- /gemfiles/rails7.0.gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '~> 7.0.0' 4 | gem 'sprockets-rails' 5 | gem 'i18n', '~> 1.6' 6 | 7 | platforms :jruby do 8 | gem "activerecord-jdbc-adapter" 9 | gem "activerecord-jdbcsqlite3-adapter" 10 | gem "jruby-openssl" 11 | end 12 | 13 | platforms :ruby do 14 | gem "sqlite3", "~> 1.4" 15 | end 16 | 17 | gemspec path: '../' 18 | -------------------------------------------------------------------------------- /lib/generators/money_rails/initializer_generator.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | module Generators 3 | class InitializerGenerator < ::Rails::Generators::Base 4 | 5 | source_root File.expand_path("../../templates", __FILE__) 6 | 7 | desc 'Creates a sample MoneyRails initializer.' 8 | 9 | def copy_initializer 10 | copy_file 'money.rb', 'config/initializers/money.rb' 11 | end 12 | 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/generators/templates/money.rb: -------------------------------------------------------------------------------- 1 | # encoding : utf-8 2 | 3 | MoneyRails.configure do |config| 4 | 5 | # To set the default currency 6 | # 7 | # config.default_currency = :usd 8 | 9 | # Set default bank object 10 | # 11 | # Example: 12 | # config.default_bank = EuCentralBank.new 13 | 14 | # Add exchange rates to current money bank object. 15 | # (The conversion rate refers to one direction only) 16 | # 17 | # Example: 18 | # config.add_rate "USD", "CAD", 1.24515 19 | # config.add_rate "CAD", "USD", 0.803115 20 | 21 | # To handle the inclusion of validations for monetized fields 22 | # The default value is true 23 | # 24 | # config.include_validations = true 25 | 26 | # Default ActiveRecord migration configuration values for columns: 27 | # 28 | # config.amount_column = { prefix: '', # column name prefix 29 | # postfix: '_cents', # column name postfix 30 | # column_name: nil, # full column name (overrides prefix, postfix and accessor name) 31 | # type: :integer, # column type 32 | # present: true, # column will be created 33 | # null: false, # other options will be treated as column options 34 | # default: 0 35 | # } 36 | # 37 | # config.currency_column = { prefix: '', 38 | # postfix: '_currency', 39 | # column_name: nil, 40 | # type: :string, 41 | # present: true, 42 | # null: false, 43 | # default: 'USD' 44 | # } 45 | 46 | # Register a custom currency 47 | # 48 | # Example: 49 | # config.register_currency = { 50 | # priority: 1, 51 | # iso_code: "EU4", 52 | # name: "Euro with subunit of 4 digits", 53 | # symbol: "€", 54 | # symbol_first: true, 55 | # subunit: "Subcent", 56 | # subunit_to_unit: 10000, 57 | # thousands_separator: ".", 58 | # decimal_mark: "," 59 | # } 60 | 61 | # Specify a rounding mode 62 | # Any one of: 63 | # 64 | # BigDecimal::ROUND_UP, 65 | # BigDecimal::ROUND_DOWN, 66 | # BigDecimal::ROUND_HALF_UP, 67 | # BigDecimal::ROUND_HALF_DOWN, 68 | # BigDecimal::ROUND_HALF_EVEN, 69 | # BigDecimal::ROUND_CEILING, 70 | # BigDecimal::ROUND_FLOOR 71 | # 72 | # set to BigDecimal::ROUND_HALF_EVEN by default 73 | # 74 | # config.rounding_mode = BigDecimal::ROUND_HALF_UP 75 | 76 | # Set default money format globally. 77 | # Default value is nil meaning "ignore this option". 78 | # Example: 79 | # 80 | # config.default_format = { 81 | # no_cents_if_whole: nil, 82 | # symbol: nil, 83 | # sign_before_symbol: nil 84 | # } 85 | 86 | # If you would like to use I18n localization (formatting depends on the 87 | # locale): 88 | # config.locale_backend = :i18n 89 | # 90 | # Example (using default localization from rails-i18n): 91 | # 92 | # I18n.locale = :en 93 | # Money.new(10_000_00, 'USD').format # => $10,000.00 94 | # I18n.locale = :es 95 | # Money.new(10_000_00, 'USD').format # => $10.000,00 96 | # 97 | # For the legacy behaviour of "per currency" localization (formatting depends 98 | # only on currency): 99 | # config.locale_backend = :currency 100 | # 101 | # Example: 102 | # Money.new(10_000_00, 'USD').format # => $10,000.00 103 | # Money.new(10_000_00, 'EUR').format # => €10.000,00 104 | # 105 | # In case you don't need localization and would like to use default values 106 | # (can be redefined using config.default_format): 107 | # config.locale_backend = nil 108 | 109 | # Set default raise_error_on_money_parsing option 110 | # It will be raise error if assigned different currency 111 | # The default value is false 112 | # 113 | # Example: 114 | # config.raise_error_on_money_parsing = false 115 | end 116 | -------------------------------------------------------------------------------- /lib/money-rails.rb: -------------------------------------------------------------------------------- 1 | require "money" 2 | require "monetize" 3 | require "monetize/core_extensions" 4 | require "money-rails/configuration" 5 | require "money-rails/money" 6 | require "money-rails/version" 7 | require 'money-rails/hooks' 8 | require 'money-rails/errors' 9 | 10 | module MoneyRails 11 | extend Configuration 12 | end 13 | 14 | if defined? ::Rails::Railtie 15 | require "money-rails/railtie" 16 | require "money-rails/engine" 17 | end 18 | 19 | if Object.const_defined?("RailsAdmin") 20 | require "money-rails/rails_admin" 21 | end 22 | -------------------------------------------------------------------------------- /lib/money-rails/active_job/money_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MoneyRails 4 | module ActiveJob 5 | class MoneySerializer < ::ActiveJob::Serializers::ObjectSerializer 6 | def serialize?(argument) 7 | argument.is_a?(Money) 8 | end 9 | 10 | def serialize(money) 11 | super("cents" => money.cents, "currency" => money.currency.to_s) 12 | end 13 | 14 | def deserialize(hash) 15 | Money.new(hash["cents"], hash["currency"]) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/money-rails/active_model/validator.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | module ActiveModel 3 | class MoneyValidator < ::ActiveModel::Validations::NumericalityValidator 4 | class Details < Struct.new(:raw_value, :thousands_separator, :decimal_mark) 5 | def abs_raw_value 6 | @abs_raw_value ||= raw_value.to_s.sub(/^\s*-/, "").strip 7 | end 8 | 9 | def decimal_pieces 10 | @decimal_pieces ||= abs_raw_value.split(decimal_mark) 11 | end 12 | end 13 | 14 | def validate_each(record, attr, _value) 15 | subunit_attr = record.class.monetized_attributes[attr.to_s] 16 | currency = record.public_send("currency_for_#{attr}") 17 | 18 | # WARNING: Currently this is only defined in ActiveRecord extension! 19 | before_type_cast = :"#{attr}_money_before_type_cast" 20 | raw_value = record.try(before_type_cast) 21 | 22 | # If raw value is nil and changed subunit is nil, then 23 | # nil is a assigned value, else we should treat the 24 | # subunit value as the one assigned. 25 | if raw_value.nil? && record.public_send(subunit_attr) 26 | subunit_value = record.public_send(subunit_attr) 27 | raw_value = subunit_value.to_f / currency.subunit_to_unit 28 | end 29 | 30 | return if options[:allow_nil] && raw_value.nil? 31 | 32 | # Set this before we modify raw_value below. 33 | stringy = raw_value.present? && !raw_value.is_a?(Numeric) && !raw_value.is_a?(Money) 34 | 35 | if stringy 36 | # TODO: This is supporting legacy behaviour where a symbol can come from a i18n locale, 37 | # however practical implications of that are most likely non-existent 38 | symbol = lookup(:symbol, currency) || currency.symbol 39 | 40 | # remove currency symbol 41 | raw_value = raw_value.to_s.gsub(symbol, "") 42 | end 43 | 44 | # Cache abs_raw_value before normalizing because it's used in 45 | # many places and relies on the original raw_value. 46 | details = generate_details(raw_value, currency) 47 | normalized_raw_value = normalize(details) 48 | 49 | super(record, attr, normalized_raw_value) 50 | 51 | return unless stringy 52 | return if record_already_has_error?(record, attr, normalized_raw_value) 53 | 54 | add_error!(record, attr, details) if 55 | value_has_too_many_decimal_points(details) || 56 | thousand_separator_after_decimal_mark(details) || 57 | invalid_thousands_separation(details) 58 | end 59 | 60 | private 61 | 62 | DEFAULTS = { 63 | decimal_mark: '.', 64 | thousands_separator: ',' 65 | }.freeze 66 | 67 | def generate_details(raw_value, currency) 68 | thousands_separator = lookup(:thousands_separator, currency) 69 | decimal_mark = lookup(:decimal_mark, currency) 70 | 71 | Details.new(raw_value, thousands_separator, decimal_mark) 72 | end 73 | 74 | def record_already_has_error?(record, attr, raw_value) 75 | record.errors.added?(attr, :not_a_number, value: raw_value) 76 | end 77 | 78 | def add_error!(record, attr, details) 79 | attr_name = attr.to_s.tr('.', '_').humanize 80 | attr_name = record.class.human_attribute_name(attr, default: attr_name) 81 | 82 | record.errors.add(attr, :invalid_currency, 83 | thousands: details.thousands_separator, 84 | decimal: details.decimal_mark, 85 | currency: details.abs_raw_value, 86 | attribute: attr_name 87 | ) 88 | end 89 | 90 | def value_has_too_many_decimal_points(details) 91 | ![1, 2].include?(details.decimal_pieces.length) 92 | end 93 | 94 | def thousand_separator_after_decimal_mark(details) 95 | details.thousands_separator.present? && 96 | details.decimal_pieces.length == 2 && 97 | details.decimal_pieces[1].include?(details.thousands_separator) 98 | end 99 | 100 | def invalid_thousands_separation(details) 101 | pieces_array = details.decimal_pieces[0].split(details.thousands_separator.presence) 102 | 103 | return false if pieces_array.length <= 1 104 | return true if pieces_array[0].length > 3 105 | 106 | pieces_array[1..-1].any? do |thousands_group| 107 | thousands_group.length != 3 108 | end 109 | end 110 | 111 | # Remove thousands separators, normalize decimal mark, 112 | # remove whitespaces and _ (E.g. 99 999 999 or 12_300_200.20) 113 | def normalize(details) 114 | details.raw_value 115 | .to_s 116 | .gsub(details.thousands_separator, '') 117 | .gsub(details.decimal_mark, '.') 118 | .gsub(/[\s_]/, '') 119 | end 120 | 121 | def lookup(key, currency) 122 | if locale_backend 123 | locale_backend.lookup(key, currency) || DEFAULTS[key] 124 | else 125 | DEFAULTS[key] 126 | end 127 | end 128 | 129 | def locale_backend 130 | Money.locale_backend 131 | end 132 | end 133 | end 134 | end 135 | 136 | # Compatibility with ActiveModel validates method which matches option keys to their validator class 137 | ActiveModel::Validations::MoneyValidator = MoneyRails::ActiveModel::MoneyValidator 138 | -------------------------------------------------------------------------------- /lib/money-rails/active_record/migration_extensions/options_extractor.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | module ActiveRecord 3 | module MigrationExtensions 4 | class OptionsExtractor 5 | def self.extract(attribute, table_name, accessor, options = {}) 6 | default = MoneyRails::Configuration.send("#{attribute}_column").merge(options[attribute] || {}) 7 | 8 | default[:column_name] ||= [default[:prefix], accessor, default[:postfix]].join 9 | default[:table_name] = table_name 10 | 11 | excluded_keys = [:amount, :currency, :type, :prefix, :postfix, :present, :column_name, :table_name] 12 | default[:options] = default.except(*excluded_keys) 13 | 14 | default.slice(:present, :table_name, :column_name, :type, :options).values 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/money-rails/active_record/migration_extensions/schema_statements.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | module ActiveRecord 3 | module MigrationExtensions 4 | module SchemaStatements 5 | def add_money(table_name, accessor, options={}) 6 | add_monetize(table_name, accessor, options) 7 | end 8 | 9 | def remove_money(table_name, accessor, options={}) 10 | remove_monetize(table_name, accessor, options) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/money-rails/active_record/migration_extensions/schema_statements_pg_rails4.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | module ActiveRecord 3 | module MigrationExtensions 4 | module SchemaStatements 5 | def add_monetize(table_name, accessor, options={}) 6 | [:amount, :currency].each do |attribute| 7 | column_present, *opts = OptionsExtractor.extract attribute, table_name, accessor, options 8 | constraints = opts.pop 9 | add_column(*opts, **constraints) if column_present 10 | end 11 | end 12 | 13 | def remove_monetize(table_name, accessor, options={}) 14 | [:amount, :currency].each do |attribute| 15 | column_present, table_name, column_name, type, _ = OptionsExtractor.extract attribute, table_name, accessor, options 16 | remove_column table_name, column_name, type if column_present 17 | end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/money-rails/active_record/migration_extensions/table.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | module ActiveRecord 3 | module MigrationExtensions 4 | module Table 5 | def money(accessor, options={}) 6 | monetize(accessor, options) 7 | end 8 | 9 | def remove_money(accessor, options={}) 10 | remove_monetize(accessor, options) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/money-rails/active_record/migration_extensions/table_pg_rails4.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | module ActiveRecord 3 | module MigrationExtensions 4 | module Table 5 | def monetize(accessor, options={}) 6 | [:amount, :currency].each do |attribute| 7 | column_present, _, *opts = OptionsExtractor.extract attribute, :no_table, accessor, options 8 | constraints = opts.pop 9 | column(*opts, **constraints) if column_present 10 | end 11 | end 12 | 13 | def remove_monetize(accessor, options={}) 14 | [:amount, :currency].each do |attribute| 15 | column_present, _, column_name, _, _ = OptionsExtractor.extract attribute, :no_table, accessor, options 16 | remove column_name if column_present 17 | end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/money-rails/active_record/monetizable.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | require 'active_support/core_ext/array/extract_options' 3 | require 'active_support/deprecation/reporting' 4 | 5 | module MoneyRails 6 | module ActiveRecord 7 | module Monetizable 8 | class ReadOnlyCurrencyException < MoneyRails::Error; end 9 | extend ActiveSupport::Concern 10 | 11 | module ClassMethods 12 | def monetized_attributes 13 | monetized_attributes = @monetized_attributes || {}.with_indifferent_access 14 | 15 | if superclass.respond_to?(:monetized_attributes) 16 | monetized_attributes.merge(superclass.monetized_attributes) 17 | else 18 | monetized_attributes 19 | end 20 | end 21 | 22 | def monetize(*fields) 23 | options = fields.extract_options! 24 | 25 | fields.each do |field| 26 | # Stringify model field name 27 | subunit_name = field.to_s 28 | 29 | if options[:field_currency] || options[:target_name] || options[:model_currency] 30 | ActiveSupport::Deprecation.warn("You are using the old " \ 31 | "argument keys of the monetize command! Instead use :as, " \ 32 | ":with_currency or :with_model_currency") 33 | end 34 | 35 | name = options[:as] || options[:target_name] || nil 36 | 37 | # Form target name for the money backed ActiveModel field: 38 | # if a target name is provided then use it 39 | # if there is a "{column.postfix}" suffix then just remove it to create the target name 40 | # if none of the previous is the case then use a default suffix 41 | if name 42 | name = name.to_s 43 | 44 | # Check if the options[:as] parameter is not the same as subunit_name 45 | # which would result in stack overflow 46 | if name == subunit_name 47 | raise ArgumentError, "monetizable attribute name cannot be the same as options[:as] parameter" 48 | end 49 | 50 | elsif subunit_name =~ /#{MoneyRails::Configuration.amount_column[:postfix]}$/ 51 | name = subunit_name.sub(/#{MoneyRails::Configuration.amount_column[:postfix]}$/, "") 52 | else 53 | raise ArgumentError, "Unable to infer the name of the monetizable attribute for '#{subunit_name}'. " \ 54 | "Expected amount column postfix is '#{MoneyRails::Configuration.amount_column[:postfix]}'. " \ 55 | "Use :as option to explicitly specify the name or change the amount column postfix in the initializer." 56 | end 57 | 58 | # Optional accessor to be run on an instance to detect currency 59 | instance_currency_name = options[:with_model_currency] || 60 | options[:model_currency] || 61 | MoneyRails::Configuration.currency_column[:column_name] 62 | 63 | # Infer currency column from name and postfix 64 | if !instance_currency_name && MoneyRails::Configuration.currency_column[:postfix].present? 65 | instance_currency_name = "#{name}#{MoneyRails::Configuration.currency_column[:postfix]}" 66 | end 67 | 68 | instance_currency_name = instance_currency_name && instance_currency_name.to_s 69 | 70 | # This attribute allows per column currency values 71 | # Overrides row and default currency 72 | field_currency_name = options[:with_currency] || 73 | options[:field_currency] || nil 74 | 75 | # Create a reverse mapping of the monetized attributes 76 | track_monetized_attribute name, subunit_name 77 | 78 | # Include numericality validations if needed. 79 | # There are two validation options: 80 | # 81 | # 1. Subunit field validation (e.g. cents should be > 100) 82 | # 2. Money field validation (e.g. euros should be > 10) 83 | # 84 | # All the options which are available for Rails numericality 85 | # validation, are also available for both types. 86 | # E.g. 87 | # monetize :price_in_a_range_cents, allow_nil: true, 88 | # subunit_numericality: { 89 | # greater_than_or_equal_to: 0, 90 | # less_than_or_equal_to: 10000, 91 | # }, 92 | # numericality: { 93 | # greater_than_or_equal_to: 0, 94 | # less_than_or_equal_to: 100, 95 | # message: "must be greater than zero and less than $100" 96 | # } 97 | # 98 | # To disable validation entirely, use :disable_validation, E.g: 99 | # monetize :price_in_a_range_cents, disable_validation: true 100 | if (validation_enabled = MoneyRails.include_validations && !options[:disable_validation]) 101 | 102 | # This is a validation for the subunit 103 | if (subunit_numericality = options.fetch(:subunit_numericality, true)) 104 | validates subunit_name, { 105 | allow_nil: options[:allow_nil], 106 | numericality: subunit_numericality 107 | } 108 | end 109 | 110 | # Allow only Money objects or Numeric values! 111 | if (numericality = options.fetch(:numericality, true)) 112 | validates name.to_sym, { 113 | allow_nil: options[:allow_nil], 114 | 'money_rails/active_model/money' => numericality 115 | } 116 | end 117 | end 118 | 119 | 120 | # Getter for monetized attribute 121 | define_method name do |*args, **kwargs| 122 | read_monetized name, subunit_name, options, *args, **kwargs 123 | end 124 | 125 | # Setter for monetized attribute 126 | define_method "#{name}=" do |value| 127 | write_monetized name, subunit_name, value, validation_enabled, instance_currency_name, options 128 | end 129 | 130 | if validation_enabled 131 | # Ensure that the before_type_cast value is cleared when setting 132 | # the subunit value directly 133 | define_method "#{subunit_name}=" do |value| 134 | instance_variable_set "@#{name}_money_before_type_cast", nil 135 | write_attribute(subunit_name, value) 136 | end 137 | end 138 | 139 | # Currency getter 140 | define_method "currency_for_#{name}" do 141 | currency_for name, instance_currency_name, field_currency_name 142 | end 143 | 144 | attr_reader "#{name}_money_before_type_cast" 145 | 146 | # Hook to ensure the reset of before_type_cast attr 147 | # TODO: think of a better way to avoid this 148 | after_save do 149 | instance_variable_set "@#{name}_money_before_type_cast", nil 150 | end 151 | end 152 | end 153 | 154 | def register_currency(currency_name) 155 | # Lookup the given currency_name and raise exception if 156 | # no currency is found 157 | currency_object = Money::Currency.find currency_name 158 | raise(ArgumentError, "Can't find #{currency_name} currency code") unless currency_object 159 | 160 | class_eval do 161 | @currency = currency_object 162 | class << self 163 | attr_reader :currency 164 | end 165 | end 166 | end 167 | 168 | private 169 | 170 | def track_monetized_attribute(name, value) 171 | @monetized_attributes ||= {}.with_indifferent_access 172 | 173 | if @monetized_attributes[name].present? 174 | raise ArgumentError, "#{self} already has a monetized attribute called '#{name}'" 175 | end 176 | 177 | @monetized_attributes[name] = value 178 | end 179 | end 180 | 181 | def read_monetized(name, subunit_name, options = nil, *args, **kwargs) 182 | # Ruby 2.x compatibility 183 | if options.nil? 184 | options = kwargs 185 | kwargs = {} 186 | end 187 | 188 | if kwargs.any? 189 | amount = public_send(subunit_name, *args, **kwargs) 190 | else 191 | # Ruby 2.x does not allow empty kwargs 192 | amount = public_send(subunit_name, *args) 193 | end 194 | 195 | return if amount.nil? && options[:allow_nil] 196 | # Get the currency object 197 | attr_currency = public_send("currency_for_#{name}") 198 | 199 | # Get the cached value 200 | memoized = instance_variable_get("@#{name}") 201 | 202 | # Dont create a new Money instance if the values haven't been changed. 203 | if memoized && memoized.cents == amount 204 | if memoized.currency == attr_currency 205 | result = memoized 206 | else 207 | memoized_amount = memoized.amount.to_money(attr_currency) 208 | write_attribute subunit_name, memoized_amount.cents 209 | # Cache the value (it may be nil) 210 | result = instance_variable_set("@#{name}", memoized_amount) 211 | end 212 | elsif amount.present? 213 | # If amount is NOT nil (or empty string) load the amount in a Money 214 | amount = Money.new(amount, attr_currency) 215 | 216 | # Cache the value (it may be nil) 217 | result = instance_variable_set("@#{name}", amount) 218 | end 219 | 220 | if MoneyRails::Configuration.preserve_user_input 221 | value_before_type_cast = instance_variable_get "@#{name}_money_before_type_cast" 222 | if errors.has_key?(name.to_sym) 223 | result.define_singleton_method(:to_s) { value_before_type_cast } 224 | result.define_singleton_method(:format) { |_| value_before_type_cast } 225 | end 226 | end 227 | 228 | result 229 | end 230 | 231 | def write_monetized(name, subunit_name, value, validation_enabled, instance_currency_name, options) 232 | # Keep before_type_cast value as a reference to original input 233 | instance_variable_set "@#{name}_money_before_type_cast", value 234 | 235 | # Use nil or get a Money object 236 | if options[:allow_nil] && value.blank? 237 | money = nil 238 | else 239 | if value.is_a?(Money) 240 | money = value 241 | else 242 | begin 243 | money = value.to_money(public_send("currency_for_#{name}")) 244 | rescue NoMethodError 245 | return nil 246 | rescue Money::Currency::UnknownCurrency, Monetize::ParseError => e 247 | raise MoneyRails::Error, e.message if MoneyRails.raise_error_on_money_parsing 248 | return nil 249 | end 250 | end 251 | end 252 | 253 | # Update cents 254 | if !validation_enabled 255 | # We haven't defined our own subunit writer, so we can invoke 256 | # the regular writer, which works with store_accessors 257 | public_send("#{subunit_name}=", money.try(:cents)) 258 | elsif self.class.respond_to?(:attribute_aliases) && 259 | self.class.attribute_aliases.key?(subunit_name) 260 | # If the attribute is aliased, make sure we write to the original 261 | # attribute name or an error will be raised. 262 | # (Note: 'attribute_aliases' doesn't exist in Rails 3.x, so we 263 | # can't tell if the attribute was aliased.) 264 | original_name = self.class.attribute_aliases[subunit_name.to_s] 265 | write_attribute(original_name, money.try(:cents)) 266 | else 267 | write_attribute(subunit_name, money.try(:cents)) 268 | end 269 | 270 | if money_currency = money.try(:currency) 271 | # Update currency iso value if there is an instance currency attribute 272 | if instance_currency_name.present? && respond_to?("#{instance_currency_name}=") 273 | public_send("#{instance_currency_name}=", money_currency.iso_code) 274 | else 275 | current_currency = public_send("currency_for_#{name}") 276 | if current_currency != money_currency.id 277 | raise ReadOnlyCurrencyException.new("Can't change readonly currency '#{current_currency}' to '#{money_currency}' for field '#{name}'") if MoneyRails.raise_error_on_money_parsing 278 | return nil 279 | end 280 | end 281 | end 282 | 283 | # Save and return the new Money object 284 | instance_variable_set "@#{name}", money 285 | end 286 | 287 | def currency_for(name, instance_currency_name, field_currency_name) 288 | if instance_currency_name.present? && respond_to?(instance_currency_name) && 289 | Money::Currency.find(public_send(instance_currency_name)) 290 | 291 | Money::Currency.find(public_send(instance_currency_name)) 292 | elsif field_currency_name.respond_to?(:call) 293 | Money::Currency.find(field_currency_name.call(self)) 294 | elsif field_currency_name 295 | Money::Currency.find(field_currency_name) 296 | elsif self.class.respond_to?(:currency) 297 | self.class.currency 298 | else 299 | Money.default_currency 300 | end 301 | end 302 | end 303 | end 304 | end 305 | -------------------------------------------------------------------------------- /lib/money-rails/configuration.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/module/delegation' 2 | require 'active_support/core_ext/module/attribute_accessors' 3 | require 'active_support/core_ext/string/inflections' 4 | 5 | module MoneyRails 6 | 7 | # MoneyRails configuration module. 8 | # This is extended by MoneyRails to provide configuration settings. 9 | module Configuration 10 | # Start a MoneyRails configuration block in an initializer. 11 | # 12 | # example: Provide a default currency for the application 13 | # MoneyRails.configure do |config| 14 | # config.default_currency = :eur 15 | # end 16 | def configure 17 | yield self 18 | end 19 | 20 | # Configuration parameters 21 | 22 | def default_currency 23 | Money::Currency.new(Money.default_currency) 24 | end 25 | 26 | # Set default currency of money library 27 | def default_currency=(currency_name) 28 | Money.default_currency = currency_name 29 | set_currency_column_for_default_currency! 30 | end 31 | 32 | # Register a custom currency 33 | def register_currency=(currency_options) 34 | Money::Currency.register(currency_options) 35 | end 36 | 37 | def set_currency_column_for_default_currency! 38 | iso_code = default_currency.iso_code 39 | currency_column.merge! default: iso_code 40 | end 41 | 42 | def rounding_mode=(mode) 43 | valid_modes = [ 44 | BigDecimal::ROUND_UP, 45 | BigDecimal::ROUND_DOWN, 46 | BigDecimal::ROUND_HALF_UP, 47 | BigDecimal::ROUND_HALF_DOWN, 48 | BigDecimal::ROUND_HALF_EVEN, 49 | BigDecimal::ROUND_CEILING, 50 | BigDecimal::ROUND_FLOOR 51 | ] 52 | raise ArgumentError, "#{mode} is not a valid rounding mode" unless valid_modes.include?(mode) 53 | Money.rounding_mode = mode 54 | end 55 | # Set default bank object 56 | # 57 | # example (given that eu_central_bank is in Gemfile): 58 | # MoneyRails.configure do |config| 59 | # config.default_bank = EuCentralBank.new 60 | # end 61 | delegate :default_bank=, :default_bank, :locale_backend, :locale_backend=, to: :Money 62 | 63 | # Provide exchange rates 64 | delegate :add_rate, to: :Money 65 | 66 | # Use (by default) validation of numericality for each monetized field. 67 | mattr_accessor :include_validations 68 | @@include_validations = true 69 | 70 | # Default ActiveRecord migration configuration values for columns 71 | mattr_accessor :amount_column 72 | @@amount_column = { postfix: '_cents', type: :integer, null: false, default: 0, present: true } 73 | 74 | mattr_accessor :currency_column 75 | @@currency_column = { postfix: '_currency', type: :string, null: false, default: 'USD', present: true } 76 | 77 | # Use nil values to ignore defaults 78 | mattr_accessor :no_cents_if_whole 79 | @@no_cents_if_whole = nil 80 | 81 | mattr_accessor :symbol 82 | @@symbol = nil 83 | 84 | mattr_accessor :sign_before_symbol 85 | @@sign_before_symbol = nil 86 | 87 | mattr_accessor :default_format 88 | @@default_format = nil 89 | 90 | # Configure whether to raise exception when an improper value 91 | # is going to be converted to a Money object. 92 | mattr_accessor :raise_error_on_money_parsing 93 | @@raise_error_on_money_parsing = false 94 | 95 | #Configure whether to maintain invalid user input after validations 96 | mattr_accessor :preserve_user_input 97 | @@preserve_user_input = false 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/money-rails/engine.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | class Engine < ::Rails::Engine 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/money-rails/errors.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | class Error < StandardError; end 3 | end 4 | -------------------------------------------------------------------------------- /lib/money-rails/helpers/action_view_extension.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | module ActionViewExtension 3 | def currency_symbol(currency = Money.default_currency) 4 | content_tag(:span, Money::Currency.find(currency).symbol, class: "currency_symbol") 5 | end 6 | 7 | def humanized_money(value, options={}) 8 | if !options || !options.is_a?(Hash) 9 | warn "humanized_money now takes a hash of formatting options, please specify { symbol: true }" 10 | options = { symbol: options } 11 | end 12 | 13 | options = { 14 | no_cents_if_whole: MoneyRails::Configuration.no_cents_if_whole.nil? ? true : MoneyRails::Configuration.no_cents_if_whole, 15 | symbol: false 16 | }.merge(options) 17 | options.delete(:symbol) if options[:disambiguate] 18 | 19 | if value.is_a?(Money) 20 | value.format(options) 21 | elsif value.respond_to?(:to_money) 22 | value.to_money.format(options) 23 | else 24 | "" 25 | end 26 | end 27 | 28 | def humanized_money_with_symbol(value, options={}) 29 | humanized_money(value, options.merge(symbol: true)) 30 | end 31 | 32 | def money_without_cents(value, options={}) 33 | if !options || !options.is_a?(Hash) 34 | warn "money_without_cents now takes a hash of formatting options, please specify { symbol: true }" 35 | options = { symbol: options } 36 | end 37 | 38 | options = { 39 | no_cents: true, 40 | no_cents_if_whole: false, 41 | symbol: false 42 | }.merge(options) 43 | 44 | humanized_money(value, options) 45 | end 46 | 47 | def money_without_cents_and_with_symbol(value) 48 | money_without_cents(value, symbol: true) 49 | end 50 | 51 | def money_only_cents(value) 52 | return '00' unless value.respond_to?(:to_money) 53 | 54 | value = value.to_money 55 | 56 | format "%0#{value.currency.exponent}d", (value.abs % value.currency.subunit_to_unit).cents 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/money-rails/hooks.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | class Hooks 3 | PG_ADAPTERS = %w(activerecord-jdbcpostgresql-adapter postgresql postgis) 4 | 5 | def self.init 6 | # For Active Record 7 | ActiveSupport.on_load(:active_record) do 8 | require 'money-rails/active_model/validator' 9 | require 'money-rails/active_record/monetizable' 10 | ::ActiveRecord::Base.send :include, MoneyRails::ActiveRecord::Monetizable 11 | if defined?(::ActiveRecord) && defined?(::ActiveRecord::VERSION) 12 | if ::ActiveRecord::VERSION::MAJOR >= 4 13 | rails42 = case 14 | when ::ActiveRecord::VERSION::MAJOR < 5 && ::ActiveRecord::VERSION::MINOR >= 2 15 | true 16 | when ::ActiveRecord::VERSION::MAJOR >= 5 17 | true 18 | else 19 | false 20 | end 21 | 22 | current_adapter = if ::ActiveRecord::Base.respond_to?(:connection_db_config) 23 | ::ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] 24 | else 25 | ::ActiveRecord::Base.connection_config[:adapter] 26 | end 27 | 28 | postgresql_with_money = rails42 && PG_ADAPTERS.include?(current_adapter) 29 | end 30 | end 31 | 32 | require "money-rails/active_record/migration_extensions/options_extractor" 33 | %w{schema_statements table}.each do |file| 34 | require "money-rails/active_record/migration_extensions/#{file}_pg_rails4" 35 | if !postgresql_with_money 36 | require "money-rails/active_record/migration_extensions/#{file}" 37 | end 38 | end 39 | ::ActiveRecord::Migration.send :include, MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements 40 | ::ActiveRecord::ConnectionAdapters::TableDefinition.send :include, MoneyRails::ActiveRecord::MigrationExtensions::Table 41 | ::ActiveRecord::ConnectionAdapters::Table.send :include, MoneyRails::ActiveRecord::MigrationExtensions::Table 42 | end 43 | 44 | # For Mongoid 45 | begin; require 'mongoid'; require 'mongoid/version'; rescue LoadError; end 46 | if defined? ::Mongoid 47 | if ::Mongoid::VERSION =~ /^2(.*)/ 48 | require 'money-rails/mongoid/two' # Loading the file is enough 49 | else 50 | require 'money-rails/mongoid/money' 51 | end 52 | end 53 | 54 | # For ActionView 55 | ActiveSupport.on_load(:action_view) do 56 | require 'money-rails/helpers/action_view_extension' 57 | ::ActionView::Base.send :include, MoneyRails::ActionViewExtension 58 | end 59 | 60 | # For ActiveSupport 61 | ActiveSupport.on_load(:active_job) do |v| 62 | if defined?(::ActiveJob::Serializers) 63 | require 'money-rails/active_job/money_serializer' 64 | Rails.application.config.active_job.tap do |config| 65 | config.custom_serializers ||= [] 66 | config.custom_serializers << MoneyRails::ActiveJob::MoneySerializer 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/money-rails/money.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/module/aliasing.rb" 2 | require "active_support/core_ext/hash/reverse_merge.rb" 3 | 4 | class Money 5 | class << self 6 | alias_method :orig_default_formatting_rules, :default_formatting_rules 7 | 8 | def default_formatting_rules 9 | rules = orig_default_formatting_rules || {} 10 | defaults = { 11 | no_cents_if_whole: MoneyRails::Configuration.no_cents_if_whole, 12 | symbol: MoneyRails::Configuration.symbol, 13 | sign_before_symbol: MoneyRails::Configuration.sign_before_symbol 14 | }.reject { |_k, v| v.nil? } 15 | 16 | rules.reverse_merge!(defaults) 17 | 18 | unless MoneyRails::Configuration.default_format.nil? 19 | rules.reverse_merge!(MoneyRails::Configuration.default_format) 20 | end 21 | rules 22 | end 23 | end 24 | 25 | # This is expected to be called by ActiveSupport when calling as_json an Money object 26 | def to_hash 27 | { cents: cents, currency_iso: currency.iso_code.to_s } 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/money-rails/mongoid/money.rb: -------------------------------------------------------------------------------- 1 | class Money 2 | 3 | # Converts an object of this instance into a database friendly value. 4 | def mongoize 5 | { 6 | cents: cents.mongoize.to_f, 7 | currency_iso: currency.iso_code.mongoize 8 | } 9 | end 10 | 11 | class << self 12 | 13 | # Get the object as it was stored in the database, and instantiate 14 | # this custom class from it. 15 | def demongoize(object) 16 | if object.is_a?(Hash) 17 | if object.respond_to?(:deep_symbolize_keys) 18 | object = object.deep_symbolize_keys 19 | else 20 | object = object.symbolize_keys 21 | end 22 | object.has_key?(:cents) ? ::Money.new(object[:cents], object[:currency_iso]) : nil 23 | else 24 | nil 25 | end 26 | end 27 | 28 | # Takes any possible object and converts it to how it would be 29 | # stored in the database. 30 | def mongoize(object) 31 | return object.mongoize if object.is_a?(Money) 32 | return mongoize_hash(object) if object.is_a?(Hash) 33 | return nil if object.nil? 34 | return mongoize_castable(object) if object.respond_to?(:to_money) 35 | 36 | object 37 | end 38 | 39 | # Converts the object that was supplied to a criteria and converts it 40 | # into a database friendly form. 41 | def evolve(object) 42 | case object 43 | when Money then object.mongoize 44 | else object 45 | end 46 | end 47 | 48 | private 49 | 50 | def mongoize_hash(hash) 51 | if hash.respond_to?(:deep_symbolize_keys!) 52 | hash.deep_symbolize_keys! 53 | elsif hash.respond_to?(:symbolize_keys!) 54 | hash.symbolize_keys! 55 | end 56 | 57 | # Guard for a blank form 58 | return nil if hash[:cents] == '' && hash[:currency_iso] == '' 59 | 60 | ::Money.new(hash[:cents], hash[:currency_iso]).mongoize 61 | end 62 | 63 | def mongoize_castable(object) 64 | object.to_money.mongoize 65 | rescue Money::Currency::UnknownCurrency, Monetize::ParseError => e 66 | return nil unless MoneyRails.raise_error_on_money_parsing 67 | raise MoneyRails::Error, e.message 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/money-rails/mongoid/two.rb: -------------------------------------------------------------------------------- 1 | # Class name does not really matches the folder hierarchy, because 2 | # in order for (de)serialization to work, the class must be re-opened. 3 | # But this file brings mongoid 2.X compat., so... 4 | 5 | class Money 6 | include ::Mongoid::Fields::Serializable 7 | 8 | # Mongo friendly -> Money 9 | def deserialize(object) 10 | return nil if object.nil? 11 | 12 | object = object.with_indifferent_access 13 | ::Money.new object[:cents], object[:currency_iso] 14 | end 15 | 16 | # Money -> Mongo friendly 17 | def serialize(object) 18 | case 19 | when object.is_a?(Money) 20 | { 21 | cents: object.cents.is_a?(BigDecimal) ? object.cents.to_s : object.cents, 22 | currency_iso: object.currency.iso_code 23 | } 24 | when object.nil? then nil 25 | when object.respond_to?(:to_money) 26 | begin 27 | serialize(object.to_money) 28 | rescue Monetize::ParseError => e 29 | raise MoneyRails::Error, e.message if MoneyRails.raise_error_on_money_parsing 30 | nil 31 | end 32 | else nil 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/money-rails/rails_admin.rb: -------------------------------------------------------------------------------- 1 | require 'rails_admin/config/fields' 2 | require 'rails_admin/config/fields/types/integer' 3 | require 'money-rails/helpers/action_view_extension' 4 | 5 | include MoneyRails::ActionViewExtension 6 | 7 | module RailsAdmin 8 | module Config 9 | module Fields 10 | module Types 11 | class Money < RailsAdmin::Config::Fields::Types::Decimal 12 | RailsAdmin::Config::Fields::Types::register(self) 13 | 14 | register_instance_option :pretty_value do 15 | humanized_money_with_symbol(value) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/money-rails/railtie.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | class Railtie < ::Rails::Railtie 3 | initializer 'moneyrails.initialize', after: 'active_record.initialize_database' do 4 | MoneyRails::Hooks.init 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/money-rails/test_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/expectations' 2 | 3 | module MoneyRails 4 | module TestHelpers 5 | def monetize(attribute) 6 | MonetizeMatcher.new(attribute) 7 | end 8 | 9 | class MonetizeMatcher 10 | def initialize(attribute) 11 | @attribute = attribute 12 | end 13 | 14 | def with_currency(currency) 15 | @currency_iso = currency 16 | self 17 | end 18 | 19 | def with_model_currency(attribute) 20 | @currency_attribute = attribute 21 | self 22 | end 23 | 24 | def as(virt_attr) 25 | @as = virt_attr 26 | self 27 | end 28 | 29 | def allow_nil 30 | @allow_nil = true 31 | self 32 | end 33 | 34 | def matches?(actual) 35 | if actual.is_a?(Class) 36 | @actual = actual.new 37 | else 38 | @actual = actual.class.new 39 | end 40 | 41 | @money_attribute = @as.presence || @attribute.to_s.sub(/_cents$/, "") 42 | @money_attribute_setter = "#{@money_attribute}=" 43 | 44 | object_responds_to_attributes? && 45 | test_allow_nil && 46 | is_monetized? && 47 | test_currency_iso && 48 | test_currency_attribute 49 | end 50 | 51 | 52 | def description 53 | desc = "monetize #{@attribute}" 54 | desc << " as #{@as}" if @as 55 | desc << " with currency #{@currency_iso}" if @currency_iso 56 | desc 57 | end 58 | 59 | def failure_message # RSpec 3.x 60 | msg = "expected that #{@attribute} of #{@actual} would be monetized" 61 | msg << " as #{@as}" if @as 62 | msg << " with currency #{@currency_iso}" if @currency_iso 63 | msg 64 | end 65 | alias_method :failure_message_for_should, :failure_message # RSpec 1.2, 2.x, and minitest-matchers 66 | 67 | def failure_message_when_negated # RSpec 3.x 68 | msg = "expected that #{@attribute} of #{@actual} would not be monetized" 69 | msg << " as #{@as}" if @as 70 | msg << " with currency #{@currency_iso}" if @currency_iso 71 | msg 72 | end 73 | alias_method :failure_message_for_should_not, :failure_message_when_negated # RSpec 1.2, 2.x, and minitest-matchers 74 | alias_method :negative_failure_message, :failure_message_when_negated # RSpec 1.1 75 | 76 | private 77 | 78 | def object_responds_to_attributes? 79 | @actual.respond_to?(@attribute) && @actual.respond_to?(@money_attribute) 80 | end 81 | 82 | def test_allow_nil 83 | if @allow_nil 84 | @actual.public_send(@money_attribute_setter, "") 85 | @actual.public_send(@money_attribute).nil? 86 | else 87 | true 88 | end 89 | end 90 | 91 | def is_monetized? 92 | @actual.public_send(@money_attribute_setter, 1) 93 | @actual.public_send(@money_attribute).instance_of?(Money) 94 | end 95 | 96 | def test_currency_iso 97 | if @currency_iso 98 | @actual.public_send(@money_attribute).currency.id == @currency_iso 99 | else 100 | true 101 | end 102 | end 103 | 104 | def test_currency_attribute 105 | if @currency_attribute 106 | @actual.public_send(@money_attribute).currency == @actual.public_send(@currency_attribute) 107 | else 108 | true 109 | end 110 | end 111 | 112 | end 113 | end 114 | end 115 | 116 | RSpec.configure do |config| 117 | config.include MoneyRails::TestHelpers 118 | end 119 | -------------------------------------------------------------------------------- /lib/money-rails/version.rb: -------------------------------------------------------------------------------- 1 | module MoneyRails 2 | VERSION = '1.15.0' 3 | end 4 | -------------------------------------------------------------------------------- /money-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/money-rails/version', __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "money-rails" 6 | s.version = MoneyRails::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.license = "MIT" 9 | s.authors = ["Andreas Loupasakis", "Shane Emmons", "Simone Carletti"] 10 | s.email = ["alup.rubymoney@gmail.com"] 11 | s.description = "This library provides integration of RubyMoney - Money gem with Rails" 12 | s.summary = "Money gem integration with Rails" 13 | s.homepage = "https://github.com/RubyMoney/money-rails" 14 | 15 | s.files = Dir.glob("{lib,spec,config}/**/*") 16 | s.files += %w(CHANGELOG.md LICENSE README.md) 17 | s.files += %w(Rakefile money-rails.gemspec) 18 | 19 | s.files.delete("spec/dummy/log") 20 | s.files.delete("spec/dummy/log/development.log") 21 | s.files.delete("spec/dummy/log/test.log") 22 | s.files.delete("spec/dummy/db/development.sqlite3") 23 | s.files.delete("spec/dummy/db/test.sqlite3") 24 | 25 | s.test_files = s.files.grep(/^spec\//) 26 | 27 | s.require_path = "lib" 28 | 29 | s.add_dependency "money", "~> 6.16" 30 | s.add_dependency "monetize", "~> 1.9" 31 | s.add_dependency "activesupport", ">= 3.0" 32 | s.add_dependency "railties", ">= 3.0" 33 | s.add_dependency "mime-types", "< 3" if RUBY_VERSION < '2.0' # mime-types > 3 depends on mime-types-data, which doesn't support ruby 1.9 34 | 35 | s.add_development_dependency "rails", ">= 3.0", "< 7.0" 36 | s.add_development_dependency "rspec-rails", "~> 3.0" 37 | s.add_development_dependency 'database_cleaner', '~> 1.6.1' 38 | s.add_development_dependency 'test-unit', '~> 3.0' if RUBY_VERSION >= '2.2' 39 | s.add_development_dependency 'bundler' 40 | 41 | if s.respond_to?(:metadata) 42 | s.metadata['changelog_uri'] = 'https://github.com/RubyMoney/money-rails/blob/master/CHANGELOG.md' 43 | s.metadata['source_code_uri'] = 'https://github.com/RubyMoney/money-rails/' 44 | s.metadata['bug_tracker_uri'] = 'https://github.com/RubyMoney/money-rails/issues' 45 | s.metadata['rubygems_mfa_required'] = 'true' 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/active_job/money_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | if defined?(::ActiveJob::Serializers) 4 | describe MoneyRails::ActiveJob::MoneySerializer do 5 | let(:money) { Money.new(1_00, "EUR") } 6 | let(:serialized_money) do 7 | { 8 | "_aj_serialized" => "MoneyRails::ActiveJob::MoneySerializer", 9 | "cents" => 1_00, 10 | "currency" => "EUR", 11 | } 12 | end 13 | 14 | describe "#serialize?" do 15 | it { expect(described_class.serialize?(money)).to be_truthy } 16 | it { expect(described_class.serialize?(1_00)).not_to be_truthy } 17 | end 18 | 19 | describe "#serialize" do 20 | it { expect(described_class.serialize(money)).to eq(serialized_money) } 21 | end 22 | 23 | describe "#deserialize" do 24 | it { expect(described_class.deserialize(serialized_money)).to eq(money) } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/active_record/migration_extensions/schema_statements_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class Item < ActiveRecord::Base; end 4 | 5 | if defined? ActiveRecord 6 | describe MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements do 7 | before :all do 8 | @connection = ActiveRecord::Base.connection 9 | @connection.drop_table :items if @connection.table_exists? :items 10 | @connection.create_table :items 11 | @connection.send :extend, MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements 12 | end 13 | 14 | describe 'add_money' do 15 | before do 16 | @connection.add_money :items, :price 17 | @connection.add_money :items, :price_without_currency, currency: { present: false } 18 | @connection.add_money :items, :price_with_full_options, amount: { 19 | prefix: :prefix_, 20 | postfix: :_postfix, 21 | type: :decimal, 22 | precision: 4, 23 | scale: 2, 24 | default: 1, 25 | null: true 26 | }, currency: { 27 | prefix: :currency_prefix, 28 | postfix: :currency_postfix, 29 | column_name: :currency 30 | } 31 | 32 | Item.reset_column_information 33 | end 34 | 35 | context 'default options' do 36 | describe 'amount' do 37 | subject { Item.columns_hash['price_cents'] } 38 | 39 | it { expect(subject.default.to_i).to eq(0) } 40 | it { expect(Item.new.public_send(subject.name)).to eq(0) } 41 | it { expect(subject.null).to be(false) } 42 | it { expect(subject.type).to eq(:integer) } 43 | end 44 | 45 | describe 'currency' do 46 | subject { Item.columns_hash['price_currency'] } 47 | 48 | # set in spec/dummy/config/initializers/money.rb 49 | it { expect(subject.default).to eq('EUR') } 50 | 51 | it { expect(subject.null).to be(false) } 52 | it { expect(subject.type).to eq(:string) } 53 | end 54 | end 55 | 56 | context 'without currency column' do 57 | it { expect(Item.columns_hash['price_without_currency_cents']).not_to be nil } 58 | it { expect(Item.columns_hash['price_without_currency_currency']).to be nil } 59 | end 60 | 61 | context 'full options' do 62 | describe 'amount' do 63 | subject { Item.columns_hash['prefix_price_with_full_options_postfix'] } 64 | 65 | it { expect(subject.default.to_i).to eq(1) } 66 | it { expect(Item.new.public_send(subject.name)).to eq(1) } 67 | it { expect(subject.null).to be(true) } 68 | it { expect(subject.type).to eq(:decimal) } 69 | it { expect(subject.precision).to eq(4) } 70 | it { expect(subject.scale).to eq(2) } 71 | end 72 | 73 | describe 'currency' do 74 | it { expect(Item.columns_hash['currency']).not_to be nil } 75 | end 76 | end 77 | end 78 | 79 | describe 'remove_money' do 80 | before do 81 | @connection.add_money :items, :price 82 | @connection.add_money :items, :price_without_currency, currency: { present: false } 83 | @connection.add_money :items, :price_with_full_options, amount: { prefix: :prefix_, postfix: :_postfix }, currency: { column_name: :currency } 84 | 85 | @connection.remove_money :items, :price 86 | @connection.remove_money :items, :price_without_currency, currency: { present: false } 87 | @connection.remove_money :items, :price_with_full_options, amount: { prefix: :prefix_, postfix: :_postfix }, currency: { column_name: :currency } 88 | 89 | Item.reset_column_information 90 | end 91 | 92 | it { expect(Item.columns_hash['price_cents']).to be nil } 93 | it { expect(Item.columns_hash['price_currency']).to be nil } 94 | 95 | it { expect(Item.columns_hash['price_without_currency_cents']).to be nil } 96 | 97 | it { expect(Item.columns_hash['prefix_price_with_full_options_postfix']).to be nil } 98 | it { expect(Item.columns_hash['currency']).to be nil } 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /spec/active_record/migration_extensions/table_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class Item < ActiveRecord::Base; end 4 | 5 | if defined? ActiveRecord 6 | describe MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements do 7 | before :all do 8 | @connection = ActiveRecord::Base.connection 9 | @connection.send :extend, MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements 10 | end 11 | 12 | describe 'money' do 13 | before do 14 | @connection.drop_table :items if @connection.table_exists? :items 15 | @connection.create_table :items do |t| 16 | t.money :price 17 | t.money :price_without_currency, currency: { present: false } 18 | t.money :price_with_full_options, amount: { 19 | prefix: :prefix_, 20 | postfix: :_postfix, 21 | type: :decimal, 22 | precision: 4, 23 | scale: 2, 24 | default: 1, 25 | null: true 26 | }, currency: { 27 | prefix: :currency_prefix, 28 | postfix: :currency_postfix, 29 | column_name: :currency 30 | } 31 | end 32 | 33 | Item.reset_column_information 34 | end 35 | 36 | context 'default options' do 37 | describe 'amount' do 38 | subject { Item.columns_hash['price_cents'] } 39 | 40 | it { expect(subject.default.to_i).to eq(0) } 41 | it { expect(Item.new.public_send(subject.name)).to eq(0) } 42 | it { expect(subject.null).to be(false) } 43 | it { expect(subject.type).to eq(:integer) } 44 | end 45 | 46 | describe 'currency' do 47 | subject { Item.columns_hash['price_currency'] } 48 | 49 | # set in spec/dummy/config/initializers/money.rb 50 | it { expect(subject.default).to eq('EUR') } 51 | 52 | it { expect(subject.null).to be(false) } 53 | it { expect(subject.type).to eq(:string) } 54 | end 55 | end 56 | 57 | context 'without currency column' do 58 | it { expect(Item.columns_hash['price_without_currency_cents']).not_to be nil } 59 | it { expect(Item.columns_hash['price_without_currency_currency']).to be nil } 60 | end 61 | 62 | context 'full options' do 63 | describe 'amount' do 64 | subject { Item.columns_hash['prefix_price_with_full_options_postfix'] } 65 | 66 | it { expect(subject.default.to_i).to eq(1) } 67 | it { expect(Item.new.public_send(subject.name)).to eq(1) } 68 | it { expect(subject.null).to be(true) } 69 | it { expect(subject.type).to eq(:decimal) } 70 | it { expect(subject.precision).to eq(4) } 71 | it { expect(subject.scale).to eq(2) } 72 | end 73 | 74 | describe 'currency' do 75 | it { expect(Item.columns_hash['currency']).not_to be nil } 76 | end 77 | end 78 | end 79 | 80 | describe 'remove_money' do 81 | before do 82 | @connection.change_table :items do |t| 83 | t.money :price 84 | t.money :price_without_currency, currency: { present: false } 85 | t.money :price_with_full_options, amount: { prefix: :prefix_, postfix: :_postfix }, currency: { column_name: :currency } 86 | 87 | t.remove_money :price 88 | t.remove_money :price_without_currency, currency: { present: false } 89 | t.remove_money :price_with_full_options, amount: { prefix: :prefix_, postfix: :_postfix }, currency: { column_name: :currency } 90 | end 91 | 92 | Item.reset_column_information 93 | end 94 | 95 | it { expect(Item.columns_hash['price_cents']).to be nil } 96 | it { expect(Item.columns_hash['price_currency']).to be nil } 97 | 98 | it { expect(Item.columns_hash['price_without_currency_cents']).to be nil } 99 | 100 | it { expect(Item.columns_hash['prefix_price_with_full_options_postfix']).to be nil } 101 | it { expect(Item.columns_hash['currency']).to be nil } 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /spec/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "configuration" do 4 | 5 | describe "initializer" do 6 | 7 | it "sets default currency" do 8 | expect(Money.default_currency).to eq(Money::Currency.new(:eur)) 9 | end 10 | 11 | it "registers a custom currency" do 12 | expect(Money::Currency.table).to include(:eu4) 13 | end 14 | 15 | it "adds exchange rates given in config initializer" do 16 | expect(Money.us_dollar(100).bank.get_rate('USD', 'CAD')).to eq(1.24515) 17 | expect(Money.ca_dollar(100).bank.get_rate('CAD', 'USD')).to eq(0.803115) 18 | end 19 | 20 | it "sets no_cents_if_whole value for formatted output globally" do 21 | # Enable formatting to depend only on currency (to avoid default symbols for :en) 22 | Money.locale_backend = :currency 23 | 24 | value = Money.new(12345600, "EUR") 25 | mark = Money::Currency.find(:eur).decimal_mark 26 | expect(value.format).to match(/#{mark}/) 27 | 28 | MoneyRails.no_cents_if_whole = true 29 | expect(value.format).not_to match(/#{mark}/) 30 | expect(value.format(no_cents_if_whole: false)).to match(/#{mark}/) 31 | 32 | MoneyRails.no_cents_if_whole = false 33 | expect(value.format).to match(/#{mark}/) 34 | expect(value.format(no_cents_if_whole: true)).not_to match(/#{mark}/) 35 | 36 | # Reset global settings 37 | MoneyRails.no_cents_if_whole = nil 38 | Money.locale_backend = :i18n 39 | end 40 | 41 | it "sets symbol for formatted output globally" do 42 | value = Money.new(12345600, "EUR") 43 | symbol = Money::Currency.find(:eur).symbol 44 | expect(value.format).to match(/#{symbol}/) 45 | 46 | MoneyRails.symbol = false 47 | expect(value.format).not_to match(/#{symbol}/) 48 | expect(value.format(symbol: true)).to match(/#{symbol}/) 49 | 50 | MoneyRails.symbol = true 51 | expect(value.format).to match(/#{symbol}/) 52 | expect(value.format(symbol: false)).not_to match(/#{symbol}/) 53 | 54 | # Reset global setting 55 | MoneyRails.symbol = nil 56 | end 57 | 58 | it "sets the location of the negative sign for formatted output globally" do 59 | value = Money.new(-12345600, "EUR") 60 | symbol = Money::Currency.find(:eur).symbol 61 | expect(value.format).to match(/#{symbol}-/) 62 | 63 | MoneyRails.sign_before_symbol = false 64 | expect(value.format).to match(/#{symbol}-/) 65 | expect(value.format(sign_before_symbol: false)).to match(/#{symbol}-/) 66 | 67 | MoneyRails.sign_before_symbol = true 68 | expect(value.format).to match(/-#{symbol}/) 69 | expect(value.format(sign_before_symbol: true)).to match(/-#{symbol}/) 70 | 71 | # Reset global setting 72 | MoneyRails.sign_before_symbol = nil 73 | end 74 | 75 | it "passes through arbitrary format options" do 76 | value = Money.new(-12345600, "EUR") 77 | symbol = Money::Currency.find(:eur).symbol 78 | 79 | MoneyRails.default_format = { format: '%n%u' } 80 | expect(value.format).to match(/#{symbol}\z/) 81 | 82 | # Override with "classic" format options for backward compatibility 83 | MoneyRails.default_format = {sign_before_symbol: :false} 84 | MoneyRails.sign_before_symbol = true 85 | expect(value.format).to match(/-#{symbol}/) 86 | 87 | # Reset global settings 88 | MoneyRails.sign_before_symbol = nil 89 | MoneyRails.default_format = nil 90 | end 91 | 92 | it "changes the amount and currency column settings based on the default currency" do 93 | old_currency = MoneyRails.default_currency 94 | MoneyRails.default_currency = :inr 95 | 96 | expect(MoneyRails.default_currency.subunit).to eq 'Paisa' 97 | expect(MoneyRails.amount_column[:postfix]).to eq("_cents") # not localized 98 | 99 | expect(MoneyRails.currency_column[:default]).to eq(MoneyRails.default_currency.iso_code) 100 | 101 | # Reset global setting 102 | MoneyRails.default_currency = old_currency 103 | end 104 | 105 | it "accepts default currency which doesn't have minor unit" do 106 | old_currency = MoneyRails.default_currency 107 | 108 | expect { 109 | MoneyRails.default_currency = :jpy 110 | }.to_not raise_error 111 | 112 | expect(MoneyRails.amount_column[:postfix]).to eq("_cents") 113 | 114 | # Reset global setting 115 | MoneyRails.default_currency = old_currency 116 | end 117 | 118 | it "assigns a default bank" do 119 | old_bank = MoneyRails.default_bank 120 | 121 | bank = Money::Bank::VariableExchange.new 122 | MoneyRails.default_bank = bank 123 | expect(Money.default_bank).to eq(bank) 124 | 125 | MoneyRails.default_bank = old_bank 126 | end 127 | 128 | describe "rounding mode" do 129 | [BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN, BigDecimal::ROUND_HALF_UP, BigDecimal::ROUND_HALF_DOWN, 130 | BigDecimal::ROUND_HALF_EVEN, BigDecimal::ROUND_CEILING, BigDecimal::ROUND_FLOOR].each do |mode| 131 | context "when set to #{mode}" do 132 | it "sets Money.rounding mode to #{mode}" do 133 | MoneyRails.rounding_mode = mode 134 | expect(Money.rounding_mode).to eq(mode) 135 | end 136 | end 137 | end 138 | 139 | context "when passed an invalid value" do 140 | it "should raise an ArgumentError" do 141 | expect(lambda{MoneyRails.rounding_mode = "booyakasha"}).to raise_error(ArgumentError, 'booyakasha is not a valid rounding mode') 142 | end 143 | end 144 | end 145 | 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /spec/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" 7 | templates that are primarily responsible for inserting pre-built data in between 8 | HTML tags. The model contains the "smart" domain objects (such as Account, 9 | Product, Person, Post) that holds all the business logic and knows how to 10 | persist themselves to a database. The controller handles the incoming requests 11 | (such as Save New Account, Update Product, Show Post) by manipulating the model 12 | and directing data to the view. 13 | 14 | In Rails, the model is handled by what's called an object-relational mapping 15 | layer entitled Active Record. This layer allows you to present the data from 16 | database rows as objects and embellish these data objects with business logic 17 | methods. You can read more about Active Record in 18 | link:files/vendor/rails/activerecord/README.html. 19 | 20 | The controller and view are handled by the Action Pack, which handles both 21 | layers by its two parts: Action View and Action Controller. These two layers 22 | are bundled in a single package due to their heavy interdependence. This is 23 | unlike the relationship between the Active Record and Action Pack that is much 24 | more separate. Each of these packages can be used independently outside of 25 | Rails. You can read more about Action Pack in 26 | link:files/vendor/rails/actionpack/README.html. 27 | 28 | 29 | == Getting Started 30 | 31 | 1. At the command prompt, create a new Rails application: 32 | rails new myapp (where myapp is the application name) 33 | 34 | 2. Change directory to myapp and start the web server: 35 | cd myapp; rails server (run with --help for options) 36 | 37 | 3. Go to http://localhost:3000/ and you'll see: 38 | "Welcome aboard: You're riding Ruby on Rails!" 39 | 40 | 4. Follow the guidelines to start developing your application. You can find 41 | the following resources handy: 42 | 43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html 44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ 45 | 46 | 47 | == Debugging Rails 48 | 49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 50 | will help you debug it and get it back on the rails. 51 | 52 | First area to check is the application log files. Have "tail -f" commands 53 | running on the server.log and development.log. Rails will automatically display 54 | debugging and runtime information to these files. Debugging info will also be 55 | shown in the browser on requests from 127.0.0.1. 56 | 57 | You can also log your own messages directly into the log file from your code 58 | using the Ruby logger class from inside your controllers. Example: 59 | 60 | class WeblogController < ActionController::Base 61 | def destroy 62 | @weblog = Weblog.find(params[:id]) 63 | @weblog.destroy 64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 65 | end 66 | end 67 | 68 | The result will be a message in your log file along the lines of: 69 | 70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! 71 | 72 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 73 | 74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are 75 | several books available online as well: 76 | 77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) 78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 79 | 80 | These two books will bring you up to speed on the Ruby language and also on 81 | programming in general. 82 | 83 | 84 | == Debugger 85 | 86 | Debugger support is available through the debugger command when you start your 87 | Mongrel or WEBrick server with --debugger. This means that you can break out of 88 | execution at any point in the code, investigate and change the model, and then, 89 | resume execution! You need to install ruby-debug to run the server in debugging 90 | mode. With gems, use sudo gem install ruby-debug. Example: 91 | 92 | class WeblogController < ActionController::Base 93 | def index 94 | @posts = Post.all 95 | debugger 96 | end 97 | end 98 | 99 | So the controller will accept the action, run the first line, then present you 100 | with a IRB prompt in the server window. Here you can do things like: 101 | 102 | >> @posts.inspect 103 | => "[#nil, "body"=>nil, "id"=>"1"}>, 105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" 107 | >> @posts.first.title = "hello from a debugger" 108 | => "hello from a debugger" 109 | 110 | ...and even better, you can examine how your runtime objects actually work: 111 | 112 | >> f = @posts.first 113 | => #nil, "body"=>nil, "id"=>"1"}> 114 | >> f. 115 | Display all 152 possibilities? (y or n) 116 | 117 | Finally, when you're ready to resume execution, you can enter "cont". 118 | 119 | 120 | == Console 121 | 122 | The console is a Ruby shell, which allows you to interact with your 123 | application's domain model. Here you'll have all parts of the application 124 | configured, just like it is when the application is running. You can inspect 125 | domain models, change values, and save to the database. Starting the script 126 | without arguments will launch it in the development environment. 127 | 128 | To start the console, run rails console from the application 129 | directory. 130 | 131 | Options: 132 | 133 | * Passing the -s, --sandbox argument will rollback any modifications 134 | made to the database. 135 | * Passing an environment name as an argument will load the corresponding 136 | environment. Example: rails console production. 137 | 138 | To reload your controllers and models after launching the console run 139 | reload! 140 | 141 | More information about irb can be found at: 142 | link:http://www.rubycentral.org/pickaxe/irb.html 143 | 144 | 145 | == dbconsole 146 | 147 | You can go to the command line of your database directly through rails 148 | dbconsole. You would be connected to the database with the credentials 149 | defined in database.yml. Starting the script without arguments will connect you 150 | to the development database. Passing an argument will connect you to a different 151 | database, like rails dbconsole production. Currently works for MySQL, 152 | PostgreSQL and SQLite 3. 153 | 154 | == Description of Contents 155 | 156 | The default directory structure of a generated Ruby on Rails application: 157 | 158 | |-- app 159 | | |-- assets 160 | | |-- images 161 | | |-- javascripts 162 | | `-- stylesheets 163 | | |-- controllers 164 | | |-- helpers 165 | | |-- mailers 166 | | |-- models 167 | | `-- views 168 | | `-- layouts 169 | |-- config 170 | | |-- environments 171 | | |-- initializers 172 | | `-- locales 173 | |-- db 174 | |-- doc 175 | |-- lib 176 | | `-- tasks 177 | |-- log 178 | |-- public 179 | |-- script 180 | |-- test 181 | | |-- fixtures 182 | | |-- functional 183 | | |-- integration 184 | | |-- performance 185 | | `-- unit 186 | |-- tmp 187 | | |-- cache 188 | | |-- pids 189 | | |-- sessions 190 | | `-- sockets 191 | `-- vendor 192 | |-- assets 193 | `-- stylesheets 194 | `-- plugins 195 | 196 | app 197 | Holds all the code that's specific to this particular application. 198 | 199 | app/assets 200 | Contains subdirectories for images, stylesheets, and JavaScript files. 201 | 202 | app/controllers 203 | Holds controllers that should be named like weblogs_controller.rb for 204 | automated URL mapping. All controllers should descend from 205 | ApplicationController which itself descends from ActionController::Base. 206 | 207 | app/models 208 | Holds models that should be named like post.rb. Models descend from 209 | ActiveRecord::Base by default. 210 | 211 | app/views 212 | Holds the template files for the view that should be named like 213 | weblogs/index.html.erb for the WeblogsController#index action. All views use 214 | eRuby syntax by default. 215 | 216 | app/views/layouts 217 | Holds the template files for layouts to be used with views. This models the 218 | common header/footer method of wrapping views. In your views, define a layout 219 | using the layout :default and create a file named default.html.erb. 220 | Inside default.html.erb, call <% yield %> to render the view using this 221 | layout. 222 | 223 | app/helpers 224 | Holds view helpers that should be named like weblogs_helper.rb. These are 225 | generated for you automatically when using generators for controllers. 226 | Helpers can be used to wrap functionality for your views into methods. 227 | 228 | config 229 | Configuration files for the Rails environment, the routing map, the database, 230 | and other dependencies. 231 | 232 | db 233 | Contains the database schema in schema.rb. db/migrate contains all the 234 | sequence of Migrations for your schema. 235 | 236 | doc 237 | This directory is where your application documentation will be stored when 238 | generated using rake doc:app 239 | 240 | lib 241 | Application specific libraries. Basically, any kind of custom code that 242 | doesn't belong under controllers, models, or helpers. This directory is in 243 | the load path. 244 | 245 | public 246 | The directory available for the web server. Also contains the dispatchers and the 247 | default HTML files. This should be set as the DOCUMENT_ROOT of your web 248 | server. 249 | 250 | script 251 | Helper scripts for automation and generation. 252 | 253 | test 254 | Unit and functional tests along with fixtures. When using the rails generate 255 | command, template test files will be generated for you and placed in this 256 | directory. 257 | 258 | vendor 259 | External libraries that the application depends on. Also includes the plugins 260 | subdirectory. If the app has frozen rails, those gems also go here, under 261 | vendor/rails/. This directory is in the load path. 262 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Dummy::Application.load_tasks 8 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyMoney/money-rails/df280dc739b570772f5cc8e99569e85972e0158c/spec/dummy/app/assets/config/manifest.js -------------------------------------------------------------------------------- /spec/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /spec/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyMoney/money-rails/df280dc739b570772f5cc8e99569e85972e0158c/spec/dummy/app/mailers/.gitkeep -------------------------------------------------------------------------------- /spec/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyMoney/money-rails/df280dc739b570772f5cc8e99569e85972e0158c/spec/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /spec/dummy/app/models/dummy_product.rb: -------------------------------------------------------------------------------- 1 | class DummyProduct < ActiveRecord::Base 2 | # Use as model level currency 3 | register_currency :gbp 4 | 5 | # Use money-rails macros 6 | monetize :price_cents, with_model_currency: :currency 7 | end 8 | 9 | -------------------------------------------------------------------------------- /spec/dummy/app/models/priceable.rb: -------------------------------------------------------------------------------- 1 | if defined? Mongoid 2 | class Priceable 3 | include Mongoid::Document 4 | 5 | field :price, type: Money 6 | field :price_hash, type: Hash 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/dummy/app/models/product.rb: -------------------------------------------------------------------------------- 1 | class Product < ActiveRecord::Base 2 | # Use USD as model level currency 3 | register_currency :usd 4 | 5 | # Use money-rails macros 6 | monetize :price_cents 7 | 8 | # Use money-rails macro with multiple fields 9 | monetize :delivery_fee_cents, :restock_fee_cents, allow_nil: true 10 | 11 | # Use a custom name for the Money attribute 12 | monetize :discount, as: "discount_value" 13 | 14 | # Allow nil 15 | monetize :optional_price_cents, allow_nil: true 16 | 17 | # Override default currency (EUR) with a specific one (GBP) for this field only 18 | monetize :bonus_cents, with_currency: :gbp 19 | 20 | # Use currency column to determine currency for this field only 21 | monetize :sale_price_amount, as: :sale_price, 22 | with_model_currency: :sale_price_currency_code 23 | 24 | monetize :price_in_a_range_cents, allow_nil: true, 25 | subunit_numericality: { 26 | greater_than: 0, 27 | less_than_or_equal_to: 10000 28 | }, 29 | numericality: { 30 | greater_than: 0, 31 | less_than_or_equal_to: 100, 32 | message: "must be greater than zero and less than $100" 33 | } 34 | 35 | # Skip validations separately from each other 36 | monetize :skip_validation_price_cents, subunit_numericality: false, numericality: false, allow_nil: true 37 | 38 | # Override default currency (EUR) with a specific one (CAD) for this field only, from a lambda 39 | monetize :lambda_price_cents, with_currency: ->(_product) { Rails.configuration.lambda_test }, allow_nil: true 40 | 41 | attr_accessor :accessor_price_cents 42 | monetize :accessor_price_cents, disable_validation: true 43 | 44 | monetize :validates_method_amount_cents, allow_nil: true 45 | 46 | validates :validates_method_amount, money: { 47 | greater_than: 0, 48 | less_than_or_equal_to: ->(product) { product.optional_price.to_f }, 49 | message: 'must be greater than zero and less than $100', 50 | }, 51 | allow_nil: true 52 | 53 | alias_attribute :renamed_cents, :aliased_cents 54 | 55 | monetize :renamed_cents, allow_nil: true 56 | 57 | # Using postfix to determine currency column (reduced_price_currency) 58 | monetize :reduced_price_cents, allow_nil: true 59 | end 60 | -------------------------------------------------------------------------------- /spec/dummy/app/models/service.rb: -------------------------------------------------------------------------------- 1 | class Service < ActiveRecord::Base 2 | monetize :charge_cents, with_currency: :usd 3 | 4 | monetize :discount_cents 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/app/models/transaction.rb: -------------------------------------------------------------------------------- 1 | class Transaction < ActiveRecord::Base 2 | monetize :amount_cents, with_model_currency: :currency 3 | 4 | monetize :tax_cents, with_model_currency: :currency 5 | 6 | monetize :total_cents, with_model_currency: :currency 7 | 8 | monetize :optional_amount_cents, with_model_currency: :currency, allow_nil: true 9 | 10 | def total_cents(foo = 0, bar: 0) 11 | amount_cents + tax_cents + foo + bar 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= stylesheet_link_tag "application", media: "all" %> 6 | <%= javascript_include_tag "application" %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Dummy::Application 5 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_mailer/railtie" 7 | unless Rails::VERSION::MAJOR >= 4 8 | require "active_resource/railtie" 9 | end 10 | require "sprockets/railtie" 11 | 12 | Bundler.require 13 | require "money-rails" 14 | 15 | module Dummy 16 | class Application < Rails::Application 17 | 18 | if I18n.respond_to?(:enforce_available_locales) 19 | I18n.enforce_available_locales = false # removes deprecation warning 20 | end 21 | 22 | # Settings in config/environments/* take precedence over those specified here. 23 | # Application configuration should go into files in config/initializers 24 | # -- all .rb files in that directory are automatically loaded. 25 | 26 | # Custom directories with classes and modules you want to be autoloadable. 27 | # config.autoload_paths += %W(#{config.root}/extras) 28 | 29 | # Only load the plugins named here, in the order given (default is alphabetical). 30 | # :all can be used as a placeholder for all plugins not explicitly named. 31 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 32 | 33 | # Activate observers that should always be running. 34 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 35 | 36 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 37 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 38 | # config.time_zone = 'Central Time (US & Canada)' 39 | 40 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 41 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 42 | # config.i18n.default_locale = :de 43 | 44 | # Configure the default encoding used in templates for Ruby 1.9. 45 | config.encoding = "utf-8" 46 | 47 | config.secret_key_base = "1234567890" # removes deprecation warning 48 | 49 | # Configure sensitive parameters which will be filtered from the log file. 50 | config.filter_parameters += [:password] 51 | 52 | # Use SQL instead of Active Record's schema dumper when creating the database. 53 | # This is necessary if your schema can't be completely dumped by the schema dumper, 54 | # like if you have constraints or database-specific column types 55 | # config.active_record.schema_format = :sql 56 | 57 | # Enable the asset pipeline 58 | config.assets.enabled = true 59 | 60 | # Version of your assets, change this if you want to expire all your assets 61 | config.assets.version = '1.0' 62 | 63 | # Currentcy Lambda test 64 | config.lambda_test = 'cad' 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # The default gemfile is rails4 4 | gemfile = File.expand_path('../../../../gemfiles/rails4.gemfile', __FILE__) 5 | 6 | unless ENV['BUNDLE_GEMFILE'] 7 | puts "No Gemfile specified, booting rails env with rails4.gemfile (default)" 8 | ENV['BUNDLE_GEMFILE'] ||= gemfile 9 | end 10 | 11 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 12 | 13 | $:.unshift File.expand_path('../../../../lib', __FILE__) -------------------------------------------------------------------------------- /spec/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | # Do not compress assets 26 | config.assets.compress = false 27 | 28 | # Expands the lines which load the assets 29 | config.assets.debug = true 30 | end 31 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Disable Rails's static asset server (Apache or nginx will already do this) 18 | config.serve_static_files = false 19 | 20 | # Compress JavaScripts and CSS 21 | config.assets.compress = true 22 | 23 | # Don't fallback to assets pipeline if a precompiled asset is missed 24 | config.assets.compile = false 25 | 26 | # Generate digests for assets URLs 27 | config.assets.digest = true 28 | 29 | # Defaults to Rails.root.join("public/assets") 30 | # config.assets.manifest = YOUR_PATH 31 | 32 | # Specifies the header that your server uses for sending files 33 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 34 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 35 | 36 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 37 | # config.force_ssl = true 38 | 39 | # See everything in the log (default is :info) 40 | # config.log_level = :debug 41 | 42 | # Prepend all log lines with the following tags 43 | # config.log_tags = [ :subdomain, :uuid ] 44 | 45 | # Use a different logger for distributed setups 46 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 47 | 48 | # Use a different cache store in production 49 | # config.cache_store = :mem_cache_store 50 | 51 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 52 | # config.action_controller.asset_host = "http://assets.example.com" 53 | 54 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 55 | # config.assets.precompile += %w( search.js ) 56 | 57 | # Disable delivery errors, bad email addresses will be ignored 58 | # config.action_mailer.raise_delivery_errors = false 59 | 60 | # Enable threaded mode 61 | # config.threadsafe! 62 | 63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 64 | # the I18n.default_locale when a translation can not be found) 65 | config.i18n.fallbacks = true 66 | 67 | # Send deprecation notices to registered listeners 68 | config.active_support.deprecation = :notify 69 | end 70 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance 16 | config.serve_static_files = true 17 | config.static_cache_control = "public, max-age=3600" 18 | 19 | # Show full error reports and disable caching 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr 35 | config.active_support.deprecation = :stderr 36 | end 37 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | # 12 | # These inflection rules are supported but not enabled by default: 13 | # ActiveSupport::Inflector.inflections do |inflect| 14 | # inflect.acronym 'RESTful' 15 | # end 16 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/money.rb: -------------------------------------------------------------------------------- 1 | # encoding : utf-8 2 | 3 | MoneyRails.configure do |config| 4 | Money.locale_backend = :i18n 5 | Money.rounding_mode = BigDecimal::ROUND_HALF_UP 6 | 7 | # To set the default currency 8 | # 9 | config.default_currency = :eur 10 | 11 | # Add some rates 12 | config.add_rate "USD", "CAD", 1.24515 13 | config.add_rate "CAD", "USD", 0.803115 14 | 15 | # To handle the inclusion of validations for monetized fields 16 | # The default value is true 17 | # 18 | config.include_validations = true 19 | 20 | # Register a custom currency 21 | # 22 | config.register_currency = { 23 | priority: 1, 24 | iso_code: "EU4", 25 | name: "Euro with subunit of 4 digits", 26 | symbol: "€", 27 | symbol_first: true, 28 | subunit: "Subcent", 29 | subunit_to_unit: 10000, 30 | thousands_separator: ".", 31 | decimal_mark: "," 32 | } 33 | end 34 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Dummy::Application.config.secret_token = 'a296fb1735faa781e147a1a15f644e9a5c74f9fec357b6d0c94983a0700db240c8c1e020f5ed6413f2025f0092cf8a8db9990ab554ee59964c6ef40fe4ba884a' 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Dummy::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/en-GB.yml: -------------------------------------------------------------------------------- 1 | en-GB: 2 | number: 3 | currency: 4 | format: 5 | delimiter: ! ',' 6 | format: ! '%u%n' 7 | precision: 2 8 | separator: . 9 | significant: false 10 | strip_insignificant_zeros: false 11 | unit: £ -------------------------------------------------------------------------------- /spec/dummy/config/locales/en-US.yml: -------------------------------------------------------------------------------- 1 | en-US: 2 | number: 3 | currency: 4 | format: 5 | delimiter: '' 6 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/it.yml: -------------------------------------------------------------------------------- 1 | it: 2 | number: 3 | currency: 4 | format: 5 | delimiter: "." 6 | format: "%n %u" 7 | precision: 2 8 | separator: "," 9 | significant: false 10 | strip_insignificant_zeros: false 11 | unit: "€" 12 | -------------------------------------------------------------------------------- /spec/dummy/config/mongoid.yml: -------------------------------------------------------------------------------- 1 | development: 2 | # For Mongoid 2.x 3 | host: localhost 4 | database: dummy_development 5 | 6 | # For Mongoid 3.x 7 | sessions: 8 | default: 9 | database: dummy_development 10 | hosts: 11 | - localhost:27017 12 | 13 | test: 14 | # For Mongoid 2.x 15 | host: localhost 16 | database: dummy_test 17 | 18 | # For Mongoid 3.x 19 | sessions: 20 | default: 21 | database: dummy_test 22 | hosts: 23 | - localhost:27017 24 | 25 | clients: 26 | default: 27 | database: dummy_test 28 | hosts: 29 | - localhost:27017 30 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | end 4 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120331190108_create_products.rb: -------------------------------------------------------------------------------- 1 | class CreateProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | create_table :products do |t| 4 | t.integer :price_cents 5 | t.integer :discount 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120402080348_add_bonus_cents_to_product.rb: -------------------------------------------------------------------------------- 1 | class AddBonusCentsToProduct < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :bonus_cents, :integer 4 | 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120524052716_create_services.rb: -------------------------------------------------------------------------------- 1 | class CreateServices < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | create_table :services do |t| 4 | t.integer :charge_cents 5 | t.integer :discount_cents 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120528181002_create_transactions.rb: -------------------------------------------------------------------------------- 1 | class CreateTransactions < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | create_table :transactions do |t| 4 | t.integer :amount_cents 5 | t.integer :tax_cents 6 | t.string :currency 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120528210103_create_dummy_products.rb: -------------------------------------------------------------------------------- 1 | class CreateDummyProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | create_table :dummy_products do |t| 4 | t.string :currency 5 | t.integer :price_cents 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120607210247_add_column_that_allows_nil.rb: -------------------------------------------------------------------------------- 1 | class AddColumnThatAllowsNil < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :optional_price_cents, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120712202655_add_sale_price_cents_to_product.rb: -------------------------------------------------------------------------------- 1 | class AddSalePriceCentsToProduct < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :sale_price_amount, :integer, 4 | default: 0, null: false 5 | add_column :products, :sale_price_currency_code, :string 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20130124023419_add_price_in_a_range_cents_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddPriceInARangeCentsToProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :price_in_a_range_cents, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20140110194016_add_validates_method_amount_cents_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddValidatesMethodAmountCentsToProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :validates_method_amount_cents, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20141005075025_add_aliased_attr_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddAliasedAttrToProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :aliased_cents, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20150107061030_add_delivery_fee_cents_and_restock_fee_cents_to_product.rb: -------------------------------------------------------------------------------- 1 | class AddDeliveryFeeCentsAndRestockFeeCentsToProduct < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :delivery_fee_cents, :integer 4 | add_column :products, :restock_fee_cents, :integer 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20150126231442_add_reduced_price_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddReducedPriceToProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :reduced_price_cents, :integer 4 | add_column :products, :reduced_price_currency, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20150213234410_add_special_price_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddSpecialPriceToProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :special_price_cents, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20150217222612_add_lambda_price_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddLambdaPriceToProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :lambda_price_cents, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20150303222230_add_skip_validation_price_cents_to_products.rb: -------------------------------------------------------------------------------- 1 | class AddSkipValidationPriceCentsToProducts < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :products, :skip_validation_price_cents, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20151026220420_add_optional_amount_to_transactions.rb: -------------------------------------------------------------------------------- 1 | class AddOptionalAmountToTransactions < (Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration) 2 | def change 3 | add_column :transactions, :optional_amount_cents, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2015_10_26_220420) do 14 | 15 | create_table "dummy_products", force: :cascade do |t| 16 | t.string "currency" 17 | t.integer "price_cents" 18 | t.datetime "created_at" 19 | t.datetime "updated_at" 20 | end 21 | 22 | create_table "products", force: :cascade do |t| 23 | t.integer "price_cents" 24 | t.integer "discount" 25 | t.datetime "created_at" 26 | t.datetime "updated_at" 27 | t.integer "bonus_cents" 28 | t.integer "optional_price_cents" 29 | t.integer "sale_price_amount", default: 0, null: false 30 | t.string "sale_price_currency_code" 31 | t.integer "price_in_a_range_cents" 32 | t.integer "validates_method_amount_cents" 33 | t.integer "aliased_cents" 34 | t.integer "delivery_fee_cents" 35 | t.integer "restock_fee_cents" 36 | t.integer "reduced_price_cents" 37 | t.string "reduced_price_currency" 38 | t.integer "special_price_cents" 39 | t.integer "lambda_price_cents" 40 | t.string "skip_validation_price_cents" 41 | end 42 | 43 | create_table "services", force: :cascade do |t| 44 | t.integer "charge_cents" 45 | t.integer "discount_cents" 46 | t.datetime "created_at" 47 | t.datetime "updated_at" 48 | end 49 | 50 | create_table "transactions", force: :cascade do |t| 51 | t.integer "amount_cents" 52 | t.integer "tax_cents" 53 | t.string "currency" 54 | t.datetime "created_at" 55 | t.datetime "updated_at" 56 | t.integer "optional_amount_cents" 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /spec/dummy/db/structure.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "dummy_products" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "currency" varchar(255), "price_cents" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL); 2 | CREATE TABLE "products" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "price_cents" integer, "discount" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL, "bonus_cents" integer, "optional_price_cents" integer, "sale_price_amount" integer DEFAULT 0 NOT NULL, "sale_price_currency_code" varchar(255), "price_in_a_range_cents" integer); 3 | CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); 4 | CREATE TABLE "services" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "charge_cents" integer, "discount_cents" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL); 5 | CREATE TABLE "transactions" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "amount_cents" integer, "tax_cents" integer, "currency" varchar(255), "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL); 6 | CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); 7 | INSERT INTO schema_migrations (version) VALUES ('20120331190108'); 8 | 9 | INSERT INTO schema_migrations (version) VALUES ('20120402080348'); 10 | 11 | INSERT INTO schema_migrations (version) VALUES ('20120524052716'); 12 | 13 | INSERT INTO schema_migrations (version) VALUES ('20120528181002'); 14 | 15 | INSERT INTO schema_migrations (version) VALUES ('20120528210103'); 16 | 17 | INSERT INTO schema_migrations (version) VALUES ('20120607210247'); 18 | 19 | INSERT INTO schema_migrations (version) VALUES ('20120712202655'); 20 | 21 | INSERT INTO schema_migrations (version) VALUES ('20130124023419'); -------------------------------------------------------------------------------- /spec/dummy/lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyMoney/money-rails/df280dc739b570772f5cc8e99569e85972e0158c/spec/dummy/lib/assets/.gitkeep -------------------------------------------------------------------------------- /spec/dummy/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyMoney/money-rails/df280dc739b570772f5cc8e99569e85972e0158c/spec/dummy/log/.gitkeep -------------------------------------------------------------------------------- /spec/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubyMoney/money-rails/df280dc739b570772f5cc8e99569e85972e0158c/spec/dummy/public/favicon.ico -------------------------------------------------------------------------------- /spec/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/helpers/action_view_extension_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'MoneyRails::ActionViewExtension', type: :helper do 4 | describe '#currency_symbol' do 5 | subject { helper.currency_symbol } 6 | it { is_expected.to be_a String } 7 | it { is_expected.to include Money.default_currency.symbol } 8 | 9 | context 'with given currency' do 10 | subject { helper.currency_symbol(Money::Currency.find(:brl)) } 11 | it { is_expected.to include Money::Currency.find(:brl).symbol } 12 | it { is_expected.to include Money::Currency.find(:brl).symbol } 13 | end 14 | 15 | context 'with given currency symbol' do 16 | subject { helper.currency_symbol(:brl) } 17 | it { is_expected.to include Money::Currency.find(:brl).symbol } 18 | it { is_expected.to include Money::Currency.find(:brl).symbol } 19 | end 20 | end 21 | 22 | describe '#humanized_money' do 23 | let(:money_object){ Money.new(12500) } 24 | let(:options) { {} } 25 | subject { helper.humanized_money money_object, options } 26 | it { is_expected.to be_a String } 27 | it { is_expected.not_to include Money.default_currency.symbol } 28 | it { is_expected.not_to include Money.default_currency.decimal_mark } 29 | 30 | context 'with symbol options' do 31 | let(:options) { { symbol: true } } 32 | it { is_expected.to include Money.default_currency.symbol } 33 | end 34 | 35 | context 'with deprecated symbol' do 36 | let(:options) { true } 37 | before(:each) do 38 | expect(helper).to receive(:warn) 39 | end 40 | it { is_expected.to include Money.default_currency.symbol } 41 | end 42 | 43 | context 'with a currency with an alternate symbol' do 44 | let(:money_object) { Money.new(125_00, 'SGD') } 45 | 46 | context 'with symbol options' do 47 | let(:options) { { symbol: true } } 48 | it { is_expected.to include Money::Currency.new(:sgd).symbol } 49 | 50 | context 'with disambiguate options' do 51 | let(:options) { { symbol: true, disambiguate: true } } 52 | it { is_expected.to include Money::Currency.new(:sgd).disambiguate_symbol } 53 | end 54 | end 55 | end 56 | end 57 | 58 | describe '#humanized_money_with_symbol' do 59 | subject { helper.humanized_money_with_symbol Money.new(12500) } 60 | it { is_expected.to be_a String } 61 | it { is_expected.not_to include Money.default_currency.decimal_mark } 62 | it { is_expected.to include Money.default_currency.symbol } 63 | end 64 | 65 | describe '#money_without_cents' do 66 | let(:options) { {} } 67 | subject { helper.money_without_cents Money.new(12500), options } 68 | it { is_expected.to be_a String } 69 | it { is_expected.not_to include Money.default_currency.symbol } 70 | it { is_expected.not_to include Money.default_currency.decimal_mark } 71 | 72 | context 'with deprecated symbol' do 73 | let(:options) { true } 74 | before(:each) do 75 | expect(helper).to receive(:warn) 76 | end 77 | it { is_expected.to include Money.default_currency.symbol } 78 | end 79 | end 80 | 81 | describe '#money_without_cents_and_with_symbol' do 82 | subject { helper.money_without_cents_and_with_symbol Money.new(12500) } 83 | it { is_expected.to be_a String } 84 | it { is_expected.not_to include Money.default_currency.decimal_mark } 85 | it { is_expected.to include Money.default_currency.symbol } 86 | it { is_expected.not_to include "00" } 87 | end 88 | 89 | describe '#money_only_cents' do 90 | let(:monetizable_object){ Money.new(125_00) } 91 | subject { helper.money_only_cents monetizable_object } 92 | it { is_expected.to eq "00" } 93 | 94 | context 'with a non-money object' do 95 | let(:monetizable_object){ 125 } 96 | it { is_expected.to eq "00" } 97 | end 98 | 99 | context 'with less than 10 cents' do 100 | let(:monetizable_object){ Money.new(8) } 101 | it { is_expected.to eq "08" } 102 | end 103 | 104 | context 'with a non monetizable object' do 105 | let(:monetizable_object){ false } 106 | it { is_expected.to eq "00" } 107 | end 108 | 109 | context 'with a negative monetizable object' do 110 | let(:monetizable_object){ Money.new(-1_25) } 111 | it { is_expected.to eq "25" } 112 | end 113 | end 114 | 115 | context 'respects MoneyRails::Configuration settings' do 116 | context 'with no_cents_if_whole: false' do 117 | 118 | before do 119 | MoneyRails.configure do |config| 120 | config.no_cents_if_whole = false 121 | end 122 | end 123 | 124 | describe '#humanized_money' do 125 | subject { helper.humanized_money Money.new(12500) } 126 | it { is_expected.to be_a String } 127 | it { is_expected.not_to include Money.default_currency.decimal_mark } 128 | it { is_expected.not_to include Money.default_currency.symbol } 129 | it { is_expected.to include "00" } 130 | end 131 | 132 | describe '#humanized_money_with_symbol' do 133 | subject { helper.humanized_money_with_symbol Money.new(12500) } 134 | it { is_expected.to be_a String } 135 | it { is_expected.not_to include Money.default_currency.decimal_mark } 136 | it { is_expected.to include Money.default_currency.symbol } 137 | it { is_expected.to include "00" } 138 | end 139 | end 140 | 141 | context 'with no_cents_if_whole: nil' do 142 | 143 | before do 144 | MoneyRails.configure do |config| 145 | config.no_cents_if_whole = nil 146 | end 147 | end 148 | 149 | describe '#humanized_money' do 150 | subject { helper.humanized_money Money.new(12500) } 151 | it { is_expected.to be_a String } 152 | it { is_expected.not_to include Money.default_currency.decimal_mark } 153 | it { is_expected.not_to include Money.default_currency.symbol } 154 | it { is_expected.not_to include "00" } 155 | end 156 | 157 | describe '#humanized_money_with_symbol' do 158 | subject { helper.humanized_money_with_symbol Money.new(12500) } 159 | it { is_expected.to be_a String } 160 | it { is_expected.not_to include Money.default_currency.decimal_mark } 161 | it { is_expected.to include Money.default_currency.symbol } 162 | it { is_expected.not_to include "00" } 163 | end 164 | end 165 | 166 | context 'with no_cents_if_whole: true' do 167 | 168 | before do 169 | MoneyRails.configure do |config| 170 | config.no_cents_if_whole = true 171 | end 172 | end 173 | 174 | describe '#humanized_money' do 175 | subject { helper.humanized_money Money.new(12500) } 176 | it { is_expected.to be_a String } 177 | it { is_expected.not_to include Money.default_currency.decimal_mark } 178 | it { is_expected.not_to include Money.default_currency.symbol } 179 | it { is_expected.not_to include "00" } 180 | end 181 | 182 | describe '#humanized_money_with_symbol' do 183 | subject { helper.humanized_money_with_symbol Money.new(12500) } 184 | it { is_expected.to be_a String } 185 | it { is_expected.not_to include Money.default_currency.decimal_mark } 186 | it { is_expected.to include Money.default_currency.symbol } 187 | it { is_expected.not_to include "00" } 188 | end 189 | end 190 | 191 | 192 | end 193 | 194 | end 195 | -------------------------------------------------------------------------------- /spec/helpers/form_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | if defined? ActiveRecord 4 | describe "Test helper in form blocks", type: :helper do 5 | 6 | let :product do 7 | Product.create(price_cents: 3000, discount: 150, 8 | bonus_cents: 200, optional_price: 100, 9 | sale_price_amount: 1200) 10 | end 11 | 12 | context "textfield" do 13 | it "uses the current value of money field in textfield" do 14 | helper.instance_variable_set :@product, product 15 | expect(helper.text_field(:product, :price)).to match(/value=\"#{product.price.to_s}\"/) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/money_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Money overrides' do 6 | describe '.default_formatting_rules' do 7 | it 'uses defauts set as individual options' do 8 | allow(MoneyRails::Configuration).to receive(:symbol).and_return('£') 9 | 10 | expect(Money.default_formatting_rules).to include(symbol: '£') 11 | end 12 | 13 | it 'ignores individual options that are nil' do 14 | allow(MoneyRails::Configuration).to receive(:symbol).and_return(nil) 15 | 16 | expect(Money.default_formatting_rules.keys).not_to include(:symbol) 17 | end 18 | 19 | it 'includes default_format options' do 20 | allow(MoneyRails::Configuration).to receive(:default_format).and_return(symbol: '£') 21 | 22 | expect(Money.default_formatting_rules).to include(symbol: '£') 23 | end 24 | 25 | it 'gives priority to original defaults' do 26 | allow(Money).to receive(:orig_default_formatting_rules).and_return(symbol: '£') 27 | allow(MoneyRails::Configuration).to receive(:symbol).and_return('€') 28 | allow(MoneyRails::Configuration).to receive(:default_format).and_return(symbol: '€') 29 | 30 | expect(Money.default_formatting_rules).to include(symbol: '£') 31 | end 32 | end 33 | 34 | describe '#to_hash' do 35 | it 'returns a hash with JSON representation' do 36 | expect(Money.new(9_99, 'EUR').to_hash).to eq(cents: 9_99, currency_iso: 'EUR') 37 | expect(Money.zero('USD').to_hash).to eq(cents: 0, currency_iso: 'USD') 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/mongoid/mongoid_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | if defined?(Mongoid) && ::Mongoid::VERSION.split('.').first.to_i > 2 4 | 5 | describe Money do 6 | let!(:priceable) { Priceable.create(price: Money.new(100, 'EUR')) } 7 | let(:priceable_from_nil) { Priceable.create(price: nil) } 8 | let(:priceable_from_num) { Priceable.create(price: 1) } 9 | let(:priceable_from_string) { Priceable.create(price: '1 EUR' )} 10 | let(:priceable_from_hash) { Priceable.create(price: {cents: 100, currency_iso: "EUR"} )} 11 | let(:priceable_from_blank_strings_hash) { 12 | Priceable.create(price: {cents: '', currency_iso: ''}) 13 | } 14 | let(:priceable_from_hash_with_indifferent_access) { 15 | Priceable.create(price: {cents: 100, currency_iso: "EUR"}.with_indifferent_access) 16 | } 17 | let(:priceable_from_string_with_hyphen) { Priceable.create(price: '1-2 EUR' )} 18 | let(:priceable_from_string_with_unknown_currency) { Priceable.create(price: '1 TLDR') } 19 | let(:priceable_with_infinite_precision) { Priceable.create(price: Money.new(BigDecimal('100.1'), 'EUR')) } 20 | let(:priceable_with_hash_field) { 21 | Priceable.create(price_hash: { 22 | key1: Money.new(100, "EUR"), 23 | key2: Money.new(200, "USD") 24 | }) 25 | } 26 | 27 | context "mongoize" do 28 | it "correctly mongoizes nil to nil" do 29 | expect(priceable_from_nil.price).to be_nil 30 | end 31 | 32 | it "correctly mongoizes a Money object to a hash of cents and currency" do 33 | expect(priceable.price.cents).to eq(100) 34 | expect(priceable.price.currency).to eq(Money::Currency.find('EUR')) 35 | end 36 | 37 | it "correctly mongoizes a Numeric object to a hash of cents and currency" do 38 | expect(priceable_from_num.price.cents).to eq(100) 39 | expect(priceable_from_num.price.currency).to eq(Money.default_currency) 40 | end 41 | 42 | it "correctly mongoizes a String object to a hash of cents and currency" do 43 | expect(priceable_from_string.price.cents).to eq(100) 44 | expect(priceable_from_string.price.currency).to eq(Money::Currency.find('EUR')) 45 | end 46 | 47 | context "when MoneyRails.raise_error_on_money_parsing is true" do 48 | before { MoneyRails.raise_error_on_money_parsing = true } 49 | after { MoneyRails.raise_error_on_money_parsing = false } 50 | 51 | it "raises exception if the mongoized value is a String with a hyphen" do 52 | expect { priceable_from_string_with_hyphen }.to raise_error MoneyRails::Error 53 | end 54 | 55 | it "raises exception if the mongoized value is a String with an unknown currency" do 56 | expect { priceable_from_string_with_unknown_currency }.to raise_error MoneyRails::Error 57 | end 58 | end 59 | 60 | context "when MoneyRails.raise_error_on_money_parsing is false" do 61 | it "does not correctly mongoize a String with a hyphen in its middle" do 62 | expect(priceable_from_string_with_hyphen.price).to eq(nil) 63 | end 64 | 65 | it "does not correctly mongoize a String with an unknown currency" do 66 | expect(priceable_from_string_with_unknown_currency.price).to eq(nil) 67 | end 68 | end 69 | 70 | it "correctly mongoizes a hash of cents and currency" do 71 | expect(priceable_from_hash.price.cents).to eq(100) 72 | expect(priceable_from_hash.price.currency).to eq(Money::Currency.find('EUR')) 73 | end 74 | 75 | it "mongoizes a hash of blank strings for cents and currency to nil" do 76 | expect(priceable_from_blank_strings_hash.price).to eq(nil) 77 | end 78 | 79 | it "correctly mongoizes a HashWithIndifferentAccess of cents and currency" do 80 | expect(priceable_from_hash_with_indifferent_access.price.cents).to eq(100) 81 | expect(priceable_from_hash_with_indifferent_access.price.currency).to eq(Money::Currency.find('EUR')) 82 | end 83 | 84 | context "infinite_precision = true" do 85 | before do 86 | Money.infinite_precision = true 87 | end 88 | 89 | after do 90 | Money.infinite_precision = false 91 | end 92 | 93 | it "correctly mongoizes a Money object to a hash of cents and currency" do 94 | expect(priceable_with_infinite_precision.price.cents).to eq(BigDecimal('100.1')) 95 | expect(priceable_with_infinite_precision.price.currency).to eq(Money::Currency.find('EUR')) 96 | end 97 | end 98 | end 99 | 100 | it "correctly serializes a Hash field containing Money objects" do 101 | expect(priceable_with_hash_field.price_hash[:key1][:cents]).to eq(100) 102 | expect(priceable_with_hash_field.price_hash[:key2][:cents]).to eq(200) 103 | expect(priceable_with_hash_field.price_hash[:key1][:currency_iso]).to eq('EUR') 104 | expect(priceable_with_hash_field.price_hash[:key2][:currency_iso]).to eq('USD') 105 | end 106 | 107 | context "demongoize" do 108 | subject { Priceable.first.price } 109 | 110 | it { is_expected.to be_an_instance_of(Money) } 111 | it { is_expected.to eq(Money.new(100, 'EUR')) } 112 | 113 | it "returns nil if a nil value was stored" do 114 | nil_priceable = Priceable.create(price: nil) 115 | expect(nil_priceable.price).to be_nil 116 | end 117 | 118 | it 'returns nil if an unknown value was stored' do 119 | zero_priceable = Priceable.create(price: []) 120 | expect(zero_priceable.price).to be_nil 121 | end 122 | end 123 | 124 | context "evolve" do 125 | it "correctly transforms a Money object into a Mongo friendly value" do 126 | expect(Priceable.where(price: Money.new(100, 'EUR')).first).to eq(priceable) 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/mongoid/two_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | if defined?(Mongoid) && ::Mongoid::VERSION =~ /^2(.*)/ 4 | 5 | describe Money do 6 | let(:priceable) { Priceable.create(price: Money.new(100, 'EUR')) } 7 | let(:priceable_from_nil) { Priceable.create(price: nil) } 8 | let(:priceable_from_num) { Priceable.create(price: 1) } 9 | let(:priceable_from_string) { Priceable.create(price: '1 EUR' )} 10 | let(:priceable_with_infinite_precision) { Priceable.create(price: Money.new(BigDecimal('100.1'), 'EUR')) } 11 | let(:priceable_from_string_with_hyphen) { Priceable.create(price: '1-2 EUR' )} 12 | 13 | context "serialize" do 14 | it "mongoizes correctly nil to nil" do 15 | expect(priceable_from_nil.price).to be_nil 16 | end 17 | 18 | it "serializes correctly a Money object to a hash of cents and currency" do 19 | expect(priceable.price.cents).to eq(100) 20 | expect(priceable.price.currency).to eq(Money::Currency.find('EUR')) 21 | end 22 | 23 | it "mongoizes correctly a Numeric object to a hash of cents and currency" do 24 | expect(priceable_from_num.price.cents).to eq(100) 25 | expect(priceable_from_num.price.currency).to eq(Money.default_currency) 26 | end 27 | 28 | it "mongoizes correctly a String object to a hash of cents and currency" do 29 | expect(priceable_from_string.price.cents).to eq(100) 30 | expect(priceable_from_string.price.currency).to eq(Money::Currency.find('EUR')) 31 | end 32 | 33 | context "infinite_precision = true" do 34 | before do 35 | Money.infinite_precision = true 36 | end 37 | 38 | after do 39 | Money.infinite_precision = false 40 | end 41 | 42 | it "mongoizes correctly a Money object to a hash of cents and currency" do 43 | expect(priceable_with_infinite_precision.price.cents).to eq(BigDecimal('100.1')) 44 | expect(priceable_with_infinite_precision.price.currency).to eq(Money::Currency.find('EUR')) 45 | end 46 | end 47 | 48 | context "when MoneyRails.raise_error_on_money_parsing is true" do 49 | before { MoneyRails.raise_error_on_money_parsing = true } 50 | after { MoneyRails.raise_error_on_money_parsing = false } 51 | 52 | it "raises exception if the mongoized value is a String with a hyphen" do 53 | expect { priceable_from_string_with_hyphen }.to raise_error MoneyRails::Error 54 | end 55 | end 56 | 57 | context "when MoneyRails.raise_error_on_money_parsing is false" do 58 | it "does not mongoizes correctly a String with hyphen in its middle" do 59 | expect(priceable_from_string_with_hyphen.price).to eq(nil) 60 | end 61 | end 62 | end 63 | 64 | context "deserialize" do 65 | subject { priceable.price } 66 | it { is_expected.to be_an_instance_of(Money) } 67 | it { is_expected.to eq(Money.new(100, 'EUR')) } 68 | 69 | it "returns nil if a nil value was stored" do 70 | nil_priceable = Priceable.create(price: nil) 71 | expect(nil_priceable.price).to be_nil 72 | end 73 | 74 | it 'returns nil if an unknown value was stored' do 75 | zero_priceable = Priceable.create(price: []) 76 | expect(zero_priceable.price).to be_nil 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = 'test' 2 | # Require dummy Rails app 3 | require File.expand_path("../../spec/dummy/config/environment", __FILE__) 4 | 5 | require 'database_cleaner' 6 | require 'rspec/rails' 7 | 8 | # Requires supporting ruby files with custom matchers and macros, etc, 9 | # in spec/support/ and its subdirectories. 10 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 11 | 12 | # Silence warnings 13 | if Money.respond_to?(:silence_core_extensions_deprecations=) 14 | Money.silence_core_extensions_deprecations = true 15 | end 16 | 17 | RSpec.configure do |config| 18 | # If true, the base class of anonymous controllers will be inferred 19 | # automatically. This will be the default behavior in future versions of 20 | # rspec-rails. 21 | config.infer_base_class_for_anonymous_controllers = false 22 | config.infer_spec_type_from_file_location! 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | DatabaseCleaner[:active_record].strategy = :transaction if defined? ActiveRecord 2 | DatabaseCleaner[:mongoid].strategy = :truncation if defined? Mongoid 3 | 4 | RSpec.configure do |config| 5 | config.before :suite do 6 | DatabaseCleaner.clean_with :truncation 7 | end 8 | config.before :each do 9 | DatabaseCleaner.start 10 | end 11 | config.after :each do 12 | DatabaseCleaner.clean 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/test_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | if defined? ActiveRecord 5 | describe 'TestHelpers' do 6 | 7 | require "money-rails/test_helpers" 8 | include MoneyRails::TestHelpers 9 | 10 | let(:product) do 11 | Product.create(price_cents: 3000, discount: 150, 12 | bonus_cents: 200, 13 | sale_price_amount: 1200) 14 | end 15 | 16 | describe "monetize matcher" do 17 | 18 | shared_context "monetize matcher" do 19 | 20 | it "matches model attribute without a '_cents' suffix by default" do 21 | is_expected.to monetize(:price) 22 | end 23 | 24 | it "matches model attribute specified by :as chain" do 25 | is_expected.to monetize(:discount).as(:discount_value) 26 | end 27 | 28 | it "matches model attribute with nil value specified by :allow_nil chain" do 29 | is_expected.to monetize(:optional_price).allow_nil 30 | end 31 | 32 | it "matches nullable model attribute when tested instance has a non-nil value" do 33 | is_expected.to monetize(:optional_price).allow_nil 34 | end 35 | 36 | it "matches model attribute with currency specified by :with_currency chain" do 37 | is_expected.to monetize(:bonus).with_currency(:gbp) 38 | end 39 | 40 | it "matches model attribute with currency attribute specified by :with_model_currency chain" do 41 | is_expected.to( 42 | monetize(:sale_price_amount) 43 | .as(:sale_price) 44 | .with_model_currency(:sale_price_currency_code) 45 | ) 46 | end 47 | 48 | it "does not match non existed attribute" do 49 | is_expected.not_to monetize(:price_fake) 50 | end 51 | 52 | it "does not match wrong currency iso" do 53 | is_expected.not_to monetize(:bonus).with_currency(:usd) 54 | end 55 | 56 | it "does not match wrong money attribute name" do 57 | is_expected.not_to monetize(:bonus).as(:bonussss) 58 | end 59 | end 60 | 61 | describe "testing against an instance of the model class" do 62 | subject { product } 63 | include_context "monetize matcher" 64 | end 65 | 66 | describe "testing against the model class itself" do 67 | subject { Product } 68 | include_context "monetize matcher" 69 | end 70 | end 71 | end 72 | end 73 | --------------------------------------------------------------------------------