byronmac
byronmac

Reputation: 27

Is there a way I can call a method from another class in ruby?

Is it possible to see an updated balance when calling my print_statement? In my Transaction class, I am using an @amount and @timestamp instance variable to store a transaction into my @transaction instance variable in my Account class. I am then calling my print_statement method which only prints out the @timestamp and @amount for each transaction.

In my desired outcome, I need to also print out an updated Balance with each transaction. I am new to coding, so any advice would be much appreciated!

My desired outcome:

date || credit || debit || balance
14/01/2012 || || 500.00 || 2500.00
13/01/2012 || 2000.00 || || 3000.00
10/01/2012 || 1000.00 || || 1000.00

My current outcome:

2.7.0 :001 > require './lib/account'
 => true 
2.7.0 :002 > require './lib/transaction'
 => false 
2.7.0 :003 > account = Account.new
2.7.0 :004 > account.deposit(100)
 => [#<Transaction:0x00007f9eff8941a8 @amount=100, @timestamp="09/01/2021">] 
2.7.0 :005 > account.withdraw(50)
 => [#<Transaction:0x00007f9eff8941a8 @amount=100, @timestamp="09/01/2021">, #<Transaction:0x00007f9efd9673c0 @amount=-50, @timestamp="09/01/2021">] 
2.7.0 :006 > account.balance
 => 50 
2.7.0 :007 > account.print_statement
   date   || credit || debit || balance || 
-------------------------------------------
09/01/2021||100||  || Balance ||
09/01/2021||  ||-50|| Balance ||
 => [nil, nil] 

Account class

require_relative 'transaction'
 
 class Account

  attr_reader :transactions

  def initialize
    @transactions = []
  end

  def balance
    @transactions.map(&:amount).sum
  end

  def deposit(amount)
    @transactions << Transaction.new(amount)
  end

  def withdraw(amount)
    @transactions << Transaction.new(-amount)
  end

  def header
    puts '   date   || credit || debit || balance || '
    puts '-------------------------------------------'
  end

  def print_statement
    header
    @transactions.map(&:formate)
  end
end

Transaction class

class Transaction
  attr_reader :amount, :timestamp

  def initialize(amount)
    @amount = amount
    @timestamp = Time.now.strftime("%d/%m/%Y")
  end


  def formate
    if @amount > 0
      puts "#{@timestamp}||#{@amount}||  || Balance ||"
    elsif
      puts "#{@timestamp}||  ||#{@amount}|| Balance ||"
    end
  end
end

Upvotes: 2

Views: 93

Answers (3)

nPn
nPn

Reputation: 16728

I think you should have a reference to an account associated with each transaction. You can then use that reference to access the account info you want. Since it also seems like you would just have a list of transactions and not store a fixed balance, I think you will need a method to get the balance after any transaction.

First I added a balance_after method to Account to calculate the balance after any given transaction. It does this by looking up the index of the passed in transaction and then summing up the amounts of all transactions prior to the one passed in.

require_relative 'transaction'
 
 class Account

  attr_reader :transactions

  def initialize
    @transactions = []
  end

  def balance
    @transactions.map(&:amount).sum
  end

  # new method to get the balance after a given transaction
  def balance_after(transaction)
    index = transactions.find_index(transaction)
    transactions[0..index].map(&:amount).sum
  end

  def deposit(amount)
    @transactions << Transaction.new(amount, self)
  end

  def withdraw(amount)
    @transactions << Transaction.new(-amount, self)
  end

  def header
    puts '   date   || credit || debit || balance || '
    puts '-------------------------------------------'
  end

  def print_statement
    header
    @transactions.map(&:formate)
  end
end

Next I added the account info into the Transaction and a method that calls balance_after() method on the account reference, passing a reference to current transaction.

 class Transaction
  attr_reader :amount, :timestamp, :account

  def initialize(amount, account)
    @amount = amount
    @timestamp = Time.now.strftime("%d/%m/%Y")
    @account = account
  end

  def balance()
    @account.balance_after(self)
  end

  def formate
    if @amount > 0
      puts "#{@timestamp}||#{@amount}||  || #{balance} ||"
    elsif
      puts "#{@timestamp}||  ||#{@amount}|| #{balance} ||"
    end
  end
end

Upvotes: 1

Giuseppe Schembri
Giuseppe Schembri

Reputation: 857

There could be more than one solution.

I'll give you a simple example. But first let me congrats with you, I like the way you coded so far, very clean and focused on the public methods for each class, a clear and meaningful API.

What you are missing is what data structure you need to rapresent the problem.

In my solution I will use a Struct, you may use a Hash, or may be you could build some method in Transaction where you keep track of the balance. But I think that a partial balance is a matter of the Account class, so here it is a suggestion:

class Account

  attr_reader :transactions

  TransactionPartialBalance = Struct.new(:transaction, :balance_at_transaction)

  def initialize
    @transactions = []
    @transactions_with_balance = []
  end

  def balance
    @transactions.map(&:amount).sum
  end

  def deposit(amount)
    @transactions << Transaction.new(amount)
    @transactions_with_balance << TransactionPartialBalance.new(@transactions.last, balance)
  end

  def withdraw(amount)
    @transactions << Transaction.new(-amount)
    @transactions_with_balance << TransactionPartialBalance.new(@transactions.last, balance)
  end

  def header
    puts '   date   || credit || debit || balance || '
    puts '-------------------------------------------'
  end

  def print_transactions_balance
    p @transactions_with_balance
  end

  def print_statement
    header
    @transactions.map(&:formate)
  end
end

class Transaction
  attr_reader :amount, :timestamp

  def initialize(amount)
    @amount = amount
    @timestamp = Time.now.strftime("%d/%m/%Y")
  end


  def formate
    if @amount > 0
      puts "#{@timestamp}||#{@amount}||  || Balance ||"
    elsif
      puts "#{@timestamp}||  ||#{@amount}|| Balance ||"
    end
  end
end

if __FILE__ == $PROGRAM_NAME
  account = Account.new
  account.deposit(100)
  account.withdraw(50)
  account.balance
  account.print_transactions_balance
  # [#<struct Account::TransactionPartialBalance transaction=#<Transaction:0x0000562a986ecc38 @amount=100, @timestamp="10/01/2021">, balance_at_transaction=100>, 
  # #<struct Account::TransactionPartialBalance transaction=#<Transaction:0x0000562a986ecad0 @amount=-50, @timestamp="10/01/2021">, balance_at_transaction=50>]

end

Upvotes: 1

r4cc00n
r4cc00n

Reputation: 2117

Definitely, there are more that one way to call a method of a particular class within another class, the simple and shortest way (since you mentioned you are new coding I will only show you 2 ways to avoid confusions):

Let's say you are within your Account class, within a method you can create an instance of your Transaction object and call any function (in a public scope for simplicity of this example):

t = Transaction.new(...)
t.formate

This is similar to what you are doing when you write:

@transactions.map(&:formate)

Basically, that line above said for each instance of transaction in that list call the method format

The other easy way to achieve this is to define the method as a class method (static function in other languages like C# for example) something like:

class A
  def self.print_foo
     p 'Foo'
  end
end

With the above definition, you can do something like A.print_foo.

There are other ways to achieve this but I think these 2 are the basics.

NOTE: IF YOU ARE IN DIFFERENT FILES AND USING PLANE RUBY (NO AUTOMATIC FILE LOADER, YOU MAY NEED TO IMPORT/REQUIRE THE FILES, BUT THAT ALREADY HAS AN ANSWER HERE IN THE SITE 👌)

Hope the above helps to clarify! 👍

Upvotes: 1

Related Questions