diff --git a/lib/virtus/instance_methods.rb b/lib/virtus/instance_methods.rb index 4448bf66..65a2af4d 100644 --- a/lib/virtus/instance_methods.rb +++ b/lib/virtus/instance_methods.rb @@ -41,8 +41,35 @@ module MassAssignment def attributes attribute_set.get(self) end - alias_method :to_hash, :attributes - alias_method :to_h, :attributes + + # Returns a hash of all publicly accessible attributes (including nested attributes) + # + # @example + # class Child + # include Virtus + # + # attribute :name, String + # end + # + # class Parent + # include Virtus + # + # attribute :name, String + # attribute :child, Child + # end + # + # parent = Parent.new(name: 'John', child: {name: 'Jim'}) + # parent.to_h # => { name: 'John', child: {name: 'Jim'} } + # + # @return [Hash] + # + # @api public + def to_h + attributes.each_with_object({}) do |(k, v), h| + h[k] = v.respond_to?(:to_h) ? v.to_h : v + end + end + alias_method :to_hash, :to_h # Mass-assign attribute values # diff --git a/spec/unit/virtus/attributes_reader_spec.rb b/spec/unit/virtus/attributes_reader_spec.rb index 82774c37..af05162b 100644 --- a/spec/unit/virtus/attributes_reader_spec.rb +++ b/spec/unit/virtus/attributes_reader_spec.rb @@ -38,4 +38,28 @@ it_behaves_like 'attribute hash' end + + context "#to_h / #to_hash" do + let(:model) { + child = Class.new { + include Virtus + + attribute :d, String + } + + Class.new { + include Virtus + + attribute :a, String + attribute :c, child + } + } + + subject { model.new a: "b", c: {d: "e"} } + + it "deeply converts to a hash" do + expect(subject.to_h).to eql(a: "b", c: {d: "e"}) + expect(subject.to_hash).to eql(a: "b", c: {d: "e"}) + end + end end