From 99fe00793632136de713d36b4cc1699195148435 Mon Sep 17 00:00:00 2001 From: Nate Holland Date: Thu, 12 Apr 2018 14:53:45 -0500 Subject: [PATCH] Fix but in coerce when Rails 5 params are passed in Since parameters in Rails 5 no longer sub class from Hash and blow up when you try to call to_hash on them when they are not permitted the coerce method in Virtus was no longer working. Since the hash here is not mass assigned but instead only permits known attributes it is safe to call `.to_unsafe_hash` if it is defined on the attributes. If not we fall back to the previous behavior. This also adds specs to capture the bahavior I was seeing on Rails 5. --- lib/virtus/attribute_set.rb | 10 ++++++--- spec/unit/virtus/attributes_writer_spec.rb | 26 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/virtus/attribute_set.rb b/lib/virtus/attribute_set.rb index 66055a84..48673077 100644 --- a/lib/virtus/attribute_set.rb +++ b/lib/virtus/attribute_set.rb @@ -193,9 +193,13 @@ def set_defaults(object, filter = method(:skip_default?)) # # @api private def coerce(attributes) - ::Hash.try_convert(attributes) or raise( - NoMethodError, "Expected #{attributes.inspect} to respond to #to_hash" - ) + if attributes.respond_to?(:to_unsafe_hash) + attributes.to_unsafe_hash + elsif hash_attributes = ::Hash.try_convert(attributes) + hash_attributes + else + raise( NoMethodError, "Expected #{attributes.inspect} to respond to #to_hash" ) + end end # @api private diff --git a/spec/unit/virtus/attributes_writer_spec.rb b/spec/unit/virtus/attributes_writer_spec.rb index e52ec1ac..075f7add 100644 --- a/spec/unit/virtus/attributes_writer_spec.rb +++ b/spec/unit/virtus/attributes_writer_spec.rb @@ -14,6 +14,32 @@ expect(subject.test).to eql('Hello World') end + + context "with Rails 5 parameters" do + class FakeParams + + def initialize(hash) + @hash = hash + end + + def to_hash + raise "Cannot be called on unsafe params" + end + + def to_unsafe_hash + @hash + end + end + + it "safely assigns the hash" do + subject.attributes = FakeParams.new({ :test => 'Hello World' }) + expect(subject.test).to eql('Hello World') + end + + it "handles properly if the attributes are not hash like" do + expect { subject.attributes = 1 }.to raise_error(NoMethodError) + end + end end context 'with a class' do