Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task 343 internal decimal object instead of delegating #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ gemspec
group :test do
gem 'simplecov'
end

group :development, :test do
gem 'pry'
end
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DecimalDollars

TODO: Write a gem description
Money representation that uses decimal math.

## Installation

Expand All @@ -20,6 +20,14 @@ Or install it yourself as:

TODO: Write usage instructions here

## To test locally

$ rake build
$ rake install
$ irb

> require 'decimal_dollars'

## Contributing

1. Fork it ( http://github.com/<my-github-username>/decimal_dollars/fork )
Expand Down
4 changes: 2 additions & 2 deletions lib/decimal_dollars.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require __dir__ << "/decimal_dollars/money"
require __dir__ << "/decimal_dollars/version"
require "decimal_dollars/money"
require "decimal_dollars/version"

# :undoc
module DecimalDollars
Expand Down
65 changes: 30 additions & 35 deletions lib/decimal_dollars/money.rb
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
require "bigdecimal"
require "delegate"
require "bigdecimal/util"

require "decimal_dollars/money/arithmetic"

module DecimalDollars
# The Money class is designed to handle the money representation of a value.
# For the most part, it acts like a BigDecimal.
class Money < DelegateClass ::BigDecimal
# Create new Money instance
# @param [Object] value
# return [DecimalDollars::Money]
def initialize(value)
super(round_float(value.to_f))
end

# Define standard operators to return Money objects.
%w(+ -).each do |method|
define_method method do |value|
Money.new(super(Money.new(value)))
end
end
class Money
include DecimalDollars::Money::Arithmetic

# Divide a Money object by another object.
# @param [Object] value
# Create new Money instance
# @param [Object] obj
# @return [DecimalDollars::Money]
def /(value)
Money.new(super(value))
def initialize(obj)
@decimal = if obj.respond_to?(:to_d)
obj.to_d
else
BigDecimal(obj.to_s)
end
end

# Multiply a Money object by a scalar.
# @param [Object] value
# @raise DecimalDollars::Money::InvalidValue if a Money object is passed.
def *(value)
raise ArgumentError, 'Money cannot be multiplied by Money' if value.is_a?(Money)
Money.new(super(value))
# Make Money instance handle the operations when arguments order is reversed.
# @return [Array]
def coerce(value)
[self, value]
end

# Stub original ** method.
# @param [Object] power
# @raise DecimalDollars::Money::InvalidValue as
def **(power)
raise RuntimeError, 'Money cannot be raised to any power'
# Return decimal representation.
# @return [BigDecimal]
def to_d
@decimal
end

# Round the passed float value using the most basic rounding method.
# @param [Float] value
# Return float representation.
# @return [Float]
def round_float(value)
(value * 100).round / 100.0
def to_f
@decimal.to_f
end

# Return string representation.
# @return [String]
def to_s
@decimal.to_s
end
private :round_float
end
end
65 changes: 65 additions & 0 deletions lib/decimal_dollars/money/arithmetic.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module DecimalDollars
class Money
# Arithmetic operations for Money.
module Arithmetic

# Return a negative representation.
# @return [Money]
def -@
Money.new(-@decimal)
end

# Check if two Money objects are equal.
# @param [DecimalDollars::Money] value
# @return [Boolean]
# @raise [ArgumentError] If +value+ is NOT a Money.
def ==(value)
raise ArgumentError, "A money cannot be compared with a #{value.class.name}." unless value.is_a?(Money)
@decimal == value.to_d
end

# Synonymous with +#==+.
# @param [DecimalDollars::Money] value
# @return [Boolean]
# @see #==
def eql?(value)
self == value
end

# Add a Money object to the current.
# @param [DecimalDollars::Money] value +Money+ object to add
# @return [DecimalDollars::Money]
def +(value)
Money.new(@decimal + Money.new(value).to_d)
end

# Subtract a Money object from the current.
# @param [DecimalDollars::Money] value A +Money+ object to add
# @return [DecimalDollars::Money]
def -(value)
Money.new(@decimal - Money.new(value).to_d)
end

# Multiply a Money object by a numeric.
# @param [Numeric] value
# @raise [ArgumentError] If +value+ is NOT a number.
def *(value)
raise ArgumentError, "A money cannot be multiplied by a #{value.class.name}." unless value.is_a?(Numeric)
Money.new(@decimal * value)
end

# Divide a Money object by another object.
# @param [Numeric] value
# @return [Float] When +value+ is a Money.
# @return [DecimalDollars::Money] When +value+ is NOT a Money.
def /(value)
if value.is_a?(Money)
(@decimal / value.to_d).to_f
else
Money.new(@decimal / Money.new(value).to_d)
end
end

end
end
end
2 changes: 1 addition & 1 deletion lib/decimal_dollars/version.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module DecimalDollars
# :undoc
VERSION = "0.0.1"
VERSION = "0.0.2"
end
66 changes: 66 additions & 0 deletions spec/lib/decimal_dollars/money/arithmetic_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require "spec_helper"

describe DecimalDollars::Money::Arithmetic do
subject { DecimalDollars::Money }

let(:money) { subject.new(1.2345) }

describe "#-@" do
it "returns Money with a negative decimal" do
expect(-money).to be_an_instance_of(subject)
expect(-money.to_d).to eq(BigDecimal('-1.2345'))
end
end

%w{ == eql? }.each do |method|
describe "##{method}" do
it "raise an ArgumentError if value we compare to is not a Money" do
expect {
money.send(method, 1.2345)
}.to raise_error(ArgumentError, /A money cannot be compared with/)
end

it "returns false if objects are not equivalent" do
expect(money.send(method, subject.new('1.234'))).to be_false
end

it "returns true if objects are equivalent" do
expect(money.send(method, subject.new('1.2345'))).to be_true
end
end
end

describe "#+" do
it "adds a value and return Money object" do
expect(money + 1.01).to eq(subject.new(2.2445))
end
end

describe "#-" do
it "subtracts a value and return Money object" do
expect(money - 1.01).to eq(subject.new(0.2245))
end
end

describe "#*" do
it "raises an ArgumentError if a Money object is passed" do
expect {
money * subject.new(2)
}.to raise_error(ArgumentError, /A money cannot be multiplied by/)
end

it "is multipliable by a value and returns Money object" do
expect(money * 1.01).to eq(subject.new(1.246845))
end
end

describe "#/" do
it "is divisible by a Money value and return float" do
expect(money / subject.new('1.01')).to eq(1.2222772277227723)
end

it "is divisible by a value and return Money object" do
expect(money / 1.01).to eq(subject.new(money.to_d / subject.new('1.01').to_d))
end
end
end
57 changes: 23 additions & 34 deletions spec/lib/decimal_dollars/money_spec.rb
Original file line number Diff line number Diff line change
@@ -1,54 +1,43 @@
require "spec_helper"

describe DecimalDollars::Money do
let(:money) { described_class.new(1.2345) }
subject { DecimalDollars::Money }

describe "#+" do
it "adds a value and return Money object" do
expect(money + 1.01).to be_an_instance_of(described_class)
end
end
let(:money) { subject.new(1.2345) }

describe "#-" do
it "subtracts a value and return Money object" do
expect(money - 1.01).to be_an_instance_of(described_class)
end
it "uses to_d for passed object if it responds to it" do
obj = "1.2345"
expect(obj).to receive(:to_d)
subject.new(obj)
end

describe "#*" do
it "is multipliable by a value and returns Money object" do
expect(money * 1.01).to be_an_instance_of(described_class)
end
it "creates new BigDecimal if passed object doesn't respond to to_d" do
obj = double("1.2345", to_s: "1.2345")
expect(obj).to receive(:to_s)
subject.new(obj)
end

it "raises an ArgumentError if a Money object is passed" do
expect {
money * described_class.new(2)
}.to raise_error(ArgumentError, 'Money cannot be multiplied by Money')
describe "#corece" do
it "returns array with reversed operands" do
expect(money.coerce(2)).to eq([money, 2])
end
end

describe "#/" do
it "is divisible by a value and return Money object" do
expect(money / 1.01).to be_an_instance_of(described_class)
describe "#to_d" do
it "returns BigDecimal representation" do
expect(money.to_d).to eq(BigDecimal('1.2345'))
end
end

describe "#**" do
it "raises a RuntimeError" do
expect {
money ** 2
}.to raise_error(RuntimeError, 'Money cannot be raised to any power')
describe "#to_f" do
it "returns Float representation" do
expect(money.to_f).to eq(BigDecimal('1.2345').to_f)
end
end

describe "#round_float" do
it "rounds correctly" do
expect(described_class.new(1.251).to_f).to eq(1.25)
expect(described_class.new(1.255).to_f).to eq(1.25)
expect(described_class.new(1.2551).to_f).to eq(1.26)
expect(described_class.new(1.2555).to_f).to eq(1.26)
expect(described_class.new(1.2556).to_f).to eq(1.26)
expect(described_class.new(1.256).to_f).to eq(1.26)
describe "#to_s" do
it "returns String representation" do
expect(money.to_s).to eq(BigDecimal('1.2345').to_s)
end
end
end