scrooge 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README +92 -0
- data/lib/scrooge.rb +91 -0
- data/lib/scrooge/active_record.rb +14 -0
- data/rails/init.rb +2 -0
- data/scrooge.gemspec +26 -0
- data/spec/scrooge_spec.rb +69 -0
- metadata +61 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
dist/*
|
data/README
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
Scrooge
|
2
|
+
=========
|
3
|
+
|
4
|
+
Make it easy to store money values in an ActiveRecord model, avoiding the
|
5
|
+
annoying floating point math. The idea is to store the monetary values as
|
6
|
+
the amount of cents in the database to avoid the math.
|
7
|
+
|
8
|
+
class Product < ActiveRecord::Base
|
9
|
+
attr_cents :price
|
10
|
+
end
|
11
|
+
|
12
|
+
product = Product.new(:price => 10.99)
|
13
|
+
product.price #=> #<Scrooge::Money @cents="1099">
|
14
|
+
|
15
|
+
The Scrooge::Money objects will act as a Numeric in all regards, so you can
|
16
|
+
do math with other numbers easily.
|
17
|
+
|
18
|
+
The `products` table should have an integer column `price_in_cents` for
|
19
|
+
this to work.
|
20
|
+
|
21
|
+
Calculations
|
22
|
+
------------
|
23
|
+
|
24
|
+
If you need aggregated calculations on your model, you can use the provided
|
25
|
+
method Numeric#as_cents, which converts a number to a Scrooge::Money instance,
|
26
|
+
assuming that the number is already the number of cents. For example:
|
27
|
+
|
28
|
+
class Sale < ActiveRecord::Base
|
29
|
+
belongs_to :product
|
30
|
+
attr_cents :value
|
31
|
+
end
|
32
|
+
|
33
|
+
class Product < ActiveRecord::Base
|
34
|
+
has_many :sales
|
35
|
+
|
36
|
+
def gross_sales
|
37
|
+
sales.sum(:value_in_cents).as_cents
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
product = Product.create
|
42
|
+
product.sales.create(:value => 20)
|
43
|
+
product.sales.create(:value => 20.5)
|
44
|
+
product.sales.create(:value => 25)
|
45
|
+
|
46
|
+
product.gross_sales #=> #<Scrooge::Money @cents="6550">
|
47
|
+
product.gross_sales == 65.5 #=> true
|
48
|
+
|
49
|
+
Why?
|
50
|
+
====
|
51
|
+
|
52
|
+
Because we only needed this, and most of the gems that do this stuff are too
|
53
|
+
damn big and have a ton of un-needed functionality.
|
54
|
+
|
55
|
+
Install
|
56
|
+
=======
|
57
|
+
|
58
|
+
gem install scrooge
|
59
|
+
|
60
|
+
Then, in order to require it, if you want the ActiveRecord helper methods:
|
61
|
+
|
62
|
+
require "scrooge/active_record"
|
63
|
+
|
64
|
+
Or:
|
65
|
+
|
66
|
+
config.gem "scrooge"
|
67
|
+
|
68
|
+
License
|
69
|
+
======
|
70
|
+
|
71
|
+
(The MIT License)
|
72
|
+
|
73
|
+
Copyright (c) 2010 Nicolas Sanguinetti, http://nicolassanguinetti.info
|
74
|
+
|
75
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
76
|
+
a copy of this software and associated documentation files (the
|
77
|
+
'Software'), to deal in the Software without restriction, including
|
78
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
79
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
80
|
+
permit persons to whom the Software is furnished to do so, subject to
|
81
|
+
the following conditions:
|
82
|
+
|
83
|
+
The above copyright notice and this permission notice shall be
|
84
|
+
included in all copies or substantial portions of the Software.
|
85
|
+
|
86
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
87
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
88
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
89
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
90
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
91
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
92
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/scrooge.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
module Scrooge
|
2
|
+
VERSION = "0.1"
|
3
|
+
|
4
|
+
class Money
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
def initialize(cents)
|
8
|
+
@cents = cents.to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_i
|
12
|
+
to_f.to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_f
|
16
|
+
to_cents / 100.0
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_money
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_cents
|
24
|
+
@cents
|
25
|
+
end
|
26
|
+
|
27
|
+
def <=>(other)
|
28
|
+
to_cents <=> other.to_money.to_cents
|
29
|
+
end
|
30
|
+
|
31
|
+
def +@
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def -@
|
36
|
+
Money.new(-to_cents)
|
37
|
+
end
|
38
|
+
|
39
|
+
def +(other)
|
40
|
+
Money.new(to_cents + other.to_money.to_cents)
|
41
|
+
end
|
42
|
+
|
43
|
+
def -(other)
|
44
|
+
self + -other
|
45
|
+
end
|
46
|
+
|
47
|
+
def *(other)
|
48
|
+
Money.new(to_cents * other)
|
49
|
+
end
|
50
|
+
|
51
|
+
def /(other)
|
52
|
+
Money.new(to_cents / other)
|
53
|
+
end
|
54
|
+
|
55
|
+
def coerce(other)
|
56
|
+
[other.to_money, self]
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(method, *args, &block)
|
60
|
+
to_f.send(method, *args, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def respond_to?(method, include_private=false)
|
64
|
+
to_f.respond_to?(method, include_private)
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
to_f.to_s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Numeric
|
74
|
+
def to_money
|
75
|
+
Scrooge::Money.new(100 * self)
|
76
|
+
end
|
77
|
+
|
78
|
+
def as_cents
|
79
|
+
Scrooge::Money.new(self)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class String
|
84
|
+
def to_money
|
85
|
+
to_f.to_money
|
86
|
+
end
|
87
|
+
|
88
|
+
def as_cents
|
89
|
+
to_i.as_cents
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "scrooge"
|
2
|
+
|
3
|
+
module Scrooge
|
4
|
+
module ActiveRecord
|
5
|
+
def attr_cents(*names)
|
6
|
+
names.each do |name|
|
7
|
+
composed_of name, :class_name => "Scrooge::Money",
|
8
|
+
:mapping => ["#{name}_in_cents", "to_cents"],
|
9
|
+
:converter => lambda {|value| (value || 0).to_money },
|
10
|
+
:constructor => lambda {|value| value.to_f.as_cents }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/rails/init.rb
ADDED
data/scrooge.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "scrooge"
|
3
|
+
s.version = "0.1"
|
4
|
+
s.date = "2010-01-19"
|
5
|
+
|
6
|
+
s.description = "Class to serialize money into the database, works out of the box with ActiveRecord"
|
7
|
+
s.summary = "Scrooge will keep all your money safe. Or at least in the database."
|
8
|
+
s.homepage = "http://github.com/foca/centipede"
|
9
|
+
|
10
|
+
s.authors = ["Nicolás Sanguinetti"]
|
11
|
+
s.email = "contacto@nicolassanguinetti.info"
|
12
|
+
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
|
16
|
+
s.files = %w[
|
17
|
+
.gitignore
|
18
|
+
README
|
19
|
+
scrooge.gemspec
|
20
|
+
lib/scrooge.rb
|
21
|
+
lib/scrooge/active_record.rb
|
22
|
+
rails/init.rb
|
23
|
+
spec/scrooge_spec.rb
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "centipede"
|
2
|
+
require "spec"
|
3
|
+
|
4
|
+
include Centipede
|
5
|
+
|
6
|
+
describe Centipede::Money do
|
7
|
+
subject { Money.new(175) }
|
8
|
+
|
9
|
+
it "can be interpreted as an integer" do
|
10
|
+
subject.to_i.should == 1
|
11
|
+
end
|
12
|
+
|
13
|
+
it "can be interpreted as a float" do
|
14
|
+
subject.to_f.should == 1.75
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can be zero" do
|
18
|
+
subject.should_not be_zero
|
19
|
+
Money.new(0).should be_zero
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "doing arithmetic operations" do
|
23
|
+
it "can add another number" do
|
24
|
+
(subject + 2.00).should == 3.75
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can be added to another number" do
|
28
|
+
(2.00 + subject).should == 3.75
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can be substracted from a number" do
|
32
|
+
(2.00 - subject).should == 0.25
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can substract a number" do
|
36
|
+
(subject - 1.00).should == 0.75
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can be multiplied by a number" do
|
40
|
+
(subject * 2.0).should == 3.50
|
41
|
+
end
|
42
|
+
|
43
|
+
it "can be divided by a number" do
|
44
|
+
(subject / 2.0).should == 0.875
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "extending core classes" do
|
49
|
+
it "allows to convert integers to money objects" do
|
50
|
+
2.to_money.should == Money.new(200)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "allows converting floats to money objects" do
|
54
|
+
2.5.to_money.should == Money.new(250)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "allows interpreting numbers as amount of cents" do
|
58
|
+
(1500.as_cents).should == 15
|
59
|
+
end
|
60
|
+
|
61
|
+
it "can interpet a string as a money value" do
|
62
|
+
"123.5".to_money.should == Money.new(12350)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "can interpet a string as a number of cents" do
|
66
|
+
"12345".as_cents.should == Money.new(12345)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scrooge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Nicol\xC3\xA1s Sanguinetti"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-19 00:00:00 -02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Class to serialize money into the database, works out of the box with ActiveRecord
|
17
|
+
email: contacto@nicolassanguinetti.info
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- README
|
27
|
+
- scrooge.gemspec
|
28
|
+
- lib/scrooge.rb
|
29
|
+
- lib/scrooge/active_record.rb
|
30
|
+
- rails/init.rb
|
31
|
+
- spec/scrooge_spec.rb
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://github.com/foca/centipede
|
34
|
+
licenses: []
|
35
|
+
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.3.5
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: Scrooge will keep all your money safe. Or at least in the database.
|
60
|
+
test_files: []
|
61
|
+
|