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 ...
source to share
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
source to share
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.
source to share