mongoid-history
mongoid-history copied to clipboard
Money-rails object is converted to Hash when storing the history
I have a model that among other fields it uses a money-rails field (money type):
class MyModel
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::History::Trackable
field :name, type: String
field :date, type: Date
field :amount, type: Money
track_history track_create: true
end
When I create an instance of this model, the amount field has the class Money, which is the expected behavior (and of course the date field is a Time object).
pry(main)> m=MyModel.create(name: "A", date: Date.today, amount: 200)
=> #<MyModel _id: 5453cab06d61632044030000, name: "A", date: 2014-10-31 00:00:00 UTC, amount: {:cents=>20000.0, :currency_iso=>"USD"}, version: 1, modifier_id: nil>
pry(main)> m.date.class
=> Date
pry(main)> m.amount.class
=> Money
However, when I get the value back from history, the date field preserves a compatible class, but the amount field is no longer a Money field, but a BSON Document:
pry(main)> m.history_tracks.last[:modified]
=> {"name"=>"A", "date"=>2014-10-31 00:00:00 UTC, "amount"=>{"cents"=>20000.0, "currency_iso"=>"USD"}}
pry(main)> m.history_tracks.last[:modified]["date"].class
=> Time
pry(main)>m.history_tracks.last[:modified]["amount"].class
=> BSON::Document
This creates complications when performing actions like comparing the current amount of the object with a previous value, stored by monogoid_history:
pry(main)> m.amount==m.history_tracks.last[:modified]["amount"]
=> false #Wrong, should be true as no changes were made yet
However, the date field works fine:
pry(main)> m.date==m.history_tracks.last[:modified]["date"]
=> true
So, my question is if this is a bug and the Money type is lost when the object copy is stored on the history_tracks table, or if this is a correct behavior.
If the behavior is the expected one, how would I override it so that the Money type is also "tracked" for the amount field, as it happens with the Date field?
Well, I couldn't get any answer about this here so I'll share my solution to this Bug, in case someone else has the same issue.
Since the Money type is lost, I implemented an explicit conversion when getting the changes back, so that the amount field is converted to the correct Money field. Here's how I did it (method on the model):
def get_previous_changes
changes=self.changes
changes.each do |f|
if changes.include?(f)
if f=="amount"
unless changes.fetch(f).first.nil?
val=changes.fetch(f).first
value=Money.new(val[:cents],val[:currency_iso])
else
value=nil
end
else
value=changes.fetch(f)[0].to_s
end
else
value=nil
end
@previous_changes.merge!(f=>value)
end
Every time I want to get the previous changes for a given object, I would use this method so that amount fields are always Money type, as it should be in the first place. I'm sure this can be fixed on this gem, but in the meantime, this option works fine.
Thanks!
Maybe I'll leave this open, in case someone at mongoid-history can provide feedback at some point.
I think this is a bug. I would expect this to work.
It would be helpful if you could write a spec for this.
OK, I'll be happy to do that. Bu can you explain more about how exactly you want that spec? What's the usual way of doing this? Fork/write test/send pull request?
I want a test that reproduces the problem :) I think you described it perfectly well above, so that in code.
Yes for fork/test/PR.
Ok I have the test ready but when I try to run it I'm getting a weird issue that might be due to something I'm forgetting to include. The error I get is: Failure/Error: @my=MyModel.create(name: "A", date: Date.today, amount: 200.to_money) NoMethodError: undefined method `bson_type' for #<Money fractional:20000 currency:USD>
Do you know what this could be about? Or want me to send you the pull request now, even with this issue?
You should PR what you have.
1) Model with MoneyRails field should have a history entry with a valid amount field with class Money
Failure/Error: self.class.tracker_class.create!(history_tracker_attributes(action.to_sym).merge(version: current_version, action: action.to_s, trackable: self))
NoMethodError:
undefined method `bson_type' for #<Money fractional:200 currency:USD>
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:47:in `block in to_bson'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:43:in `each'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:43:in `to_bson'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:49:in `block in to_bson'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:43:in `each'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:43:in `to_bson'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/array.rb:49:in `block in to_bson'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/array.rb:46:in `each'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/array.rb:46:in `each_with_index'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/array.rb:46:in `to_bson'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:49:in `block in to_bson'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:43:in `each'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/bson-4.1.1/lib/bson/hash.rb:43:in `to_bson'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/protocol/serializers.rb:163:in `serialize'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/protocol/message.rb:209:in `block in serialize_fields'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/protocol/message.rb:197:in `each'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/protocol/message.rb:197:in `serialize_fields'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/protocol/message.rb:100:in `serialize'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/server/connection.rb:188:in `block in write'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/server/connection.rb:187:in `each'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/server/connection.rb:187:in `write'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/server/connection.rb:161:in `deliver'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/server/connection.rb:108:in `block in dispatch'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/monitoring/publishable.rb:47:in `publish_command'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/server/connection.rb:107:in `dispatch'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/operation/write/command/writable.rb:38:in `block in execute'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/server/connection_pool.rb:107:in `with_connection'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/server/context.rb:63:in `with_connection'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/operation/write/command/writable.rb:37:in `execute'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/operation/write/insert.rb:55:in `execute_write_command'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/operation/write/write_command_enabled.rb:38:in `execute'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/collection.rb:346:in `block in insert_one'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/retryable.rb:112:in `write_with_retry'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongo-2.2.6/lib/mongo/collection.rb:337:in `insert_one'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/query_cache.rb:168:in `insert_one_with_clear_cache'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/creatable.rb:79:in `insert_as_root'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/creatable.rb:27:in `block in insert'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/creatable.rb:118:in `block (2 levels) in prepare_insert'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/activesupport-4.0.13/lib/active_support/callbacks.rb:393:in `_run__1773079500231756398__create__callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/activesupport-4.0.13/lib/active_support/callbacks.rb:80:in `run_callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/interceptable.rb:138:in `run_callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/creatable.rb:117:in `block in prepare_insert'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/activesupport-4.0.13/lib/active_support/callbacks.rb:373:in `_run__1773079500231756398__save__callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/activesupport-4.0.13/lib/active_support/callbacks.rb:80:in `run_callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/interceptable.rb:138:in `run_callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/creatable.rb:116:in `prepare_insert'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/creatable.rb:23:in `insert'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/creatable.rb:180:in `block in create!'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/threaded/lifecycle.rb:161:in `_creating'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/creatable.rb:175:in `create!'
# ./lib/mongoid/history/trackable.rb:278:in `track_history_for_action'
# ./lib/mongoid/history/trackable.rb:240:in `track_update'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/activesupport-4.0.13/lib/active_support/callbacks.rb:387:in `_run__2981391924363940241__update__callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/activesupport-4.0.13/lib/active_support/callbacks.rb:80:in `run_callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/interceptable.rb:138:in `run_callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/updatable.rb:117:in `block in prepare_update'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/activesupport-4.0.13/lib/active_support/callbacks.rb:373:in `_run__2981391924363940241__save__callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/activesupport-4.0.13/lib/active_support/callbacks.rb:80:in `run_callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/interceptable.rb:138:in `run_callbacks'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/updatable.rb:116:in `prepare_update'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/updatable.rb:139:in `update_document'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/mongoid-5.1.3/lib/mongoid/persistable/savable.rb:25:in `save'
# ./spec/unit/money_rails_spec.rb:23:in `block (2 levels) in <top (required)>'
# /home/siva/.rvm/gems/ruby-2.3.0/gems/rspec-core-3.5.1/exe/rspec:4:in `<top (required)>'
# /home/siva/.rvm/gems/ruby-2.3.0/bin/rspec:23:in `load'
# /home/siva/.rvm/gems/ruby-2.3.0/bin/rspec:23:in `<main>'
# /home/siva/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `eval'
# /home/siva/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `<main>'
Trying to simulate issue but getting this error while creating an object in the test case
CustomModel.create(name: "A", date: Date.today, amount: Money.new(200))
But I am not getting error if I run same spec under money-rails
project.