diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 37e77d4f93d1aa71938ec846a243a5a7eeb41195..d854b9baedd96a4ce65a5aaec669da941c8d6322 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -606,6 +606,43 @@ def initialize(attributes = {}) load(attributes) end + # Returns a clone of the resource that hasn't been assigned an id yet and + # is treated as a new resource. + # + # ryan = Person.find(1) + # not_ryan = ryan.clone + # not_ryan.new? # => true + # + # Any active resource member attributes will NOT be cloned, though all other + # attributes are. This is to prevent the conflict between any prefix_options + # that refer to the original parent resource and the newly cloned parent + # resource that does not exist. + # + # ryan = Person.find(1) + # ryan.address = StreetAddress.find(1, :person_id => ryan.id) + # ryan.hash = {:not => "an ARes instance"} + # + # not_ryan = ryan.clone + # not_ryan.new? # => true + # not_ryan.address # => NoMethodError + # not_ryan.hash # => {:not => "an ARes instance"} + # + def clone + # Clone all attributes except the pk and any nested ARes + attrs = self.attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.inject({}) do |attrs, (k, v)| + attrs[k] = v.clone + attrs + end + # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which + # attempts to convert hashes into member objects and arrays into collections of objects. We want + # the raw objects to be cloned so we bypass load by directly setting the attributes hash. + resource = self.class.new({}) + resource.prefix_options = self.prefix_options + resource.send :instance_variable_set, '@attributes', attrs + resource + end + + # A method to determine if the resource a new object (i.e., it has not been POSTed to the remote service yet). # # ==== Examples diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index 13709f1ca1754b30bef99bd4fe16a6c53a17f5c1..9caca803b74940ac175503befee139f4f7957f38 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -558,6 +558,41 @@ def test_create assert_raises(ActiveResource::ResourceConflict) { Person.create(:name => 'Rick') } end + def test_clone + matz = Person.find(1) + matz_c = matz.clone + assert matz_c.new? + matz.attributes.each do |k, v| + assert_equal v, matz_c.send(k) if k != Person.primary_key + end + end + + def test_nested_clone + addy = StreetAddress.find(1, :params => {:person_id => 1}) + addy_c = addy.clone + assert addy_c.new? + addy.attributes.each do |k, v| + assert_equal v, addy_c.send(k) if k != StreetAddress.primary_key + end + assert_equal addy.prefix_options, addy_c.prefix_options + end + + def test_complex_clone + matz = Person.find(1) + matz.address = StreetAddress.find(1, :params => {:person_id => matz.id}) + matz.non_ar_hash = {:not => "an ARes instance"} + matz.non_ar_arr = ["not", "ARes"] + matz_c = matz.clone + assert matz_c.new? + assert_raises(NoMethodError) {matz_c.address} + assert_equal matz.non_ar_hash, matz_c.non_ar_hash + assert_equal matz.non_ar_arr, matz_c.non_ar_arr + + # Test that actual copy, not just reference copy + matz.non_ar_hash[:not] = "changed" + assert_not_equal matz.non_ar_hash, matz_c.non_ar_hash + end + def test_update matz = Person.find(:first) matz.name = "David"