3

I have a list of immutable value objects. The lookup class provides ways to iterate and query that data:

class Banker
  Bank = Struct.new(:name, :bic, :codes)

  attr_reader :banks
  def initialize
    @banks = [
      Bank.new('Citibank',    '1234567', ['1', '2']),
      Bank.new('Wells Fargo', '7654321', ['4']), # etc.
    ]
  end

  def find_by_bic(bic)
    banks.each do |bank|
      return bank if bank.bic == bic
    end
  end
end

@banks is initialized every time Banker is used. What options are there to cache @banks so that it's reused across different instances of the Banker?

randomguy
  • 12,042
  • 16
  • 71
  • 101
  • I don't understand the reason of the downvote. I think the question and the problem is well explained. Please elaborate on what's missing! – randomguy Oct 12 '14 at 11:41
  • The question is pretty clear to me, but its possible the downvoter felt question statement itself was slightly broad (I would disagree if that were the case). Or that the title doesn't quite match what you're actually asking (not really a downvotable reason imo). – Paul Richter Oct 12 '14 at 21:11

2 Answers2

4

I don't think Struct buys you anything here. How about doing it like this?

Code

class Banker
  @all_banks = {}

  class << self
    attr_reader :all_banks
  end

  attr_reader :banks

  def initialize(banks)
    @banks = banks.keys
    banks.each { |k,v| self.class.all_banks[k] = v }
  end

  def find_by_bic(bic)
    return nil unless @banks.include?(bic)
    self.class.all_banks[bic]
  end
end

Note self in self.class is needed to distinguish the class of self from the keyword class.

Example

b1 = Banker.new({ '1234567' => { name: 'Citibank', codes: ["1", "2"] },
                  '7654321' => { name: 'Wells Fargo', codes: ['4'] } })
b1.banks
  #=> ["1234567", "7654321"]
Banker.all_banks
  #=> {"1234567"=>{:name=>"Citibank", :codes=>["1", "2"]},
  #    "7654321"=>{:name=>"Wells Fargo", :codes=>["4"]}}
b1.find_by_bic '7654321'
  #=> {:name=>"Wells Fargo", :codes=>["4"]}
b1.find_by_bic '1234567'
  #=> {:name=>"Citibank", :codes=>["1", "2"]}
b1.find_by_bic '0000000'
  #=> nil

b2 = Banker.new({ '6523155' => { name: 'Bank of America', codes: ["3"] },
                  '1234567' => { name: 'Citibank', codes: ["1", "2"] } })
b2.banks
  #=> ["6523155", "1234567"]
Banker.all_banks
  #=> {"1234567"=>{:name=>"Citibank", :codes=>["1", "2"]},
  #    "7654321"=>{:name=>"Wells Fargo", :codes=>["4"]},
  #    "6523155"=>{:name=>"Bank of America", :codes=>["3"]}}
b2.find_by_bic '6523155'
  #=> {:name=>"Bank of America", :codes=>["3"]}
b2.find_by_bic '1234567'
  #=> {:name=>"Citibank", :codes=>["1", "2"]}
b2.find_by_bic '7654321'
  #=> nil

Alternatives

If you prefer you could instead add the class method:

def self.new(banks)
  banks.each { |k,v| all_banks[k] = v }
  super
 end

and remove the first line in initialize.

Or, if you have a complete list of all banks, you could instead just make all_banks a constant:

ALL_BANKS = {"1234567"=>{:name=>"Citibank", :codes=>["1", "2"]},
             "7654321"=>{:name=>"Wells Fargo", :codes=>["4"]},
             "6523155"=>{:name=>"Bank of America", :codes=>["3"]}}

def find_by_bic(bic)
  return nil unless @banks.include?(bic)
  ALL_BANKS[bic]
end

and change initialize to:

def initialize(bics)
  @banks = bics
end

where bics is an array of bic values.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
3

To share immutable data between instances you can use frozen class variables: @@banks ||= [...].freeze

Alexander Karmes
  • 2,438
  • 1
  • 28
  • 34