kotyara85
kotyara85

Reputation: 325

ruby clone an object

I need to clone an existing object and change that cloned object. The problem is that my changes change original object. Here's the code:

require "httparty"

class Http
  attr_accessor :options
  attr_accessor :rescue_response
  include HTTParty
  def initialize(options)
    options[:path] = '/' if options[:path].nil? == true
    options[:verify] = false
    self.options = options

    self.rescue_response = {
      :code => 500
    }
  end

  def get
    self.class.get(self.options[:path], self.options)
  end

  def post
    self.class.post(self.options[:path], self.options)
  end

  def put
    self.class.put(self.options[:path], self.options)
  end

  def delete
    self.class.put(self.options[:path], self.options)
  end

end

Scenario:

test = Http.new({})

test2 = test

test2.options[:path] = "www"

p test2
p test

Output:

#<Http:0x00007fbc958c5bc8 @options={:path=>"www", :verify=>false}, @rescue_response={:code=>500}>
#<Http:0x00007fbc958c5bc8 @options={:path=>"www", :verify=>false}, @rescue_response={:code=>500}>

Is there a way to fix this?

Upvotes: 1

Views: 608

Answers (2)

lacostenycoder
lacostenycoder

Reputation: 11226

You want .clone or perhaps .dup

test2 = test.clone

But depending on your purposes, but in this case, you probably want .clone see What's the difference between Ruby's dup and clone methods?

The main difference is that .clone also copies the objects singleton methods and frozen state.

On a side note, you can also change

options[:path] = '/' if options[:path].nil? # you don't need "== true" 

Upvotes: 2

max pleaner
max pleaner

Reputation: 26768

You don't even need to clone here, you just need to make a new instance.

Right here:

test = Http.new({})
test2 = test

you don't have two instances of Http, you have one. You just have two variables pointing to the same instance.

You could instead change it to this, and you wouldn't have the problem.

test = Http.new({})
test2 = Http.new({})

If, however, you used a shared options argument, that's where you'd encounter an issue:

options = { path: nil }
test = Http.new(options)

# options has been mutated, which may be undesirable
puts options[:path] # => "/"

To avoid this "side effect", you could change the initialize method to use a clone of the options:

def initialize(options)
  options = options.clone
  # ... do other stuff
end

You could also make use of the splat operator, which is a little more cryptic but possibly more idiomatic:

def initialize(**options)
  # do stuff with options, no need to clone
end

You would then call the constructor like so:

options = { path: nil }
test = Http.new(**options)
puts test.options[:path] # => "/"

# the original hasn't been mutated
puts options[:path] # => nil

Upvotes: 2

Related Questions