Search code examples
rubyoopcollectionsenumerable

How can I create instances of a ruby class from a hash array?


I have a module FDParser that reads a csv file and returns a nice array of hashes that each look like this:

{
  :name_of_investment => "Zenith Birla",
  :type => "half-yearly interest",
  :folio_no => "52357",
  :principal_amount => "150000",
  :date_of_commencement => "14/05/2010",
  :period => "3 years",
  :rate_of_interest => "11.25"
}

Now I have an Investment class that accepts the above hash as input and transforms each attribute according to what I need.

class Investment
  attr_reader :name_of_investment, :type, :folio_no,
              :principal_amount, :date_of_commencement,
              :period, :rate_of_interest

  def initialize(hash_data)
    @name = hash_data[:name_of_investment]
    @type = hash_data[:type]
    @folio_no = hash_data[:folio_no]
    @initial_deposit = hash_data[:principal_amount]
    @started_on =hash_data[:date_of_commencement]
    @term = hash_data[:period]
    @rate_of_interest = hash_data[:rate_of_interest]
  end

  def type
    #-- custom transformation here
  end
end

I also have a Porfolio class with which I wish to manage a collection of investment objects. Here is what the Portfolio class looks like:

class Portfolio
  include Enumerable
  attr_reader :investments

  def initialize(investments)
    @investments = investments
  end

  def each &block
    @investments.each do |investment|
      if block_given?
        block.call investment
      else
        yield investment
      end
    end
  end
end

Now what I want is to loop over the investment_data yielded by the module and dynamically create instances of the investment class and then send those instances as input to the Portfolio class.

So far I tried:

FDParser.investment_data.each_with_index do |data, index|
  "inv#{index+1}" = Investment.new(data)
end

But obviously this doesn't work because I get a string instead of an object instance. What is the right way to send a collection of instances to a enumerable collection class that can then manage them?


Solution

  • I'm not sure what "send as input to the Portfolio class" means; classes themselves don't accept "input". But if you're just trying to add Investment objects to the @investments instance variable inside an instance of Portfolio, try this:

    portfolio = Portfolio.new([])
    
    FDParser.investment_data.each do |data|
      portfolio.investments << Investment.new(data)
    end
    

    Note that the array literal [] and the return value of portfolio.investments point to the self-same Array object here. This means you could equivalently do this, which arguably is a little clearer:

    investments = []
    
    FDParser.investment_data.each do |data|
      investments << Investment.new(data)
    end
    
    Portfolio.new(investments)
    

    And if you want to play a little code golf, it shrinks further if you use map.

    investments = FDParser.investment_data.map {|data| Investment.new(data) }
    
    Portfolio.new(investments)
    

    I think this is a little harder to read than the previous option, though.