Alternative to Ruby Case Statement

I am currently writing a program for a bank administration system using Ruby. One of the features of this system is that it can create a new account, accounts can be one of six types.

I have the following method in my controller to support this functionality:

def create_account(type, holder)
  case type
  when :current  then CurrentAccount.new(holder, @account_number)
  when :savings  then SavingsAccount.new(holder, @account_number)
  when :business then BusinessAccount.new(holder, @account_number)
  when :ir       then IRAccount.new(holder, @account_number)
  when :smb      then SMBAccount.new(holder, @account_number)
  when :student  then StudentAccount.new(holder, @account_number)
  end
end

      

Each of these accounts inherits from the base account and will eventually contain separate attributes, for example. Interest rate, overdraft, etc.

While it is functional and delivers the desired results, it does feel a little long. However, I can't think of any obvious refactorings.

Any suggestions are appreciated ...

+3


source to share


2 answers


My guess is that at some point the system or end user is effectively choosing the type of text and you need to convert it to a class to be used. Otherwise, you could write calling code that simply referenced and generated the correct class.

You can do what you have by defining a mapping between symbol type

and class. So you can do it in the scope create_account

:

ACCOUNT_CLASS_FOR = Hash[
  current:  CurrentAccount,
  savings:  SavingsAccount,
  business: BusinessAccount,
  ir:       IRAccount,
  smb:      SMBAccount,
  student:  StudentAccount
]

def create_account(type, holder)
  if account_class = ACCOUNT_CLASS_FOR[ type ]
    account_class.new( holder, @account_number )
  else
    raise "Bad account type #{type}"
  end
end

      

This is less repetitive code, and makes the mapping between symbol names and Ruby class mapping more explicit. If you need to apply or test the conversion elsewhere, you can make the constant available elsewhere without repeating.

You can make this even cleaner if each class recognizes its own label, eg.



class CurrentAccount
  def self.label
     :current
  end
end

      

Then you might have something like this:

ALLOWED_ACCOUNT_CLASSES = [CurrentAccount,SavingsAccount,BusinessAccount, # etc.

ACCOUNT_CLASS_FOR = Hash[
  ALLOWED_ACCOUNT_CLASSES.map { |klass| [klass.label, klass] }
]

      

Note the fairly common practice of using a variable klass

with a wrong value to avoid colliding with the Ruby keyword class

, but you can also just useaccount_class

+6


source


Here's another way, but you need the type to be specified with the class appropriately (i.e.: ir →: i_r)

def create_account(type, holder)
    Object.const_get(type.to_s.camelize + "Account").new(holder, @account_number)
end

      



Even if this one is short, I like Neil because he looks safer.

+3


source







All Articles