easy_rails_money 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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
- doc/
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
  [![Dependency Status](https://gemnasium.com/deepak/easy_rails_money.png)](https://gemnasium.com/deepak/easy_rails_money)
3
3
  [![Code Climate](https://codeclimate.com/github/deepak/easy_rails_money.png)](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
- [money-rails](https://github.com/RubyMoney/money-rails) is much more
21
- popular and full-featured. Definately try it out. I have actually
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
- I have tried to create a simpler version of [money-rails](https://github.com/RubyMoney/money-rails)
25
- With a better API and database schema, in my opinion
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 :principal
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 and changing the column name in
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 :principal
125
+ t.integer :principal_money
125
126
  t.string :principal_currency
126
- t.integer :repaid
127
+ t.integer :repaid_money
127
128
  t.string :repaid_currency
128
- t.integer :npa
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 :principal
145
- t.integer :repaid
146
- t.integer :npa
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 'active_support/core_ext/array/extract_options'
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
- def money(field, *args)
11
- options = args.extract_options!
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
- end
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
- delegate :default_currency, :to => :Money
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
@@ -0,0 +1,9 @@
1
+ module EasyRailsMoney
2
+ module MoneyDslHelper
3
+ def to_currency currency
4
+ return currency if currency.is_a? ::Money::Currency
5
+ ::Money::Currency.new(currency)
6
+ end
7
+ module_function :to_currency
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module EasyRailsMoney
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end