Ghoyos
Ghoyos

Reputation: 622

Illogical: circle of logic, in ruby using 'super'

I keep trying to reason upon the functionality of a method within a subclass that inherits functionality of a parents class. But it seems that I keep getting into a mental loop of: one cannot behave without the other but the other cannot come before the one... My brain hurts...

Ok heres my relevant code in the parent class

class BankAccount
  # method to initialize and other methods etc...

  def withdraw(amount)
    if (amount <= @balance)
      @balance -= amount
    else
      'Insufficient funds'
    end
  end

end

And heres my relevant code in the subclass

class CheckingAccount < BankAccount
  # methods to initialize and other methods etc...

  def withdraw
    super
  end 

end

According to the tutorial im learning from - what I am trying to accomplish is

So if I create a variable number_of_withdrawals inside of my BankAccount class (as tutorial examples hint towards) then how is it that when I call super from the subclass version of withdraw that it would know to increment number_of_withdrawals based on the if else statement executing a withdraw or not.

Shouldn't a variable number_of_withdrawals be declared in the BankAccount class, not the CheckingAccount class (even though tutorial examples hint towards putting it in the CheckingAccount class). For a full picture of this here is a gist with the test specs() below of my current code state:

Test Specs / Code Attempt

If someone can provide a working example of

With modified code I have provided in the GIST - I would really appreciate it. Im very new to ruby.(1 week)

Upvotes: 1

Views: 119

Answers (4)

max pleaner
max pleaner

Reputation: 26768

With inheritance, the parent can be thought of as a 'template' for the child. That is to say, you can instead of using a parent at all simply write everything into the child class (not that you should). The point is that everything from the parent class can be thought of as getting copied on to the child class, so if you make an instance variable on the parent and change it from the child, there is only one instance variable defined since there is only one object instantiated. In other words, when you say CheckingAccount.new there is no separate BankAccount getting instantiated - it's all the same object.

So, for every method that is defined in both the parent and child, you need to call super or else the parent method won't be called. Here's an example with your code:

class BankAccount
  def initialize
    @balance = 0
    @number_of_withdrawals = 0
  end

  def withdraw(amount)
    if amount <= @balance
      @balance -= amount
      @number_of_withdrawals += 1
    else
      'Insufficient funds'
    end
  end
end

class CheckingAccount < BankAccount
  MAX_FREE_WITHDRAWALS = 3

  def withdraw(amount)
    if @number_of_withdrawals >= self.class::MAX_FREE_WITHDRAWALS
    amount += 5
    super(amount)
  end 

end

I just skimmed the requirement document, so make sure to double check (e.g. don't just take my code and hand it is as homework :D)

Upvotes: 1

tadman
tadman

Reputation: 211590

Your method does way too much and internalizes too many assumptions. A better approach to this is to break things up a little:

class BankAccount
  attr_reader :balance

  def initialize
    @balance = 0
  end

  def withdraw(amount)
    if (can_withdraw?(amount))
      credit(amount)

      after_withdraw
    else
      false
    end
  end

  def can_withdraw?(amount)
    amount <= balance
  end

  def after_withdraw
    # No default behaviour
  end

  def debit(amount)
    @balance += amount
  end

  def credit(amount)
    @balance -= amount
  end
end

Then you can make the subclass specialize very specific methods instead of having to lean on super so hard:

class CheckingAccount < BankAccount
  attr_reader :overdraft

  def initialize
    super
    @overdraft = 0
    @withdrawals = 0
  end

  def can_withdraw?(amount)
    amount <= balance + overdraft
  end

  def after_withdraw
    @withdrawals += 1
  end
end

Upvotes: 2

pdoherty926
pdoherty926

Reputation: 10349

The behavior you're seeing -- or expected to provide in order to complete the exercise -- is due to Ruby's dynamic nature. Since your program is "interpreted" as it's run (and is subject to change), there's no way for Ruby to know that the instance variable in question won't exist until the method is actually executed.

Here's a contrived example which (hopefully) demonstrates why you're seeing/hoping to see this behavior:

class Foo    
  def say_something_that_doesnt_exist
    # Foo is free to try to make use of @nothing, 
    # in case it's been provided by a child class' 
    # instance, but if it's not present, its value 
    # will just be nil
    puts "say_something_that_doesnt_exist, like #{@nothing}!"
  end

  def say_something_that_does_exist
    puts "say_something_that_does_exist, like #{@bar}!"
  end
end

class Bar < Foo
  attr_reader :bar

  def initialize
    super
    @bar = "bar"
  end
end

bar = Bar.new
bar.say_something_that_doesnt_exist # say_something_that_doesnt_exist, like !
bar.say_something_that_does_exist # say_something_that_does_exist, like bar!

You should have a look at this question and its answers for a more detailed discussion about the distinction between static/dynamic languages and the early/late binding of values.

Upvotes: 1

Aniket Schneider
Aniket Schneider

Reputation: 944

With the code as it's currently written, CheckingAccount#withdraw could check the return value of super to determine whether the withdrawal was successful or not.

For example:

def withdraw(n)
  result = super
  if result != 'Insufficient funds'
    @number_of_withdrawals += 1
  end
  result
end

Upvotes: 2

Related Questions