easy_rails_money 0.0.3 → 0.0.4
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.
- data/.gitignore +10 -5
- data/CHANGELOG.md +3 -2
- data/README.md +114 -20
- data/lib/easy_rails_money/active_record/money_dsl.rb +85 -5
- data/lib/easy_rails_money/configuration.rb +6 -1
- data/lib/easy_rails_money/money_dsl_helper.rb +9 -0
- data/lib/easy_rails_money/version.rb +1 -1
- data/spec/active_record/migration_spec.rb +235 -271
- data/spec/active_record/money_dsl_spec.rb +207 -5
- data/spec/active_record_spec_helper.rb +29 -0
- data/spec/configuration_spec.rb +30 -13
- data/spec/loan_model_spec_helper.rb +6 -0
- data/spec/loan_with_currency_model_spec_helper.rb +10 -0
- metadata +9 -4
data/.gitignore
CHANGED
@@ -3,20 +3,25 @@
|
|
3
3
|
*.rbc
|
4
4
|
.bundle
|
5
5
|
.config
|
6
|
-
.yardoc
|
7
6
|
Gemfile.lock
|
8
|
-
InstalledFiles
|
9
|
-
_yardoc
|
10
7
|
coverage
|
11
|
-
|
8
|
+
InstalledFiles
|
12
9
|
lib/bundler/man
|
13
10
|
pkg
|
14
|
-
rdoc
|
15
11
|
spec/reports
|
16
12
|
test/tmp
|
17
13
|
test/version_tmp
|
18
14
|
tmp
|
19
15
|
|
16
|
+
# YARD artifacts
|
17
|
+
.yardoc
|
18
|
+
_yardoc
|
19
|
+
doc/
|
20
|
+
rdoc
|
21
|
+
|
20
22
|
.rvmrc
|
23
|
+
.ruby-version
|
24
|
+
.ruby-gemset
|
25
|
+
|
21
26
|
NOTES
|
22
27
|
.rspec
|
data/CHANGELOG.md
CHANGED
@@ -14,9 +14,10 @@
|
|
14
14
|
- has a dependency on ActiveRecord and removed the dependency on Rails
|
15
15
|
|
16
16
|
## 0.0.3
|
17
|
-
- add missing cases for supporting a single currency
|
17
|
+
- add missing test-cases for supporting a single currency
|
18
18
|
- refactor tests
|
19
19
|
- add simplecov coverage report
|
20
20
|
- Add yard docs and update README
|
21
21
|
|
22
|
-
|
22
|
+
## 0.0.4
|
23
|
+
- add dsl with_currency for defining a single currency on the model
|
data/README.md
CHANGED
@@ -2,11 +2,6 @@
|
|
2
2
|
[](https://gemnasium.com/deepak/easy_rails_money)
|
3
3
|
[](https://codeclimate.com/github/deepak/easy_rails_money)
|
4
4
|
|
5
|
-
### Under Development
|
6
|
-
|
7
|
-
The migration helpers are functionally complete.
|
8
|
-
Working on integrating with Rails' ActiveModel
|
9
|
-
|
10
5
|
# EasyRailsMoney
|
11
6
|
|
12
7
|
> “Young people, nowadays, imagine that money is everything.
|
@@ -17,13 +12,11 @@ Working on integrating with Rails' ActiveModel
|
|
17
12
|
|
18
13
|
This library provides integration of [money](http://github.com/Rubymoney/money) gem with [Rails](https://github.com/rails/rails).
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
submitted a PR to that project and it is actively maintained.
|
15
|
+
It provides migration helpers to define a schema with either a single
|
16
|
+
currency column or a currency column per-money object.
|
23
17
|
|
24
|
-
|
25
|
-
|
26
|
-
I created this project to scratch my itch.
|
18
|
+
It also provides a ActiveRecord DSL to define that an attribute is a
|
19
|
+
Money object and that it has a default currency
|
27
20
|
|
28
21
|
Please open a new issue [in the github project issues tracker](http://github.com/deepak/easy_rails_money/issues). You are also
|
29
22
|
more than welcome to contribute to the project :-)
|
@@ -33,6 +26,14 @@ more than welcome to contribute to the project :-)
|
|
33
26
|
Have stolen lots of code from [money-rails](https://github.com/RubyMoney/money-rails)
|
34
27
|
But database schema, API and tests are written from scratch
|
35
28
|
|
29
|
+
[money-rails](https://github.com/RubyMoney/money-rails) is much more
|
30
|
+
popular and full-featured. Definately try it out. I have actually
|
31
|
+
submitted a PR to that project and it is actively maintained.
|
32
|
+
|
33
|
+
I have tried to create a simpler version of [money-rails](https://github.com/RubyMoney/money-rails)
|
34
|
+
With a better API and database schema, in my opinion.
|
35
|
+
I created this project to scratch my itch.
|
36
|
+
|
36
37
|
## Installation
|
37
38
|
|
38
39
|
Add this line to your application's Gemfile:
|
@@ -69,7 +70,7 @@ Option 1:
|
|
69
70
|
class CreateLoan < ActiveRecord::Migration
|
70
71
|
def change
|
71
72
|
create_table :loans do |t|
|
72
|
-
t.integer :
|
73
|
+
t.integer :principal_money
|
73
74
|
t.string :principal_currency
|
74
75
|
end
|
75
76
|
end
|
@@ -101,8 +102,8 @@ Floating-Point Arithmetic, by David Goldberg, published in March,
|
|
101
102
|
|
102
103
|
We have encoded the currency in the column name. I like it because
|
103
104
|
there is no need to define another column and it is simple. But the
|
104
|
-
disadvantage is that it is inflexible
|
105
|
-
MySQL might require downtime for a big table
|
105
|
+
disadvantage is that it is inflexible ie. cannot store two currencies
|
106
|
+
and changing the column name in MySQL might require downtime for a big table
|
106
107
|
|
107
108
|
So let us go with the first option. The disadvantage is that currency
|
108
109
|
is stored as a string. Integer might be better for storing in the database
|
@@ -121,11 +122,11 @@ Now we would represent it as
|
|
121
122
|
class CreateLoan < ActiveRecord::Migration
|
122
123
|
def change
|
123
124
|
create_table :loans do |t|
|
124
|
-
t.integer :
|
125
|
+
t.integer :principal_money
|
125
126
|
t.string :principal_currency
|
126
|
-
t.integer :
|
127
|
+
t.integer :repaid_money
|
127
128
|
t.string :repaid_currency
|
128
|
-
t.integer :
|
129
|
+
t.integer :npa_money
|
129
130
|
t.string :npa_currency
|
130
131
|
end
|
131
132
|
end
|
@@ -141,9 +142,9 @@ class CreateLoan < ActiveRecord::Migration
|
|
141
142
|
def change
|
142
143
|
create_table :loans do |t|
|
143
144
|
t.string :currency
|
144
|
-
t.integer :
|
145
|
-
t.integer :
|
146
|
-
t.integer :
|
145
|
+
t.integer :principal_money
|
146
|
+
t.integer :repaid_money
|
147
|
+
t.integer :npa_money
|
147
148
|
end
|
148
149
|
end
|
149
150
|
end
|
@@ -152,6 +153,10 @@ end
|
|
152
153
|
It might be possible that we set a currency once for the whole app and
|
153
154
|
never change it. But this seems like a nice tradeoff api-wise
|
154
155
|
|
156
|
+
Also the column names are suffixed with ```_money``` and ```_currency```
|
157
|
+
We need this for now, to reflect on the database scheme. I
|
158
|
+
Ideally should be able to read the metadata from rails scheme cache.
|
159
|
+
|
155
160
|
## Usage
|
156
161
|
|
157
162
|
### ActiveRecord
|
@@ -256,6 +261,88 @@ It is used to reflect on the database schema ie. to find out the
|
|
256
261
|
money and currency columns defined.
|
257
262
|
Right now, none of these choices are customizable.
|
258
263
|
|
264
|
+
#### Defining the Model
|
265
|
+
|
266
|
+
If every money column has its own currency column, then we cn define
|
267
|
+
the model as:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
class Loan < ActiveRecord::Base
|
271
|
+
attr_accessible :name
|
272
|
+
money :principal
|
273
|
+
money :repaid
|
274
|
+
money :npa
|
275
|
+
end
|
276
|
+
```
|
277
|
+
|
278
|
+
The corresponding migration (given above) is:
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
class CreateLoanWithCurrency < ActiveRecord::Migration
|
282
|
+
def change
|
283
|
+
create_table :loans, force: true do |t|
|
284
|
+
t.string :name
|
285
|
+
t.money :principal
|
286
|
+
t.money :repaid
|
287
|
+
t.money :npa
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
```
|
292
|
+
|
293
|
+
Now if you want a single currency column then:
|
294
|
+
|
295
|
+
```ruby
|
296
|
+
class Loan < ActiveRecord::Base
|
297
|
+
attr_accessible :name
|
298
|
+
|
299
|
+
with_currency(:inr) do
|
300
|
+
money :principal
|
301
|
+
money :repaid
|
302
|
+
money :npa
|
303
|
+
end
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
The corresponding migration (given above) is:
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
class CreateLoanWithCurrency < ActiveRecord::Migration
|
311
|
+
def change
|
312
|
+
create_table :loans, force: true do |t|
|
313
|
+
t.string :name
|
314
|
+
t.money :principal
|
315
|
+
t.money :repaid
|
316
|
+
t.money :npa
|
317
|
+
t.currency
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
```
|
322
|
+
|
323
|
+
For such a record, where the single currency is defined. calling
|
324
|
+
currency on a new record will give us the currency. And can define a
|
325
|
+
common currency per-record while creating it
|
326
|
+
|
327
|
+
eg:
|
328
|
+
```ruby
|
329
|
+
class Loan < ActiveRecord::Base
|
330
|
+
attr_accessible :name
|
331
|
+
|
332
|
+
with_currency(:inr) do
|
333
|
+
money :principal
|
334
|
+
money :repaid
|
335
|
+
money :npa
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
loan = Loan.new
|
340
|
+
loan.currency # equals Money::Currency.new(:inr)
|
341
|
+
|
342
|
+
loan_usd = Loan.new(currency: :usd)
|
343
|
+
loan_usd.currency # equals Money::Currency.new(:usd)
|
344
|
+
```
|
345
|
+
|
259
346
|
## TODO's
|
260
347
|
1. Proof-read docs
|
261
348
|
2. currency is stored as a string. Integer might be better for storing in the database
|
@@ -267,3 +354,10 @@ Right now, none of these choices are customizable.
|
|
267
354
|
6. configure the ```_money``` and ```_currency``` prefix and the name
|
268
355
|
of the common ```currency``` column
|
269
356
|
7. check specs tagged as "fixme"
|
357
|
+
8. cryptographically sign gem
|
358
|
+
9. test if Memoization in ```MoneyDsl#money`` will make any difference
|
359
|
+
and add a performance test to catch regressions
|
360
|
+
10. will it make sense to define the ```money``` dsl on ```ActiveModel``` ?
|
361
|
+
11. the column names are suffixed with ```_money``` and ```_currency```
|
362
|
+
We need this for now, to reflect on the database scheme.
|
363
|
+
Ideally should be able to read the metadata from rails scheme cache.
|
@@ -1,17 +1,97 @@
|
|
1
1
|
require 'active_support/concern'
|
2
|
-
require
|
2
|
+
require "easy_rails_money/money_dsl_helper"
|
3
3
|
|
4
4
|
module EasyRailsMoney
|
5
5
|
module ActiveRecord
|
6
6
|
module MoneyDsl
|
7
7
|
extend ActiveSupport::Concern
|
8
|
-
|
8
|
+
|
9
9
|
module ClassMethods
|
10
|
-
|
11
|
-
|
10
|
+
attr_accessor :single_currency
|
11
|
+
|
12
|
+
def single_currency?
|
13
|
+
self.columns_hash.has_key? "currency"
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_currency currency, &block
|
17
|
+
self.single_currency = EasyRailsMoney::MoneyDslHelper.to_currency(currency)
|
18
|
+
instance_eval &block
|
19
|
+
end
|
20
|
+
|
21
|
+
def new(attributes = nil, options = {})
|
22
|
+
instance = super
|
23
|
+
# single currency is defined
|
24
|
+
if single_currency?
|
25
|
+
if attributes && attributes[:currency]
|
26
|
+
instance.currency = EasyRailsMoney::MoneyDslHelper.to_currency attributes[:currency]
|
27
|
+
else
|
28
|
+
instance.currency = instance.class.single_currency
|
29
|
+
end
|
30
|
+
end
|
31
|
+
instance
|
12
32
|
end
|
13
|
-
|
33
|
+
|
34
|
+
def money column_name
|
35
|
+
money_column = "#{column_name}_money"
|
36
|
+
currency_column = "#{column_name}_currency"
|
37
|
+
single_currency_column = "currency"
|
38
|
+
|
39
|
+
if single_currency?
|
40
|
+
define_method column_name do |*args|
|
41
|
+
money = send(money_column)
|
42
|
+
currency = send(single_currency_column)
|
43
|
+
|
44
|
+
if money
|
45
|
+
Money.new(money, currency)
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
# TODO: test if Memoization will make any difference
|
52
|
+
define_method column_name do |*args|
|
53
|
+
money = send(money_column)
|
54
|
+
currency = send(currency_column) || EasyRailsMoney.default_currency
|
55
|
+
|
56
|
+
if money
|
57
|
+
Money.new(money, currency)
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if single_currency?
|
65
|
+
define_method "#{column_name}=" do |value|
|
66
|
+
raise ::ArgumentError.new("only Integer or nil accepted") unless (value.kind_of?(Integer) || value.is_a?(NilClass))
|
67
|
+
|
68
|
+
send("#{money_column}=", value)
|
69
|
+
# currency is stored in a seperate common column
|
70
|
+
return Money.new(value, self.currency)
|
71
|
+
end # define_method setter
|
72
|
+
else
|
73
|
+
define_method "#{column_name}=" do |value|
|
74
|
+
raise ::ArgumentError.new("only Money or nil accepted") unless (value.kind_of?(Money) || value.is_a?(NilClass))
|
75
|
+
|
76
|
+
if value
|
77
|
+
send("#{money_column}=", value.fractional)
|
78
|
+
# it is stored in the database as a string but the Money
|
79
|
+
# object exposes it as a Symbol. so we store it as a
|
80
|
+
# String for consistency
|
81
|
+
send("#{currency_column}=", value.currency.id.to_s)
|
82
|
+
return value
|
83
|
+
else
|
84
|
+
send("#{money_column}=", nil)
|
85
|
+
send("#{currency_column}=", nil)
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
end # define_method setter
|
89
|
+
end # if single_currency?
|
90
|
+
end # def money
|
91
|
+
end # module ClassMethods
|
14
92
|
|
15
93
|
end
|
16
94
|
end
|
17
95
|
end
|
96
|
+
|
97
|
+
|
@@ -8,6 +8,11 @@ module EasyRailsMoney
|
|
8
8
|
|
9
9
|
# Configuration parameters
|
10
10
|
delegate :default_currency=, :to => :Money
|
11
|
-
|
11
|
+
|
12
|
+
def default_currency
|
13
|
+
default = Money.default_currency
|
14
|
+
return default if default.is_a? ::Money::Currency
|
15
|
+
return ::Money::Currency.new(default)
|
16
|
+
end
|
12
17
|
end
|
13
18
|
end
|