Ruby should allow assigning config params

Viewed 98

I am trying to implement a class that takes a hash as a constructor as part of my Ruby learning.

This is what I have so far:

class Example

  attr_accessor :bar, :baz, :some

  def initialize args
     args.each do |k,v|
       instance_variable_set("@#{k}", v) unless v.nil?
     end
  end


  my_hash = Example.new(foo: {bar: "x", "baz" => "y"}, "some" => "other")

end

The above is supposed to read the values as follows:

my_hash.foo.bar
=> "x"
my_hash.foo.baz
=> "y"
my_hash.some
=> "other"
my_hash.foo.class
=> Example
my_hash.unknown
=> nil

and also override the values and assign new ones. However, after I implemented this apparently, this code snippet here:

def initialize args
         args.each do |k,v|
           instance_variable_set("@#{k}", v) unless v.nil?
         end
      end

is no good because it does not allow assigning of params? Not sure what that means.

This is one of the tests I am not passing

it 'should store my_hash from hash passed through constructor' do
    my_hash = Example.new({p1: 'test', 'p2' => {'p3' => [10, 12]}, p4: {}})

    expect(my_hash.p1).to eq('test')
    expect(my_hash.p2.p3).to eq([10, 12])
    expect(my_hash.p2).to be_a(Example)
  end

And the test that is failing my initialize method is this one:

it 'should allow assigning config params' do
    my_hash = Example.new

    my_hash.some = 'test'
    my_hash.other = 'test2'
    my_hash.yet_one_more = {}
    my_hash.yet_one_more.test = 'test'

    expect(my_hash.some).to eq('test')
    expect(my_hash.other).to eq('test2')
    expect(my_hash.yet_one_more.test).to eq('test')
  end
1 Answers

In your example you are passing a hash to the initialize method and then iterating a new instance variable for each key-value pair. Considering this, the only instance variables you are creating are:

@foo = {bar: "x", "baz" => "y"}
@some = 'other'

Therefore you only need attribute accessors for said variables.

# this allows you to access to whatever `foo` and `some` are holding,
# including nested properties.
attr_accessor :foo, :some

Oh, and you're getting an error because you're trying to create a variable my_hash inside the class (you're trying to instance a class inside its own definition). This is how you would define it:

class Example

  attr_accessor :foo, :some

  def initialize args
    args.each do |k, v|
      instance_variable_set("@#{k}", v) unless v.nil?
    end
  end
end

my_hash = Example.new(foo: {bar: "x", "baz" => "y"}, "some" => "other")

# Your @foo instance variable which you can access and modify
puts my_hash.foo
# your @foo's bar property which was defined as symbol
puts my_hash.foo[:bar]
# your @foo's baz property which was defined as string
puts my_hash.foo["baz"]
# your @some instance variable
puts my_hash.some
# Notice that even though `foo` was defined as symbol and `some` as
# string, the accessor allows you to get their values by using the
# dot notation.

Edit: From the tests you added I'm guessing that they expect you to create a new instance if the parameter passed is a nested hash. If so, you could do this:

class Example
  def initialize args
    args.each do |k, v|
      # Create a new Example instance if the value is a hash
      v = self.class.new(v) if v.class == Hash
      instance_variable_set("@#{k}", v) unless v.nil?
      # Dynamically set the accessor for each attribute.
      self.class.__send__(:attr_accessor, k)
    end
  end
end
Related