Skip to content

Commit

Permalink
support for between queries on MoneyRange
Browse files Browse the repository at this point in the history
  • Loading branch information
kristianmandrup committed Sep 24, 2012
1 parent 51571da commit 0ccddf6
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 29 deletions.
6 changes: 3 additions & 3 deletions Gemfile
Expand Up @@ -3,16 +3,16 @@ source :rubygems
gem 'money'

gem 'mongoid', "~> 3.0.0"
gem 'origin', :git => "git://github.com/nessche/origin.git", :branch => "custom_criteria_expansion"
gem 'origin' #, :git => "git://github.com/nessche/origin.git", :branch => "custom_criteria_expansion"
gem 'moped'

# gem 'mongoid', "~> 2.4"
# gem 'bson'

group :development do
gem "rspec", "~> 2.10"
gem "rspec", ">= 2.10"
gem "rdoc", ">= 3.12"
gem "bundler", "~> 1.1.0"
gem "bundler", ">= 1.1.0"
gem "jeweler", ">= 1.8.3"
gem "simplecov",">= 0.5"
end
Expand Down
14 changes: 4 additions & 10 deletions Gemfile.lock
@@ -1,10 +1,3 @@
GIT
remote: git://github.com/nessche/origin.git
revision: 5ca0dd3bb4729d08605fd87d74d3347bce504ae5
branch: custom_criteria_expansion
specs:
origin (1.1.0)

GEM
remote: http://rubygems.org/
specs:
Expand Down Expand Up @@ -34,6 +27,7 @@ GEM
tzinfo (~> 0.3.22)
moped (1.2.2)
multi_json (1.3.6)
origin (1.0.9)
rake (0.9.2.2)
rdoc (3.12)
json (~> 1.4)
Expand All @@ -55,12 +49,12 @@ PLATFORMS
ruby

DEPENDENCIES
bundler (~> 1.1.0)
bundler (>= 1.1.0)
jeweler (>= 1.8.3)
money
mongoid (~> 3.0.0)
moped
origin!
origin
rdoc (>= 3.12)
rspec (~> 2.10)
rspec (>= 2.10)
simplecov (>= 0.5)
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -20,6 +20,8 @@ Bundle via Terminal:
require 'money-mongoid'
``

Now also supports MoneyRange with between queries and can even do dynamic currency conversions as part of the query!!! ;)

See specs for usage examples, fx `money/mongoid/3x/money_spec.rb`

## Contributing to money-mongoid
Expand Down
18 changes: 16 additions & 2 deletions lib/money/mongoid/3x/money.rb
Expand Up @@ -35,6 +35,14 @@ def custom_specify(name, operator, value, options = {})
end
end

def custom_between(field, value, options = {})
{ "$gte" => ::Money.new(value.min, value.iso_code), "$lte" => ::Money.new(value.max, value.iso_code) }
end

def custom_between? field, value
true
end

private

def specify_with_multiple_currencies(name, operator, value, options)
Expand Down Expand Up @@ -79,5 +87,11 @@ def exchange_to_currencies(currencies)
end

Mongoid::Fields.option :compare_using do |model, field, value|
# TODO: add some validation here to make sure the value is a valid ISO currency code
end
value.each do |iso_code|
unless Money::Currency.find(iso_code)
raise ArgumentError, "Invalid ISO currency code: #{value}"
end
end
end

require 'money/mongoid/3x/origin/selectable'
66 changes: 66 additions & 0 deletions lib/money/mongoid/3x/origin/selectable.rb
@@ -0,0 +1,66 @@
# encoding: utf-8
module Origin

# An origin selectable is selectable, in that it has the ability to select
# document from the database. The selectable module brings all functionality
# to the selectable that has to do with building MongoDB selectors.
module Selectable

private

# Create the standard expression query.
#
# @api private
#
# @example Create the selection.
# selectable.expr_query(age: 50)
#
# @param [ Hash ] criterion The field/value pairs.
#
# @return [ Selectable ] The cloned selectable.
#
# @since 1.0.0
def expr_query(criterion)
selection(criterion) do |selector, field, value|
if (field.is_a? Key) && custom_serialization?(field.name, field.operator)
specified = custom_specify(field.name, field.operator, value)
else
specified = field.specify(value.__expand_complex__, negating?)
end
selector.merge!(specified)
end
end

def between(criterion = nil)
selection(criterion) do |selector, field, value|
expr = custom_between?(field, value) ? custom_between(field, value) : { "$gte" => value.min, "$lte" => value.max }
selector.store(
field, expr
)
end
end

def custom_between? name, value
serializer = @serializers[name.to_s]
serializer && serializer.type.respond_to?(:custom_between?) && serializer.type.custom_between?(name, value)
end

def custom_between(name, value)
serializer = @serializers[name.to_s]
raise RuntimeError, "No Serializer found for field #{name}" unless serializer
serializer.type.custom_between(name, value, serializer.options)
end


def custom_serialization?(name, operator)
serializer = @serializers[name.to_s]
serializer && serializer.type.respond_to?(:custom_serialization?) && serializer.type.custom_serialization?(operator)
end

def custom_specify(name, operator, value)
serializer = @serializers[name.to_s]
raise RuntimeError, "No Serializer found for field #{name}" unless serializer
serializer.type.custom_specify(name, operator, value, serializer.options)
end
end
end
1 change: 1 addition & 0 deletions lib/money/mongoid/core_ext.rb
@@ -1,2 +1,3 @@
require 'money/mongoid/core_ext/string'
require 'money/mongoid/core_ext/array'
require 'money/mongoid/core_ext/range'
43 changes: 43 additions & 0 deletions lib/money/mongoid/core_ext/range.rb
@@ -0,0 +1,43 @@
class Range
def dollars
::MoneyRange.new self, 'usd'
end

def euros
::MoneyRange.new self, 'eur'
end

def to_currency iso_code
::MoneyRange.new self, iso_code
end
end

class Money
def self.range range, iso_code
::MoneyRange.new range, iso_code
end
end

# http://blog.jayfields.com/2008/02/ruby-replace-methodmissing-with-dynamic.html
class DelegateDecorator
def initialize(subject)
subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
end

class MoneyRange < DelegateDecorator
attr_reader :iso_code, :range

def initialize range, iso_code
super(range)
@range = range
@iso_code = iso_code
end
end

46 changes: 34 additions & 12 deletions spec/money/mongoid/3x/money_spec.rb
Expand Up @@ -9,6 +9,22 @@
let(:products) do
end

before :all do
# Mongoid.logger = Logger.new($stdout)
# Moped.logger = Logger.new($stdout)

Money.add_rate("USD","EUR", 0.5)
Money.add_rate("EUR","USD", 2)
end

def create_money iso_code, count = 6, options = {step: 500}
step = options[:step]
count.times do |n|
Product.create :price => Money.new(n * step, iso_code)
end
end


its(:price) { should be_a Money }

specify { subject.price.cents.should == 3000 }
Expand All @@ -30,12 +46,7 @@
end

it "should be searchable by price using gte and a money value of different currency" do
Money.add_rate("USD","EUR", 0.5)
Money.add_rate("EUR","USD", 2)

6.times do |n|
Product.create :price => Money.new(n * 500, 'USD')
end
create_money 'USD'

# So far only works by explicit conversion before search. Any solution?
Product.all.exchange_to! 'EUR'
Expand All @@ -47,13 +58,24 @@
search_res.count.should == 0
end

it 'should search using money range' do
create_money 'USD'

res1 = Product.between price: (500..2000).dollars
res2 = Product.between price: (500..2000).to_currency('USD')

res1.count.should == 4
res2.count.should == res1.count

res3 = Product.between price: Money.range((500..2000), 'usd')
res3.count.should == 4
end


it "should respect the currency information when using comparison operators" do
Money.add_rate("USD","EUR", 0.5)
Money.add_rate("EUR","USD", 2)
6.times do |n|
Product.create :price => Money.new(n * 500, 'USD')
Product.create :price => Money.new(n * 500, 'EUR')
end
create_money 'USD'
create_money 'EUR'

search_res = Product.where :price.gte => Money.new(2000, 'EUR')
search_res.count.should == 2
search_res = Product.where :price.lt => Money.new(2000, "USD")
Expand Down
4 changes: 2 additions & 2 deletions spec/money/mongoid/spec_helper.rb
Expand Up @@ -5,8 +5,8 @@
require 'money/mongoid/version_setup'

Mongoid.configure do |config|
Mongoid.logger.level = Logger::DEBUG
Moped.logger.level = Logger::DEBUG
# Mongoid.logger.level = Logger::DEBUG
# Moped.logger.level = Logger::DEBUG
Mongoid::VersionSetup.configure config
end

Expand Down

0 comments on commit 0ccddf6

Please sign in to comment.