easy_rails_money 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -19,3 +19,4 @@ tmp
19
19
 
20
20
  .rvmrc
21
21
  NOTES
22
+ .rspec
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1
4
+ - Add a README listing the rationale and rough draft of the migration
5
+ DSL to be implemented
6
+ - Added migration DSL to add two columns to persist a money object.
7
+ An Interger column to persist the money amount and a String column
8
+ to persist the currency name
9
+
10
+ ## 0.0.2
11
+ - Modify migration DSL to support multiple money amount columns and a
12
+ single currency column
13
+ - add travis, codeclimate and gemnasium gem-dependency badges
14
+ - has a dependency on ActiveRecord and removed the dependency on Rails
15
+
16
+ ## 0.0.3
17
+ - add missing cases for supporting a single currency
18
+ - refactor tests
19
+ - add simplecov coverage report
20
+ - Add yard docs and update README
21
+
22
+
data/README.md CHANGED
@@ -2,14 +2,20 @@
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. Only the migration part is done now
5
+ ### Under Development
6
+
7
+ The migration helpers are functionally complete.
8
+ Working on integrating with Rails' ActiveModel
6
9
 
7
10
  # EasyRailsMoney
8
11
 
9
- This library provides integration of [money](http://github.com/Rubymoney/money) gem with Rails.
12
+ > “Young people, nowadays, imagine that money is everything.
13
+ >
14
+ > Yes, murmured Lord Henry, settling his button-hole in his coat;
15
+ > and when they grow older they know it.”
16
+ > ― Oscar Wilde, The Picture of Dorian Gray and Other Writings
10
17
 
11
- Use 'money' to specify which fields you want to be backed by a Money
12
- object
18
+ This library provides integration of [money](http://github.com/Rubymoney/money) gem with [Rails](https://github.com/rails/rails).
13
19
 
14
20
  [money-rails](https://github.com/RubyMoney/money-rails) is much more
15
21
  popular and full-featured. Definately try it out. I have actually
@@ -19,10 +25,37 @@ I have tried to create a simpler version of [money-rails](https://github.com/Rub
19
25
  With a better API and database schema, in my opinion
20
26
  I created this project to scratch my itch.
21
27
 
22
- Have stolen lots of code from [money-rails](https://github.com/RubyMoney/money-rails)
23
- API and tests are written from scratch
28
+ Please open a new issue [in the github project issues tracker](http://github.com/deepak/easy_rails_money/issues). You are also
29
+ more than welcome to contribute to the project :-)
30
+
31
+ ## Credits
32
+
33
+ Have stolen lots of code from [money-rails](https://github.com/RubyMoney/money-rails)
34
+ But database schema, API and tests are written from scratch
35
+
36
+ ## Installation
37
+
38
+ Add this line to your application's Gemfile:
39
+
40
+ gem 'easy_rails_money'
41
+
42
+ And then execute:
43
+
44
+ $ bundle
45
+
46
+ Or install it yourself as:
47
+
48
+ $ gem install easy_rails_money
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork it
53
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
54
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
55
+ 4. Push to the branch (`git push origin my-new-feature`)
56
+ 5. Create new Pull Request
24
57
 
25
- ## How it works
58
+ ## Rationale
26
59
 
27
60
  Let us say you want to store a Rupee Money object in the database
28
61
 
@@ -30,6 +63,7 @@ Let us say you want to store a Rupee Money object in the database
30
63
  principal = Money.new(100, "inr")
31
64
  ```
32
65
 
66
+ To serialize the values in the database
33
67
  Option 1:
34
68
  ```ruby
35
69
  class CreateLoan < ActiveRecord::Migration
@@ -58,8 +92,12 @@ Note that we are storing the base unit in the database. If the amount
58
92
  is in dollars we store in cents or if the amount is in Indian Rupees
59
93
  we store in paise and so on. This is done because FLoats do not have a
60
94
  accurate representation but Integers do. Can store BigDecimal as well
61
- but it is slower. This is why the Money gem stores amounts as integer
95
+ but it is slower. This is why the Money gem stores amounts as integer
96
+
62
97
  Watch [Rubyconf 2011 Float-is-legacy](http://www.confreaks.com/videos/698-rubyconf2011-float-is-legacy) for more details
98
+ and read [What Every Computer Scientist Should Know About
99
+ Floating-Point Arithmetic, by David Goldberg, published in March,
100
+ 1991](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)
63
101
 
64
102
  We have encoded the currency in the column name. I like it because
65
103
  there is no need to define another column and it is simple. But the
@@ -114,56 +152,118 @@ end
114
152
  It might be possible that we set a currency once for the whole app and
115
153
  never change it. But this seems like a nice tradeoff api-wise
116
154
 
117
- ### TODO's
118
- 1. currency is stored as a string. Integer might be better for storing in the database
119
- 2. store a snapshot of the exchange rate as well when the record was
120
- inserted or if we want to "freeze" the exchange rate per-record
155
+ ## Usage
121
156
 
122
- ## Installation
157
+ ### ActiveRecord
123
158
 
124
- Add this line to your application's Gemfile:
159
+ Only ActiveRecord is supported for now. And has been tested on
160
+ ActiveRecord 3.x
125
161
 
126
- gem 'easy_rails_money'
162
+ #### Migration helpers
127
163
 
128
- And then execute:
164
+ If you want to create a table which has some money columns, then you can use the ```money``` migration helper
129
165
 
130
- $ bundle
166
+ ```ruby
167
+ class CreateLoanWithCurrency < ActiveRecord::Migration
168
+ def change
169
+ create_table :loans, force: true do |t|
170
+ t.string :name
171
+ t.money :principal
172
+ t.money :repaid
173
+ t.money :npa
174
+ t.currency
175
+ end
176
+ end
177
+ end
178
+ ```
131
179
 
132
- Or install it yourself as:
180
+ If you want to add a money column to an existing table then you can
181
+ again use the ```money``` migration helper
133
182
 
134
- $ gem install easy_rails_money
183
+ ```ruby
184
+ class AddPrincipalToLoan < ActiveRecord::Migration
185
+ def change
186
+ change_table :loans do |t|
187
+ t.money :principal
188
+ end
189
+ end
190
+ end
191
+ ```
135
192
 
136
- ## Usage
193
+ Another option is to use ```add_money``` migration helper
194
+ It is a different DSL style, similar to ```create_table```
137
195
 
138
- ### ActiveRecord
196
+ ```ruby
197
+ class AddPrincipalToLoan < ActiveRecord::Migration
198
+ def up
199
+ add_money :loans, :principal, :repaid, :npa
200
+ end
139
201
 
140
- Only ActiveRecord is supported for now
202
+ def down
203
+ remove_money :loans, :principal, :repaid, :npa
204
+ end
205
+ end
206
+ ```
141
207
 
142
- #### Migration helpers
208
+ ```add_money``` helper is revertable, so you may use it inside ```change``` migrations.
209
+ If you writing separate ```up``` and ```down``` methods, you may use
210
+ the ```remove_money``` migration helper.
143
211
 
144
- If you want to add money field to product model you may use ```add_money``` helper
212
+ The above statements for ```money``` and ```add_money``` will create
213
+ two columns. An integer column to store the lower denomination as an
214
+ integer and a string column to store the currency name.
145
215
 
146
- ```ruby
147
- class MonetizeLoan < ActiveRecord::Migration
148
- def change
149
- add_money :loans, :principal
216
+ eg. if we say ```add_money :loans, :principal``` Then the following two
217
+ columns will be created:
218
+ 1. integer column called ```principal_money```
219
+ 2. string column called ```principal_currency```
150
220
 
151
- # OR
221
+ If we want to store ```$ 100``` in this column then:
222
+ 1. column ```principal_money``` will contain the unit in the lower denomination
223
+ ie. cents in this case. So for ```$100``` it will store ```100 * 100 => 100_000 cents```
224
+ 2. column ```principal_currency``` will store the currency name ie. ```usd```
152
225
 
153
- change_table :products do |t|
154
- t.money :principal
226
+ Both the amount and currency is needed to create a ```Money``` object
227
+
228
+ Now if we have multiple money columns, then you can choose to have a
229
+ single currency column
230
+
231
+ ```ruby
232
+ class CreateLoanWithCurrency < ActiveRecord::Migration
233
+ def change
234
+ create_table :loans, force: true do |t|
235
+ t.string :name
236
+ t.money :principal
237
+ t.money :repaid
238
+ t.money :npa
239
+ t.currency
155
240
  end
156
241
  end
157
242
  end
158
243
  ```
159
244
 
160
- ```add_money``` helper is revertable, so you may use it inside ```change``` migrations.
161
- If you writing separate ```up``` and ```down``` methods, you may use ```remove_money``` helper.
245
+ This will create a single column for currency:
246
+ 1. It creates three columns for each of the money columns
247
+ ```principal_money```, ```repaid_money``` and ```npa_money```
248
+ 2. note that it does not create a currency column for each of the
249
+ money columns. But a common currency column is created.
250
+ It is boringly enough called ```currency```
162
251
 
163
- ## Contributing
252
+ Note that columns are prefixed with ```_money``` and ```_currency```
253
+ And the common currency column is called ```currency```.
164
254
 
165
- 1. Fork it
166
- 2. Create your feature branch (`git checkout -b my-new-feature`)
167
- 3. Commit your changes (`git commit -am 'Add some feature'`)
168
- 4. Push to the branch (`git push origin my-new-feature`)
169
- 5. Create new Pull Request
255
+ It is used to reflect on the database schema ie. to find out the
256
+ money and currency columns defined.
257
+ Right now, none of these choices are customizable.
258
+
259
+ ## TODO's
260
+ 1. Proof-read docs
261
+ 2. currency is stored as a string. Integer might be better for storing in the database
262
+ 3. store a snapshot of the exchange rate as well when the record was
263
+ inserted or if we want to "freeze" the exchange rate per-record
264
+ 4. specs for migration test the same thing in multiple ways. have a
265
+ spec helper
266
+ 5. add Gemfil to test on ActiveRecord 4.x ie. with Rails4 . Add to travis.yml as well
267
+ 6. configure the ```_money``` and ```_currency``` prefix and the name
268
+ of the common ```currency``` column
269
+ 7. check specs tagged as "fixme"
data/Rakefile CHANGED
@@ -5,3 +5,12 @@ RSpec::Core::RakeTask.new('spec')
5
5
 
6
6
  # If you want to make this the default task
7
7
  task :default => :spec
8
+
9
+ begin
10
+ require 'yard'
11
+ YARD::Rake::YardocTask.new do |t|
12
+ t.files = ['lib/**/*.rb'] # optional
13
+ t.options = ['--one-file']
14
+ end
15
+ rescue LoadError
16
+ end
@@ -21,8 +21,19 @@ Gem::Specification.new do |gem|
21
21
  gem.add_dependency "activesupport", "~> 3.2"
22
22
 
23
23
  gem.add_development_dependency "rake", "~> 10.0.4"
24
+
25
+ # for running tests
24
26
  gem.add_development_dependency "rspec", "~> 2.12"
25
- gem.add_development_dependency "activerecord", "~> 3.2"
27
+ # we use an in-memory sqlite database for speed
26
28
  gem.add_development_dependency "sqlite3", "~> 1.3.7"
29
+ # testing against the ActiveRecord interface
30
+ gem.add_development_dependency "activerecord", "~> 3.2"
31
+
27
32
  gem.add_development_dependency "debugger", "~> 1.5.0"
33
+ gem.add_development_dependency "simplecov", "~> 0.7.1"
34
+
35
+ # for generating docs
36
+ gem.add_development_dependency "yard", "~> 0.8.5.2"
37
+ # needed by YARD to read markdown files
38
+ gem.add_development_dependency "redcarpet", "~> 2.2.2"
28
39
  end
@@ -1,7 +1,15 @@
1
+ # -*- coding: utf-8 -*-
2
+
1
3
  require "money"
2
4
  require "easy_rails_money/version"
3
5
  require "easy_rails_money/configuration"
4
6
 
7
+ # @author Deepak Kannan
8
+ # “Young people, nowadays, imagine that money is everything.
9
+ #
10
+ # Yes, murmured Lord Henry, settling his button-hole in his coat; and when they grow older they know it.”
11
+ # ― Oscar Wilde, The Picture of Dorian Gray and Other Writings
12
+ # This library provides integration of [money](http://github.com/Rubymoney/money) gem with [Rails](https://github.com/rails/rails).
5
13
  module EasyRailsMoney
6
14
  extend Configuration
7
15
  end
@@ -2,18 +2,104 @@ module EasyRailsMoney
2
2
  module ActiveRecord
3
3
  module Migration
4
4
  module SchemaStatements
5
- def add_money(table_name, *columns)
6
- columns.each do |name|
5
+ # Creates one or two columns to represent a Money object in the named table
6
+ #
7
+ # An integer column for storing Money in its base unit
8
+ # eg. cents for a Dollar denomination and a string for storing
9
+ # its currency name. Can think of it as a persisted or serialized
10
+ # Money object in the database. The integer column is suffixed
11
+ # with '_money' to aid in reflection ie. to find all money
12
+ # columns. Likewise the currency column is suffixed with '_currency'
13
+ # If does not create an individual currency column if a
14
+ # common currency column is defined
15
+ #
16
+ # @param table_name [Symbol|String]
17
+ # @param column_names [Array|Symbol|String] List of money columns to add
18
+ # @return is not important and can change
19
+ #
20
+ # @note If we have defined a currency column for a record then only the integer column is defined.
21
+ # @see EasyRailsMoney::ActiveRecord::Migration::TableDefinition#money
22
+ # @see EasyRailsMoney::ActiveRecord::Migration::Table#money
23
+ # @see EasyRailsMoney::ActiveRecord::Migration::TableDefinition#currency
24
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#remove_money
25
+ def add_money table_name, *column_names
26
+ column_names.each do |name|
7
27
  add_column table_name, "#{name}_money", :integer
8
- add_column table_name, "#{name}_currency", :string
28
+ add_column table_name, "#{name}_currency", :string unless has_currency_column?(table_name)
9
29
  end
10
30
  end
11
31
 
12
- def remove_money(table_name, *columns)
13
- columns.each do |name|
32
+ # Removes the columns which represent the Money object
33
+ #
34
+ # Removes the two columns added by add_money. The money amount and
35
+ # the currency column. If there are no remaining money amount columns
36
+ # and a common currency column exists. then it is also removed
37
+ #
38
+ # @param table_name [Symbol|String]
39
+ # @param column_names [Array|Symbol|String] List of money columns to remove
40
+ # @return is not important and can change
41
+ # @note If we have defined a currency column for a record then currency column is removed only if no other money column are there
42
+ #
43
+ # @see EasyRailsMoney::ActiveRecord::Migration::Table#remove_money
44
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#add_money
45
+ # @see EasyRailsMoney::ActiveRecord::Migration::TableDefinition#currency
46
+ # @note multiple remove_money calls are not supported in one migration. because at this point the schema is different from the migration defined
47
+ def remove_money table_name, *column_names
48
+ column_names.each do |name|
14
49
  remove_column table_name, "#{name}_money"
15
50
  remove_column table_name, "#{name}_currency"
16
51
  end
52
+ remove_currency table_name unless has_money_columns?(table_name)
53
+ end
54
+
55
+ # Removes the common currency column
56
+ #
57
+ # Usually we create an currency column for each money
58
+ # columns. We can have multiple money columns in the same
59
+ # record, in which case we can have a single currency
60
+ # column. This helper removes that common curremcy column. For
61
+ # the existing money column it adds back their currency
62
+ # columns as well. It reflects on the database schema by
63
+ # looking at the column name. By convention the money amount
64
+ # column is prefixed by '_money' and the currency column by '_currency'
65
+ #
66
+ # @param table_name [Symbol|String]
67
+ #
68
+ # @see EasyRailsMoney::ActiveRecord::Migration::Table#remove_currency
69
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#remove_money
70
+ # @see EasyRailsMoney::ActiveRecord::Migration::TableDefinition#currency
71
+ def remove_currency table_name
72
+ remove_column table_name, :currency
73
+ money_columns(table_name) do |money_column|
74
+ add_column table_name, "#{money_column}_currency", :string
75
+ end
76
+ end
77
+
78
+ protected
79
+ def has_currency_column? table_name
80
+ connection.schema_cache.clear_table_cache! table_name
81
+ connection.schema_cache.columns[table_name].select {|x| x.name == "currency" }.any?
82
+ end
83
+
84
+ def money_columns table_name
85
+ # FIXME: see specs tagged
86
+ # fixme_need_to_clear_table_cache. for that test needed to
87
+ # clear the cache
88
+ connection.schema_cache.clear_table_cache! table_name
89
+ connection.schema_cache.columns[table_name].select { |col|
90
+ col.name =~ /_money/
91
+ }.map { |col|
92
+ name = col.name.match(/(.+)_money/)[1]
93
+ if block_given?
94
+ yield name
95
+ else
96
+ name
97
+ end
98
+ }
99
+ end
100
+
101
+ def has_money_columns? table_name
102
+ money_columns(table_name).any?
17
103
  end
18
104
  end
19
105
  end
@@ -5,27 +5,80 @@ module EasyRailsMoney
5
5
  # called for change_table
6
6
  # currency and #money defined in TableDefinition
7
7
 
8
+ # Creates one or two columns to represent a Money object in the named table
9
+ #
10
+ # An integer column for storing Money in its base unit
11
+ # eg. cents for a Dollar denomination and a string for storing
12
+ # its currency name. Can think of it as a persisted or serialized
13
+ # Money object in the database. The integer column is suffixed
14
+ # with '_money' to aid in reflection ie. to find all money
15
+ # columns. Likewise the currency column is suffixed with '_currency'
16
+ # If does not create an individual currency column if a
17
+ # common currency column is defined
18
+ #
19
+ # @param column_names [Array|Symbol|String] List of money columns to add
20
+ # @return is not important and can change
21
+ #
22
+ # @note If we have defined a currency column for a record then only the integer column is defined.
23
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#add_money
24
+ # @see EasyRailsMoney::ActiveRecord::Migration::TableDefinition#money
25
+ # @see EasyRailsMoney::ActiveRecord::Migration::TableDefinition#currency
26
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#remove_money
8
27
  def money(*column_names)
9
28
  column_names.each do |name|
10
- column "#{name}_money", :integer
11
- unless columns.select { |x| x.name == "currency" }.any?
12
- column "#{name}_currency", :string
13
- end
29
+ column "#{name}_money", :integer
30
+ column "#{name}_currency", :string unless has_currency_column?
14
31
  end
15
32
  end
16
33
 
17
- def currency
18
- remove_currency_columns
19
- column :currency, :string
20
- end
21
-
34
+ # Removes the columns which represent the Money object
35
+ #
36
+ # Removes the two columns added by money. The money amount and
37
+ # the currency column. If there are no remaining money amount columns
38
+ # and a common currency column exists. then it is also removed
39
+ #
40
+ # @param column_names [Array|Symbol|String] List of money columns to remove
41
+ # @return is not important and can change
42
+ #
43
+ # @note If we have defined a currency column for a record then currency column is removed only if no other money column are there
44
+ #
45
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#remove_money
46
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#add_money
47
+ # @see EasyRailsMoney::ActiveRecord::Migration::TableDefinition#currency
48
+ # @note multiple remove_money calls are not supported in one migration. because at this point the schema is different from the migration defined
22
49
  def remove_money(*column_names)
23
50
  column_names.each do |name|
24
51
  remove "#{name}_money"
25
52
  remove "#{name}_currency"
26
53
  end
54
+ remove_currency unless has_money_columns?
27
55
  end
28
56
 
57
+ # Add a common currency column
58
+ #
59
+ # @return is not important and can change
60
+ #
61
+ # Add a common currency column and remove the individual
62
+ # currrency columns if they exist
63
+ def currency
64
+ remove_currency_columns
65
+ column :currency, :string
66
+ end
67
+
68
+ # Removes the common currency column
69
+ #
70
+ # Usually we create an currency column for each money
71
+ # columns. We can have multiple money columns in the same
72
+ # record, in which case we can have a single currency
73
+ # column. This helper removes that common curremcy column. For
74
+ # the existing money column it adds back their currency
75
+ # columns as well. It reflects on the database schema by
76
+ # looking at the column name. By convention the money amount
77
+ # column is prefixed by '_money' and the currency column by '_currency'
78
+ #
79
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#remove_currency
80
+ # @see EasyRailsMoney::ActiveRecord::Migration::SchemaStatements#remove_money
81
+ # @see EasyRailsMoney::ActiveRecord::Migration::TableDefinition#currency
29
82
  def remove_currency
30
83
  remove :currency
31
84
  money_columns do |money_column|
@@ -33,26 +86,38 @@ module EasyRailsMoney
33
86
  end
34
87
  end
35
88
 
89
+ protected
36
90
  def remove_currency_columns
37
91
  money_columns do |money_column|
38
92
  remove "#{money_column}_currency"
39
93
  end
40
94
  end
41
-
42
- protected
95
+
43
96
  def columns
44
- @base.schema_cache.columns[@table_name]
97
+ # @base.schema_cache.clear_table_cache! @table_name
98
+ @base.schema_cache.columns[@table_name].map { |x| x.name }
99
+ end
100
+
101
+ def has_currency_column?
102
+ columns.select { |x| x == "currency" }.any?
45
103
  end
46
104
 
47
105
  def money_columns
48
- columns.select do |col|
49
- col.name =~ /_money/
50
- end.map do |col|
51
- money_column = col.name.match(/(.+)_money/)[1]
52
- yield money_column
53
- end
106
+ columns.select { |col|
107
+ col =~ /_money/
108
+ }.map { |col|
109
+ name = col.match(/(.+)_money/)[1]
110
+ if block_given?
111
+ yield name
112
+ else
113
+ name
114
+ end
115
+ }
54
116
  end
55
117
 
118
+ def has_money_columns?
119
+ money_columns.any?
120
+ end
56
121
  end
57
122
  end
58
123
  end