money-collection 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 31d48e73a143672af436d2128462573a8f9b7d70
4
+ data.tar.gz: 2c8528a4f6163392762d25ddea387eb6649d860a
5
+ SHA512:
6
+ metadata.gz: 9027fda3114e23db23061ab4a88d31e071a89d690a11c4de994c0c8b6517d6a41f5e28aa1620dff27285be12aac433612e95871ff3609d6bc457aeb82d358603
7
+ data.tar.gz: 85632bb6ae43b0eac67779762a35909c1d00894ba67c51cb727413f78f977538d3349d1af189c1094e5f2822c55390dfefe098bf2f0dd3b34eedf5bdb3bb3313
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in money-collection.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 lulalala
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,56 @@
1
+ # Money::Collection
2
+
3
+ Calculating sum/min/max on a bunch of Money objects, with __accuracy__ and __efficiency__.
4
+
5
+ This is achieved by minimizing number of currency conversions and object creations.
6
+
7
+ For large collections, sum/min/max can perform up to 4 times as fast. For small collections (n<10), it can perform at least 2 times as fast.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'money-collection'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install money-collection
24
+
25
+ ## Usage
26
+
27
+ Here is the demo of the Money::Collection library:
28
+
29
+ ```ruby
30
+ require 'money'
31
+ require 'money/collection'
32
+
33
+ c = Money::Collection.new( [money1, money2] )
34
+ c << money3
35
+ c.concat [money4, money5]
36
+
37
+ c.sum
38
+ c.sum('TWD') # returns sum in TWD currency.
39
+ c.max
40
+ c.min
41
+ ```
42
+
43
+ For more, please check the API documentation.
44
+
45
+ ## Contributing
46
+
47
+ The followings areas can use some of your expertise:
48
+
49
+ * Benchmarking, including comparison between commits.
50
+ * Accuracy improvement
51
+ * Optimization
52
+
53
+ ## License
54
+
55
+ MIT License (See LICENSE.txt)
56
+
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push "lib"
6
+ t.libs.push "spec"
7
+ t.test_files = FileList['spec/**/*_spec.rb']
8
+ t.verbose = true
9
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'bundler'
5
+ rescue LoadError => e
6
+ STDERR.puts e.message
7
+ STDERR.puts "Run `gem install bundler` to install Bundler."
8
+ exit e.status_code
9
+ end
10
+
11
+ begin
12
+ Bundler.setup(:default, :development, :test)
13
+ rescue Bundler::BundlerError => e
14
+ STDERR.puts e.message
15
+ STDERR.puts "Run `bundle install` to install missing gems."
16
+ exit e.status_code
17
+ end
@@ -0,0 +1,47 @@
1
+ require_relative 'helper'
2
+ require 'benchmark'
3
+ require 'money'
4
+ require 'money/collection'
5
+
6
+ Money.add_rate(:usd, :twd, 30.4071518)
7
+ Money.add_rate(:twd, :usd, 0.032887)
8
+
9
+ def generate_samples(size, possible_currencies = [:usd])
10
+ size.times.map do
11
+ Money.new(Random.rand(100000), possible_currencies.sample)
12
+ end
13
+ end
14
+
15
+ benchmarking_sizes = [1,2,5,10,50,100,200,400,600,800,1000]
16
+
17
+ puts 'Single currency'
18
+
19
+ samples = generate_samples(benchmarking_sizes.max)
20
+ # rehearsal
21
+ 100.times{ Money::Collection.new(samples).sum }
22
+ Benchmark.bm do |x|
23
+ benchmarking_sizes.each do |size|
24
+ x.report "sum #{size} items" do
25
+ sliced_samples = samples.first(size)
26
+ 10000.times do
27
+ Money::Collection.new(sliced_samples).max
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ puts 'Multiple currencies'
34
+
35
+ samples = generate_samples(benchmarking_sizes.max, [:usd, :twd])
36
+ # rehearsal
37
+ 100.times{ Money::Collection.new(samples).sum }
38
+ Benchmark.bm do |x|
39
+ benchmarking_sizes.each do |size|
40
+ x.report "sum #{size} items" do
41
+ sliced_samples = samples.first(size)
42
+ 10000.times do
43
+ Money::Collection.new(sliced_samples).max
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ require_relative 'helper'
2
+ require 'benchmark'
3
+ require 'money'
4
+ require 'money/collection'
5
+
6
+ Money.add_rate(:usd, :twd, 30.4071518)
7
+ Money.add_rate(:twd, :usd, 0.032887)
8
+
9
+ def generate_samples(size, possible_currencies = [:usd])
10
+ size.times.map do
11
+ Money.new(Random.rand(100000), possible_currencies.sample)
12
+ end
13
+ end
14
+
15
+ benchmarking_sizes = [1,2,5,10,50,100,200,400,600,800,1000]
16
+
17
+ puts 'Single currency'
18
+
19
+ samples = generate_samples(benchmarking_sizes.max)
20
+ # rehearsal
21
+ 100.times{ Money::Collection.new(samples).sum }
22
+ Benchmark.bm do |x|
23
+ benchmarking_sizes.each do |size|
24
+ x.report "sum #{size} items" do
25
+ sliced_samples = samples.first(size)
26
+ 10000.times do
27
+ Money::Collection.new(sliced_samples).sum
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ puts 'Multiple currencies'
34
+
35
+ samples = generate_samples(benchmarking_sizes.max, [:usd, :twd])
36
+ # rehearsal
37
+ 100.times{ Money::Collection.new(samples).sum }
38
+ Benchmark.bm do |x|
39
+ benchmarking_sizes.each do |size|
40
+ x.report "sum #{size} items" do
41
+ sliced_samples = samples.first(size)
42
+ 10000.times do
43
+ Money::Collection.new(sliced_samples).sum
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,125 @@
1
+ require "money/collection/version"
2
+
3
+ class Money
4
+ class Collection
5
+ include Enumerable
6
+
7
+ # @parma array [#to_a] collection of Money objects
8
+ def initialize(array = nil)
9
+ @collection = array.to_a.dup
10
+ @group_by_currency = @collection.group_by(&:currency)
11
+ end
12
+
13
+ # Sums up Money objects in collection.
14
+ # @param target_currency [Currency, String, Symbol] - currency of the returning money object.
15
+ # @return [Money] sum of Money collection.
16
+ def sum(target_currency = nil)
17
+ if @collection.empty?
18
+ return Money.new(0, target_currency)
19
+ end
20
+
21
+ if @group_by_currency.size == 1
22
+ sum = self.class.sum_single_currency(@collection)
23
+ else
24
+ sums_per_currency = @group_by_currency.values.map{|moneys|
25
+ self.class.sum_single_currency(moneys)
26
+ }
27
+
28
+ # If target_currency is specified, and is in collection,
29
+ # move it to the front so it has precedence over other currencies.
30
+ if target_currency
31
+ target_currency = self.class.convert_to_currency_object(target_currency)
32
+ if index = sums_per_currency.find_index{|money| money.currency == target_currency}
33
+ money = sums_per_currency.delete_at(index)
34
+ sums_per_currency.unshift money
35
+ end
36
+ end
37
+
38
+ sum = self.class.sum_basic(sums_per_currency)
39
+ end
40
+
41
+ if target_currency.nil?
42
+ return sum
43
+ else
44
+ return sum.exchange_to(target_currency)
45
+ end
46
+ end
47
+
48
+ def max
49
+ @group_by_currency.values.map{|moneys|
50
+ moneys.max_by{|money| money.fractional}
51
+ }.max
52
+ end
53
+
54
+ def min
55
+ @group_by_currency.values.map{|moneys|
56
+ moneys.min_by{|money| money.fractional}
57
+ }.min
58
+ end
59
+
60
+ #### delegations
61
+
62
+ def each
63
+ if block_given?
64
+ @collection.each{|x| yield(x)}
65
+ return self
66
+ else
67
+ return @collection.each
68
+ end
69
+ end
70
+
71
+ def <<(obj)
72
+ @collection << obj
73
+ if @group_by_currency[obj.currency].nil?
74
+ @group_by_currency[obj.currency] = [obj]
75
+ else
76
+ @group_by_currency[obj.currency] << obj
77
+ end
78
+ self
79
+ end
80
+
81
+ def concat(other_ary)
82
+ @collection.concat(other_ary)
83
+
84
+ other_ary.group_by(&:currency).each do |currency, ary|
85
+ if @group_by_currency[currency].nil?
86
+ @group_by_currency[currency] = ary
87
+ else
88
+ @group_by_currency[currency].concat(ary)
89
+ end
90
+ end
91
+ self
92
+ end
93
+
94
+ def size
95
+ @collection.size
96
+ end
97
+
98
+ private
99
+
100
+ # Sums up Money objects using built-in :+ method.
101
+ # @param moneys [Enumerable<Money>] list of Moneys.
102
+ # @return [Money] sum of Money collection.
103
+ def self.sum_basic(moneys)
104
+ moneys.reduce{|total, money| total + money }
105
+ end
106
+
107
+ # Sums up Money objects of the same currency.
108
+ # Number of object creation is minimized.
109
+ # @param moneys [Enumerable<Money>] list of Moneys. There is no validation so caller must ensure all Moneys belong to the same currency.
110
+ # @return [Money] sum of Money collection.
111
+ def self.sum_single_currency(moneys)
112
+ total_fractional = moneys.reduce(0){|fractional, money| fractional += money.fractional }
113
+ Money.new(total_fractional, moneys[0].currency)
114
+ end
115
+
116
+ def self.convert_to_currency_object(currency)
117
+ case currency
118
+ when String, Symbol
119
+ ::Money::Currency.new(currency)
120
+ when ::Money::Currency
121
+ currency
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,5 @@
1
+ class Money
2
+ class Collection
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'money/collection/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "money-collection"
8
+ spec.version = Money::Collection::VERSION
9
+ spec.authors = ["lulalala"]
10
+ spec.email = ["mark@goodlife.tw"]
11
+ spec.summary = %q{Optimized operation on collection of Money objects}
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/lulalala/money-collection"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "money", "~> 6.3"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ end
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'bundler'
5
+ rescue LoadError => e
6
+ STDERR.puts e.message
7
+ STDERR.puts "Run `gem install bundler` to install Bundler."
8
+ exit e.status_code
9
+ end
10
+
11
+ begin
12
+ Bundler.setup(:default, :development, :test)
13
+ rescue Bundler::BundlerError => e
14
+ STDERR.puts e.message
15
+ STDERR.puts "Run `bundle install` to install missing gems."
16
+ exit e.status_code
17
+ end
18
+
19
+ require 'minitest/spec'
20
+ require 'minitest/autorun'
21
+
@@ -0,0 +1,211 @@
1
+ require 'helper'
2
+ require 'money'
3
+ require 'money/collection'
4
+
5
+ describe Money::Collection do
6
+ before do
7
+ foo = {
8
+ :iso_code => "FOO",
9
+ :subunit_to_unit => 100,
10
+ }
11
+ Money::Currency.register(foo)
12
+
13
+ Money.add_rate("FOO", "USD", 0.5)
14
+ Money.add_rate("USD", "FOO", 2)
15
+ end
16
+
17
+ it 'has version' do
18
+ Money::Collection.const_get('VERSION').wont_be_empty
19
+ end
20
+
21
+ describe '#sum' do
22
+
23
+ it 'sums with no element' do
24
+ c = Money::Collection.new
25
+ c.sum.must_equal Money.new(0)
26
+ end
27
+
28
+ it 'sums with single element' do
29
+ c = Money::Collection.new
30
+ c << Money.new(10,:usd)
31
+ c.sum.must_equal Money.new(10,:usd)
32
+ end
33
+
34
+ it 'sums same currency' do
35
+ ary = [
36
+ Money.new(10,:usd),
37
+ Money.new(20,:usd),
38
+ Money.new(30,:usd),
39
+ ]
40
+
41
+ c = Money::Collection.new(ary)
42
+
43
+ c.sum.must_equal Money.new(60,:usd)
44
+ end
45
+
46
+ it 'sums different currencies' do
47
+ ary = [
48
+ Money.new(100,:usd),
49
+ Money.new(10,:foo),
50
+ ]
51
+
52
+ c = Money::Collection.new(ary)
53
+
54
+ c.sum.must_equal normal_sum(ary)
55
+ end
56
+
57
+ it 'sums correctly, avoiding rounding down error' do
58
+ ary = [
59
+ Money.new(10,:usd),
60
+ Money.new(1,:foo),
61
+ Money.new(1,:foo),
62
+ ]
63
+
64
+ c = Money::Collection.new(ary)
65
+
66
+ c.sum.must_equal Money.new(11,:usd)
67
+ end
68
+
69
+ it 'sums correctly, avoiding rounding twice if possible' do
70
+ ary = [
71
+ Money.new(10,:usd),
72
+ Money.new(1,:foo),
73
+ ]
74
+
75
+ c = Money::Collection.new(ary)
76
+
77
+ c.sum(:foo).must_equal Money.new(21,:foo)
78
+ end
79
+
80
+ it 'returns sum in the specified currency' do
81
+ ary = [
82
+ Money.new(10,:usd),
83
+ Money.new(2,:foo),
84
+ ]
85
+
86
+ c = Money::Collection.new(ary)
87
+
88
+ c.sum('foo').must_equal Money.new(22,:foo)
89
+ c.sum('usd').must_equal Money.new(11,:usd)
90
+ end
91
+
92
+ it 'returns sum large number of Money' do
93
+ 10.times do
94
+ # force first bunch to be FOO, the a bunch of USD,
95
+ # so there won't be currency conversion error even for native sum method
96
+ ary = 1000.times.map do
97
+ Money.new(Random.rand(100000), :foo)
98
+ end
99
+ ary.concat(
100
+ 1000.times.map do
101
+ Money.new(Random.rand(100000), :usd)
102
+ end
103
+ )
104
+
105
+ c = Money::Collection.new(ary)
106
+ c.sum('foo').must_equal normal_sum(ary)
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '#min' do
112
+ it 'returns accurate smallest Money object' do
113
+ ary = [
114
+ Money.new(30,:foo),
115
+ Money.new(1,:usd),
116
+ Money.new(3,:foo),
117
+ ]
118
+
119
+ c = Money::Collection.new(ary)
120
+ c.min.must_equal Money.new(1,:usd)
121
+ end
122
+ it 'returns smallest Money objects (from many items)' do
123
+ ary = 1000.times.map do
124
+ Money.new(Random.rand(100000) + 10, :foo)
125
+ end
126
+ c = Money::Collection.new(ary)
127
+ min = Money.new(1, :usd)
128
+ c << min
129
+ c.min.must_equal min
130
+ end
131
+ end
132
+
133
+ describe '#max' do
134
+ it 'returns accurate biggest Money object' do
135
+ ary = [
136
+ Money.new(30,:foo),
137
+ Money.new(1,:usd),
138
+ Money.new(3,:foo),
139
+ ]
140
+
141
+ c = Money::Collection.new(ary)
142
+ c.max.must_equal Money.new(30,:foo)
143
+ end
144
+ it 'returns biggest Money objects (from many items)' do
145
+ ary = 1000.times.map do
146
+ Money.new(Random.rand(100000), :foo)
147
+ end
148
+ c = Money::Collection.new(ary)
149
+ max = Money.new(100001, :usd)
150
+ c << max
151
+ c.max.must_equal max
152
+ end
153
+ end
154
+
155
+ describe '#concat' do
156
+ it 'concats Money objects to collection' do
157
+ ary = [
158
+ Money.new(10,:usd),
159
+ Money.new(2,:foo),
160
+ ]
161
+
162
+ c = Money::Collection.new
163
+ c.concat ary
164
+
165
+ c.size.must_equal 2
166
+ c.sum('foo').must_equal Money.new(22,:foo)
167
+ end
168
+ it 'concats Money objects to collection multiple times' do
169
+ ary = [
170
+ Money.new(10,:usd),
171
+ Money.new(2,:foo),
172
+ ]
173
+
174
+ c = Money::Collection.new
175
+ c.concat ary
176
+ c.concat ary
177
+
178
+ c.size.must_equal 4
179
+ c.sum('foo').must_equal Money.new(44,:foo)
180
+ end
181
+ end
182
+
183
+ describe '#<<' do
184
+ it 'concats Money object to collection multiple times' do
185
+ c = Money::Collection.new
186
+ c << Money.new(10,:usd)
187
+ c << Money.new(10,:foo)
188
+ c << Money.new(10,:usd)
189
+
190
+ c.sum.must_equal Money.new(25,:usd)
191
+ end
192
+ end
193
+
194
+ describe 'no side effect' do
195
+ it 'does not change original array that is passed in the initialize method' do
196
+ m1 = Money.new(10,:usd)
197
+ ary = [m1]
198
+
199
+ c = Money::Collection.new(ary)
200
+ c << Money.new(1,:usd)
201
+
202
+ ary.size.must_equal 1
203
+ ary[0].must_equal m1
204
+ end
205
+ end
206
+ end
207
+
208
+ # Adds up array of Money one by one and returns the sum.
209
+ def normal_sum(array)
210
+ array.reduce{|total, money| total + money }
211
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: money-collection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - lulalala
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: money
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Optimized operation on collection of Money objects
56
+ email:
57
+ - mark@goodlife.tw
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - benchmark/helper.rb
68
+ - benchmark/max.rb
69
+ - benchmark/sum.rb
70
+ - lib/money/collection.rb
71
+ - lib/money/collection/version.rb
72
+ - money-collection.gemspec
73
+ - spec/helper.rb
74
+ - spec/money/collection_spec.rb
75
+ homepage: https://github.com/lulalala/money-collection
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.2.2
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Optimized operation on collection of Money objects
99
+ test_files:
100
+ - spec/helper.rb
101
+ - spec/money/collection_spec.rb
102
+ has_rdoc: