economy 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +129 -0
  4. data/Rakefile +19 -0
  5. data/lib/economy.rb +43 -0
  6. data/lib/economy/builder.rb +81 -0
  7. data/lib/economy/cache.rb +26 -0
  8. data/lib/economy/configuration.rb +11 -0
  9. data/lib/economy/currencies.rb +34 -0
  10. data/lib/economy/currency.rb +28 -0
  11. data/lib/economy/exchange.rb +17 -0
  12. data/lib/economy/extensions/active_record/base.rb +18 -0
  13. data/lib/economy/money.rb +170 -0
  14. data/lib/economy/railtie.rb +18 -0
  15. data/lib/economy/rates/base.rb +27 -0
  16. data/lib/economy/rates/yahoo.rb +38 -0
  17. data/lib/economy/version.rb +5 -0
  18. data/lib/generators/economy/install_generator.rb +24 -0
  19. data/lib/generators/economy/templates/initializer.rb +13 -0
  20. data/lib/generators/economy/templates/migration.rb +14 -0
  21. data/lib/tasks/economy.rake +5 -0
  22. data/test/dummy/Rakefile +5 -0
  23. data/test/dummy/app/assets/javascripts/application.js +13 -0
  24. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  25. data/test/dummy/app/controllers/application_controller.rb +5 -0
  26. data/test/dummy/app/helpers/application_helper.rb +2 -0
  27. data/test/dummy/app/models/plan.rb +5 -0
  28. data/test/dummy/app/models/product.rb +5 -0
  29. data/test/dummy/app/views/layouts/application.html.erb +12 -0
  30. data/test/dummy/bin/bundle +4 -0
  31. data/test/dummy/bin/rails +5 -0
  32. data/test/dummy/bin/rake +5 -0
  33. data/test/dummy/bin/setup +30 -0
  34. data/test/dummy/config.ru +4 -0
  35. data/test/dummy/config/application.rb +25 -0
  36. data/test/dummy/config/boot.rb +5 -0
  37. data/test/dummy/config/database.yml +7 -0
  38. data/test/dummy/config/database.yml.travis +3 -0
  39. data/test/dummy/config/environment.rb +5 -0
  40. data/test/dummy/config/environments/development.rb +41 -0
  41. data/test/dummy/config/environments/production.rb +79 -0
  42. data/test/dummy/config/environments/test.rb +45 -0
  43. data/test/dummy/config/initializers/assets.rb +11 -0
  44. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  45. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  46. data/test/dummy/config/initializers/economy.rb +19 -0
  47. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  48. data/test/dummy/config/initializers/inflections.rb +16 -0
  49. data/test/dummy/config/initializers/mime_types.rb +4 -0
  50. data/test/dummy/config/initializers/session_store.rb +3 -0
  51. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  52. data/test/dummy/config/locales/en.yml +23 -0
  53. data/test/dummy/config/routes.rb +56 -0
  54. data/test/dummy/config/secrets.yml +22 -0
  55. data/test/dummy/db/migrate/20161115135521_create_exchanges.rb +14 -0
  56. data/test/dummy/db/migrate/20161115145905_create_products.rb +10 -0
  57. data/test/dummy/db/migrate/20161115150024_create_plans.rb +11 -0
  58. data/test/dummy/db/schema.rb +45 -0
  59. data/test/dummy/log/development.log +172 -0
  60. data/test/dummy/log/test.log +8574 -0
  61. data/test/dummy/public/404.html +67 -0
  62. data/test/dummy/public/422.html +67 -0
  63. data/test/dummy/public/500.html +66 -0
  64. data/test/dummy/public/favicon.ico +0 -0
  65. data/test/fixtures/yahoo/multiple.json +29 -0
  66. data/test/fixtures/yahoo/single.json +18 -0
  67. data/test/fixtures/yahoo/unknown.json +18 -0
  68. data/test/generator_test.rb +19 -0
  69. data/test/money_test.rb +142 -0
  70. data/test/rates_test.rb +60 -0
  71. data/test/record_test.rb +60 -0
  72. data/test/support/money_helper.rb +30 -0
  73. data/test/support/rates_helper.rb +16 -0
  74. data/test/task_test.rb +22 -0
  75. data/test/test_helper.rb +14 -0
  76. metadata +234 -0
@@ -0,0 +1,170 @@
1
+ module Economy
2
+ class Money
3
+ include Comparable
4
+
5
+ attr_reader :amount, :currency
6
+
7
+ delegate :to_d, :to_i, :to_f, to: :amount
8
+
9
+ def initialize(amount, currency)
10
+ if amount.is_a?(BigDecimal)
11
+ @amount = amount
12
+ else
13
+ @amount = BigDecimal(amount.to_s)
14
+ end
15
+ @currency = normalize_currency(currency)
16
+ end
17
+
18
+ def coerce(other)
19
+ [self, other]
20
+ end
21
+
22
+ def abs
23
+ Money.new amount.abs, currency
24
+ end
25
+ alias_method :magnitude, :abs
26
+
27
+ def -@
28
+ Money.new -amount, currency
29
+ end
30
+
31
+ def positive?
32
+ amount > 0
33
+ end
34
+
35
+ def negative?
36
+ amount < 0
37
+ end
38
+
39
+ def zero?
40
+ amount == 0
41
+ end
42
+
43
+ def nonzero?
44
+ amount != 0
45
+ end
46
+
47
+ def ===(other)
48
+ if other.is_a?(Money)
49
+ amount == other.amount && currency == other.currency
50
+ else
51
+ raise "Can't compare #{other.class.name} with Money"
52
+ end
53
+ end
54
+
55
+ def <=>(other)
56
+ if other.is_a?(Numeric) && other == 0
57
+ amount <=> other
58
+ elsif other.is_a?(Money)
59
+ other = other.exchange_to(currency)
60
+ amount <=> other.amount
61
+ else
62
+ raise "Only Numeric 0 and Money can be compared with Money"
63
+ end
64
+ end
65
+
66
+ def +(other)
67
+ if other.is_a?(Money)
68
+ other = other.exchange_to(currency)
69
+ Money.new (amount + other.amount), currency
70
+ else
71
+ raise "Can't add #{other.class.name} to Money"
72
+ end
73
+ end
74
+
75
+ def -(other)
76
+ if other.is_a?(Money)
77
+ other = other.exchange_to(currency)
78
+ Money.new (amount - other.amount), currency
79
+ else
80
+ raise "Can't subtract #{other.class.name} from Money"
81
+ end
82
+ end
83
+
84
+ def *(value)
85
+ if value.is_a?(Numeric)
86
+ Money.new (amount * value), currency
87
+ else
88
+ raise "Can't multiply Money by #{value.class.name}"
89
+ end
90
+ end
91
+
92
+ def /(value)
93
+ case value
94
+ when Money
95
+ amount / value.exchange_to(currency).amount
96
+ when Numeric
97
+ Money.new (amount / value), currency
98
+ else
99
+ raise "Can't divide Money by #{value.class.name}"
100
+ end
101
+ end
102
+ alias_method :div, :/
103
+
104
+ def divmod(value)
105
+ case value
106
+ when Money
107
+ quotient, modulo = amount.divmod(value.exchange_to(currency).amount)
108
+ [quotient, Money.new(modulo, currency)]
109
+ when Numeric
110
+ quotient, modulo = amount.divmod(value)
111
+ [Money.new(quotient, currency), Money.new(modulo, currency)]
112
+ else
113
+ raise "Can't divide Money by #{value.class.name}"
114
+ end
115
+ end
116
+
117
+ def %(value)
118
+ divmod(value)[1]
119
+ end
120
+ alias_method :modulo, :%
121
+
122
+ def remainder(value)
123
+ case value
124
+ when Money
125
+ Money.new amount.remainder(value.exchange_to(currency).amount), currency
126
+ when Numeric
127
+ Money.new amount.remainder(value), currency
128
+ else
129
+ raise "Can't divide Money by #{value.class.name}"
130
+ end
131
+ end
132
+
133
+ def exchange_to(new_currency)
134
+ new_currency = normalize_currency(new_currency)
135
+ if currency != new_currency
136
+ if rate = Economy.cache.get(currency, new_currency)
137
+ Money.new (amount * BigDecimal(rate)), new_currency
138
+ else
139
+ raise "Exchange #{currency} => #{new_currency} not found"
140
+ end
141
+ else
142
+ self
143
+ end
144
+ end
145
+
146
+ def to_json(options={})
147
+ "%.#{currency.decimals}f" % amount
148
+ end
149
+ alias_method :as_json, :to_json
150
+
151
+ def to_s(precision=nil)
152
+ ActiveSupport::NumberHelper.number_to_currency(
153
+ amount,
154
+ unit: currency.symbol,
155
+ precision: (precision || currency.decimals)
156
+ )
157
+ end
158
+
159
+ private
160
+
161
+ def normalize_currency(value)
162
+ if value.is_a?(Currency)
163
+ value
164
+ else
165
+ Economy.currencies.find value
166
+ end
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,18 @@
1
+ module Economy
2
+ class Railtie < Rails::Railtie
3
+
4
+ initializer :sidejobs do
5
+ # Prevent deprecation warning
6
+ require 'economy/exchange'
7
+
8
+ ::ActiveRecord::Base.include(
9
+ Economy::Extensions::ActiveRecord::Base
10
+ )
11
+ end
12
+
13
+ rake_tasks do
14
+ load 'tasks/economy.rake'
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ module Economy
2
+ module Rates
3
+ class Base
4
+
5
+ attr_reader :retries
6
+
7
+ def initialize
8
+ @retries = 0
9
+ end
10
+
11
+ def fetch
12
+ begin
13
+ call
14
+ rescue
15
+ if retries < 30
16
+ sleep 60
17
+ @retries += 1
18
+ fetch
19
+ else
20
+ puts "Giving up after #{retries} retries"
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ module Economy
2
+ module Rates
3
+ class Yahoo < Base
4
+
5
+ private
6
+
7
+ def call
8
+ ids = Economy.currencies.map(&:iso_code).permutation(2).map(&:join).join(',')
9
+ uri = URI('https://query.yahooapis.com/v1/public/yql')
10
+ uri.query = URI.encode_www_form(
11
+ q: "select * from yahoo.finance.xchange where pair in ('#{ids}')",
12
+ env: 'store://datatables.org/alltableswithkeys',
13
+ format: 'json'
14
+ )
15
+ response = Net::HTTP.get_response(uri)
16
+ if response.code == '200'
17
+ body = JSON.parse(response.body.downcase, symbolize_names: true)
18
+ results = body[:query][:results][:rate]
19
+ if results.is_a?(Hash)
20
+ results = [results]
21
+ end
22
+ rates = []
23
+ results.each do |result|
24
+ if result[:name] != 'n/a'
25
+ from, to = result[:name].split('/').map(&:upcase)
26
+ rate = BigDecimal(result[:rate])
27
+ rates << [from, to, rate]
28
+ end
29
+ end
30
+ rates
31
+ else
32
+ []
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ module Economy
2
+
3
+ VERSION = '0.0.1'
4
+
5
+ end
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+
3
+ module Economy
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ def create_initializer_file
11
+ copy_file 'initializer.rb', 'config/initializers/economy.rb'
12
+ end
13
+
14
+ def create_migration_file
15
+ migration_template 'migration.rb', 'db/migrate/create_exchanges.rb'
16
+ end
17
+
18
+ def self.next_migration_number(path)
19
+ Time.now.utc.strftime '%Y%m%d%H%M%S'
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ Economy.configure do |config|
2
+
3
+ config.rates = :yahoo
4
+ config.default_currency = 'USD'
5
+
6
+ config.add_currency(
7
+ iso_code: 'USD',
8
+ iso_number: 840,
9
+ symbol: 'U$S',
10
+ decimals: 2
11
+ )
12
+
13
+ end
@@ -0,0 +1,14 @@
1
+ class CreateExchanges < ActiveRecord::Migration
2
+ def change
3
+ create_table :exchanges do |t|
4
+ t.string :service, limit: 30
5
+ t.string :from, limit: 3
6
+ t.string :to, limit: 3
7
+ t.decimal :rate, precision: 24, scale: 12
8
+
9
+ t.timestamps null: false
10
+ end
11
+
12
+ add_index :exchanges, %i(from to)
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ namespace :economy do
2
+ task update_rates: :environment do
3
+ Economy.update_rates
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+ Rails.application.load_tasks
@@ -0,0 +1,13 @@
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 any plugin's vendor/assets/javascripts directory 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
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
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 any plugin's vendor/assets/stylesheets directory 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 bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,5 @@
1
+ class Plan < ActiveRecord::Base
2
+
3
+ monetize :monthly_price, :annually_price
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ class Product < ActiveRecord::Base
2
+
3
+ monetize :price
4
+
5
+ end
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dummy</title>
5
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6
+ <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+ <%= yield %>
11
+ </body>
12
+ </html>
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
4
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
4
+ require_relative '../config/boot'
5
+ require 'rails/commands'
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../config/boot'
4
+ require 'rake'
5
+ Rake.application.run
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+
5
+ # path to your application root.
6
+ APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
7
+
8
+ Dir.chdir APP_ROOT do
9
+ # This script is a starting point to setup your application.
10
+ # Add necessary setup steps to this file:
11
+
12
+ puts '== Installing dependencies =='
13
+ system 'gem install bundler --conservative'
14
+ system 'bundle check || bundle install'
15
+
16
+ # puts "\n== Copying sample files =="
17
+ # unless File.exist?('config/database.yml')
18
+ # system 'cp config/database.yml.sample config/database.yml'
19
+ # end
20
+
21
+ puts "\n== Preparing database =="
22
+ system 'bin/rake db:setup'
23
+
24
+ puts "\n== Removing old logs and tempfiles =="
25
+ system 'rm -f log/*'
26
+ system 'rm -rf tmp/cache'
27
+
28
+ puts "\n== Restarting application server =="
29
+ system 'touch tmp/restart.txt'
30
+ end