From c2bcd7b0075a82c2f70ce0c40f9e3a3ea50288a4 Mon Sep 17 00:00:00 2001 From: Brad Robertson and Sachin Siby Date: Thu, 6 Aug 2015 09:50:01 -0400 Subject: [PATCH 1/3] to_h/to_hash should deeply convert attributes to a hash --- lib/virtus/instance_methods.rb | 31 ++++++++++++++++++++-- spec/unit/virtus/attributes_reader_spec.rb | 24 +++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) 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 From d682c1d938f334bb61b217d578592934dd15d863 Mon Sep 17 00:00:00 2001 From: Gabe S Date: Mon, 6 Mar 2017 15:13:07 -0600 Subject: [PATCH 2/3] fix for Fooda --- .ruby-version | 2 +- .travis.yml | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.ruby-version b/.ruby-version index 879b416e..2bf1c1cc 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.1 +2.3.1 diff --git a/.travis.yml b/.travis.yml index a1eec966..99ffeaa7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ rvm: - 2.0 - 2.1 - 2.2 + - 2.3 - jruby - rbx - ruby-head @@ -16,8 +17,4 @@ matrix: - rvm: rbx notifications: email: - - piotr.solnica@gmail.com - - dan.kubb@gmail.com -addons: - code_climate: - repo_token: 2b66fbb7c7c72503eb7841a479c0ad923f691729f4109b4aa8c9b4def1ebb42d + - gabe.saravia@fooda.com From 333d7c45c3f892179d5c75f656c6cb4abce28dd4 Mon Sep 17 00:00:00 2001 From: Gabe S Date: Mon, 6 Mar 2017 16:30:34 -0600 Subject: [PATCH 3/3] also handle arrays of a nested model --- lib/virtus/instance_methods.rb | 10 +++++++++- spec/unit/virtus/attributes_reader_spec.rb | 14 ++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/virtus/instance_methods.rb b/lib/virtus/instance_methods.rb index 65a2af4d..626de57d 100644 --- a/lib/virtus/instance_methods.rb +++ b/lib/virtus/instance_methods.rb @@ -66,7 +66,11 @@ def attributes # @api public def to_h attributes.each_with_object({}) do |(k, v), h| - h[k] = v.respond_to?(:to_h) ? v.to_h : v + if v.is_a? Array + h[k] = v.map { |v| hash_if_responds_or_value v } + else + h[k] = hash_if_responds_or_value v + end end end alias_method :to_hash, :to_h @@ -225,6 +229,10 @@ def set_default_attributes! private + def hash_if_responds_or_value(value) + value.respond_to?(:to_h) ? value.to_h : value + end + # The list of allowed public methods # # @return [Array] diff --git a/spec/unit/virtus/attributes_reader_spec.rb b/spec/unit/virtus/attributes_reader_spec.rb index af05162b..d1c98699 100644 --- a/spec/unit/virtus/attributes_reader_spec.rb +++ b/spec/unit/virtus/attributes_reader_spec.rb @@ -44,22 +44,24 @@ child = Class.new { include Virtus - attribute :d, String + attribute :foo, String } Class.new { include Virtus - attribute :a, String - attribute :c, child + attribute :bar, String + attribute :nested_model, child + attribute :array_of_nested_model, Array[child] + } } - subject { model.new a: "b", c: {d: "e"} } + subject { model.new bar: "1", nested_model: { foo: "2" }, array_of_nested_model: [{ foo: "3" }, { foo: "4" }] } 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"}) + expect(subject.to_h).to eql(bar: "1", nested_model: { foo: "2" }, array_of_nested_model: [{ foo: "3" }, { foo: "4" }] ) + expect(subject.to_hash).to eql(bar: "1", nested_model: { foo: "2" }, array_of_nested_model: [{ foo: "3" }, { foo: "4" }] ) end end end