Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit, renaming from centipede to scrooge

  • Loading branch information...
commit 6d7a038b8b87f9bb82338c2ffe18b4b6e8a4c216 0 parents
@foca foca authored
1  .gitignore
@@ -0,0 +1 @@
+dist/*
92 README
@@ -0,0 +1,92 @@
+Scrooge
+=========
+
+Make it easy to store money values in an ActiveRecord model, avoiding the
+annoying floating point math. The idea is to store the monetary values as
+the amount of cents in the database to avoid the math.
+
+ class Product < ActiveRecord::Base
+ attr_cents :price
+ end
+
+ product = Product.new(:price => 10.99)
+ product.price #=> #<Scrooge::Money @cents="1099">
+
+The Scrooge::Money objects will act as a Numeric in all regards, so you can
+do math with other numbers easily.
+
+The `products` table should have an integer column `price_in_cents` for
+this to work.
+
+Calculations
+------------
+
+If you need aggregated calculations on your model, you can use the provided
+method Numeric#as_cents, which converts a number to a Scrooge::Money instance,
+assuming that the number is already the number of cents. For example:
+
+ class Sale < ActiveRecord::Base
+ belongs_to :product
+ attr_cents :value
+ end
+
+ class Product < ActiveRecord::Base
+ has_many :sales
+
+ def gross_sales
+ sales.sum(:value_in_cents).as_cents
+ end
+ end
+
+ product = Product.create
+ product.sales.create(:value => 20)
+ product.sales.create(:value => 20.5)
+ product.sales.create(:value => 25)
+
+ product.gross_sales #=> #<Scrooge::Money @cents="6550">
+ product.gross_sales == 65.5 #=> true
+
+Why?
+====
+
+Because we only needed this, and most of the gems that do this stuff are too
+damn big and have a ton of un-needed functionality.
+
+Install
+=======
+
+ gem install scrooge
+
+Then, in order to require it, if you want the ActiveRecord helper methods:
+
+ require "scrooge/active_record"
+
+Or:
+
+ config.gem "scrooge"
+
+License
+======
+
+(The MIT License)
+
+Copyright (c) 2010 Nicolas Sanguinetti, http://nicolassanguinetti.info
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16 Rakefile
@@ -0,0 +1,16 @@
+require "spec/rake/spectask"
+
+begin
+ require "mg"
+ MG.new("scrooge.gemspec")
+
+ task :build => :vendor
+rescue LoadError
+end
+
+desc "Default: run specs"
+task :default => :spec
+
+Spec::Rake::SpecTask.new do |task|
+ task.spec_opts << "--color"
+end
91 lib/scrooge.rb
@@ -0,0 +1,91 @@
+module Scrooge
+ VERSION = "0.1"
+
+ class Money
+ include Comparable
+
+ def initialize(cents)
+ @cents = cents.to_i
+ end
+
+ def to_i
+ to_f.to_i
+ end
+
+ def to_f
+ to_cents / 100.0
+ end
+
+ def to_money
+ self
+ end
+
+ def to_cents
+ @cents
+ end
+
+ def <=>(other)
+ to_cents <=> other.to_money.to_cents
+ end
+
+ def +@
+ self
+ end
+
+ def -@
+ Money.new(-to_cents)
+ end
+
+ def +(other)
+ Money.new(to_cents + other.to_money.to_cents)
+ end
+
+ def -(other)
+ self + -other
+ end
+
+ def *(other)
+ Money.new(to_cents * other)
+ end
+
+ def /(other)
+ Money.new(to_cents / other)
+ end
+
+ def coerce(other)
+ [other.to_money, self]
+ end
+
+ def method_missing(method, *args, &block)
+ to_f.send(method, *args, &block)
+ end
+
+ def respond_to?(method, include_private=false)
+ to_f.respond_to?(method, include_private)
+ end
+
+ def to_s
+ to_f.to_s
+ end
+ end
+end
+
+class Numeric
+ def to_money
+ Scrooge::Money.new(100 * self)
+ end
+
+ def as_cents
+ Scrooge::Money.new(self)
+ end
+end
+
+class String
+ def to_money
+ to_f.to_money
+ end
+
+ def as_cents
+ to_i.as_cents
+ end
+end
14 lib/scrooge/active_record.rb
@@ -0,0 +1,14 @@
+require "scrooge"
+
+module Scrooge
+ module ActiveRecord
+ def attr_cents(*names)
+ names.each do |name|
+ composed_of name, :class_name => "Scrooge::Money",
+ :mapping => ["#{name}_in_cents", "to_cents"],
+ :converter => lambda {|value| (value || 0).to_money },
+ :constructor => lambda {|value| value.to_f.as_cents }
+ end
+ end
+ end
+end
2  rails/init.rb
@@ -0,0 +1,2 @@
+require "scrooge/active_record"
+ActiveRecord::Base.extend Scrooge::ActiveRecord
26 scrooge.gemspec
@@ -0,0 +1,26 @@
+Gem::Specification.new do |s|
+ s.name = "scrooge"
+ s.version = "0.1"
+ s.date = "2010-01-19"
+
+ s.description = "Class to serialize money into the database, works out of the box with ActiveRecord"
+ s.summary = "Scrooge will keep all your money safe. Or at least in the database."
+ s.homepage = "http://github.com/foca/centipede"
+
+ s.authors = ["Nicolás Sanguinetti"]
+ s.email = "contacto@nicolassanguinetti.info"
+
+ s.require_paths = ["lib"]
+ s.has_rdoc = true
+
+ s.files = %w[
+.gitignore
+README
+scrooge.gemspec
+lib/scrooge.rb
+lib/scrooge/active_record.rb
+rails/init.rb
+spec/scrooge_spec.rb
+]
+end
+
69 spec/scrooge_spec.rb
@@ -0,0 +1,69 @@
+require "centipede"
+require "spec"
+
+include Centipede
+
+describe Centipede::Money do
+ subject { Money.new(175) }
+
+ it "can be interpreted as an integer" do
+ subject.to_i.should == 1
+ end
+
+ it "can be interpreted as a float" do
+ subject.to_f.should == 1.75
+ end
+
+ it "can be zero" do
+ subject.should_not be_zero
+ Money.new(0).should be_zero
+ end
+
+ describe "doing arithmetic operations" do
+ it "can add another number" do
+ (subject + 2.00).should == 3.75
+ end
+
+ it "can be added to another number" do
+ (2.00 + subject).should == 3.75
+ end
+
+ it "can be substracted from a number" do
+ (2.00 - subject).should == 0.25
+ end
+
+ it "can substract a number" do
+ (subject - 1.00).should == 0.75
+ end
+
+ it "can be multiplied by a number" do
+ (subject * 2.0).should == 3.50
+ end
+
+ it "can be divided by a number" do
+ (subject / 2.0).should == 0.875
+ end
+ end
+
+ describe "extending core classes" do
+ it "allows to convert integers to money objects" do
+ 2.to_money.should == Money.new(200)
+ end
+
+ it "allows converting floats to money objects" do
+ 2.5.to_money.should == Money.new(250)
+ end
+
+ it "allows interpreting numbers as amount of cents" do
+ (1500.as_cents).should == 15
+ end
+
+ it "can interpet a string as a money value" do
+ "123.5".to_money.should == Money.new(12350)
+ end
+
+ it "can interpet a string as a number of cents" do
+ "12345".as_cents.should == Money.new(12345)
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.