Closed nessche closed 11 years ago
:)
Hi Marco,
I have been looking into your code today, both your additions to origin and money.rb
for mongoid 3 in money-mongoid. I made the following improvements in `money_spec.rb``
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
it "should be searchable by price using gte and a money value of different currency" do
create_money 'USD'
...
it "should respect the currency information when using comparison operators" do
create_money 'USD'
create_money 'EUR'
...
With logging output to STDOUT you can clearly track the way the query is being built with an $or
for each currency supported :)
I like your solution so far. I just wish that your origin additions would be added to origin soon in some form in order to support this kind of scenario, also for other custom field types.
For now, I think it would make sense to monkey-patch origin from with money-mongoid instead of referencing your specific fork of origin.
Good job!
Kris
My suggestion:
# money/mongoid/3x/origin/selectable.rb
# 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 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
# money/mongoid/3x/money.rb
...
Mongoid::Fields.option :compare_using do |model, field, value|
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'
Sweet :)
gem 'money'
gem 'mongoid', "~> 3.0.0"
gem 'origin'
gem 'moped'
group :development do
gem "rspec", ">= 2.10"
gem "rdoc", ">= 3.12"
gem "bundler", ">= 1.1.0"
gem "jeweler", ">= 1.8.3"
gem "simplecov",">= 0.5"
end
For now I've created a gem: https://github.com/kristianmandrup/origin-selectable_ext (also pushed it to rubygems)
@kristianmandrup Thanks for creating that. As far as origin goes, it's a bit too use case specific to implement anything here that handles all cases at the moment.
Multi-currency queries are very useful. Other custom types could use the feature as well, I think. So it would be great if this feature is implemented in some way.
Hi,
while working with money-mongoid project, I realized there is a need for custom types to generate portions of the MongoDB query directly (and not just serialize the type itself). E.g. when using a complex type like money (which consists of "cents", an Integer, and "currency_iso" a String) with comparison operators, the more natural way to express the query on a
Product
object having a fieldprice
of typeMoney
is something along the lines ofProduct.where(:price.gt => Money.new(2000,"USD"))
when only serializing the money object, the resulting query compares the cents value, ignoring the currency code, resulting in wrong results if the values in the DB are not all stored using the same currency.
In order to correctly perform the query the custom type should be able to actually generate the query subportion related to the
price
field such that it would look like`{"price.cents" => {"$gt" => 2000}, "price.currency_iso" => "USD"}
I managed to implement such functionality by modifying the implementation of
expr_query
method ofSelectable
as a proof-of-concept (pls seecustom_criteria_expansion
branch of my fork of the origin project), but as an implementation it is a hack because it uses inSelectable
the concept of serializers (defined inQueryable
and some properties that "happen to be there" because the serializer is of typeMongoid::Field::Standard
which is defined in the Mongoid gem.So the changes should be possibly split between origin (either in
Queryable
orSelectable
or both) and in mongoid (maybe introducing aMongoid::Field::Custom
class), but I leave to the more expert people the decision on where actually the changes should go, although I offer my help to implement such changes once there is an agreement on their correct place.An example of how the
Money
type handles the query generation can be found here.marco