0

I'm working with a massive legacy code base, so I am looking for advice concerning this particular issue, please, not suggestions of better high-level implementations.

A simplified version of what I'm working with:

class Order < ActiveRecord::Base
   has_many :line_items
   #other stuff

   def balance
       #some definition
   end
end

class LineItem < ActiveRecord::Base
   belongs_to :order
   #other stuff
end

module Concerns
  module LineItems
    module Aggregates
      extend ActiveSupport::Concern
      #stuff

      def balance
          #some other definition
      end

    end
   end
end

Order has a method called 'balance,' and a module of LineItem also has a method called 'balance.' It seems that most of the time (in most places in the code base), when specific_line_item.balance is called, it used the method definition under the LineItem module, but there are a couple of places where it instead calls the method from Order.

Is there any way in Ruby/Rails to specify on method call which of these two I'd like to use? OR is there probably something else going on here because Ruby doesn't have method overloading, so the problem I'm describing here isn't possible?

All relevant cases where either method is called are coming from a line_item (i.e. specific_line_item.balance), so I would think it would always choose the method closer to home, rather than making the associative jump and calling Order's 'balance' method without being told to.

EDIT: Thanks for the responses! It seems I wasn't clear enough with my question. I understand the difference between

Order.first.balance

and

LineItem.first.balance

and that the balance method being called is the one defined within the class for that object. In the situation I'm describing, I observed, in the actual live app environment, that at a place in the code where

LineItem.find(some_id).balance

was called it output not the result that would be computed by the LineItem 'balance' method, but the one from the Order class.

So I had hoped to learn that there's some ruby quirk that might have an object call an associate's method of the same name under some conditions, rather than it's own. But I'm thinking that's not possible, so there's probably something else going on under the covers specific to this situation.

volx757
  • 163
  • 1
  • 2
  • 19
  • 1
    I cannot see any reason why a line item would call `Order#balance` unless the `specific_line_item` happens to be an `Order`. Ruby uses inheritance chains to find the method and these chains run upward to `Object` since `LineItem` does not inherit from `Order` unless you have a delegation somewhere that is not shown there would be no way for it to call this method. – engineersmnky Oct 12 '15 at 16:27
  • Ok, that's what I thought, just wanted to be sure, thanks. – volx757 Oct 12 '15 at 17:43

1 Answers1

0

Firstly, ActiveRecord::Concern can change a lot of behaviour and you've left out a lot of code, most crucially, I don't know where it's being injected, but I can make an educated guess.

For a Concern's methods to be available a given object, it must be include'd in the object's class's body.

If you have access to an instance of the Order object, at any point you can call the balance method:

order = Orders.last         # grab the last order in your database
order.balance               # this will call Order#balance

And if you have the Order then you can also get the LineItem:

order.line_items.first.balance    # should call the Concerns:: LineItems::Aggregates#balance

You can open up a Rails console (with rails console) and run the above code to see if it works as you expect. You'll need a working database to get meaningful orders and balances, and you might need to poke around to find a completed order, but Ruby is all about exploration and a REPL is the place to go.

I'd also grep (or ag or ack) the codebase looking for calls to balance maybe doing something like grep -r "(^|\s)\w+\.balance" *, what you want to look for is the word before .balance, that is the receiver of the "balance" message, if that receiver is an Order object then it will call Order#balance and if it is a LineItem object then it will call Concerns:: LineItems::Aggregates#balance instead.

I get the feeling you're not familiar with Ruby's paradigm, and if that's the case then an example might help.

Let's define two simple Ruby objects:

class Doorman
  def greet
    puts "Good day to you sir!"
  end
end

class Bartender
  def greet
    puts "What are you drinking?"
  end
end

Doorman and Bartender both have a greet method, and which is called depends on the object we call greet on.

# Here we instantiate one of each
a_doorman = Doorman.new
a_bartender = Bartender.new

a_doorman.greet      # outputs "Good day to you sir!"
a_bartender.greet    # outputs "What are you drinking?"

We're still using a method called greet but the receiver is what determines which is called.

Ruby is a "message passing language" and each "method" is not a function but it's a message that is passed to an object and handled by that object.


References

Anthony Michael Cook
  • 1,082
  • 10
  • 16
  • Hey, balance can be called from a LineItem or from an Order, but not on the collection of line_items belonging to an Order (order.line_items.balance). In fact, it is called from both Order and this definition in the Aggregates module, but depending on from where it is called, rails chooses one or the other. What I can't figure out is what informs this decision? Although it sounds like the behavior I'm seeing is probably caused by something else in the code, as ruby doesn't support method overloading and there is no inheritance between Order and LineItem. – volx757 Oct 12 '15 at 17:45
  • I've updated the answer with this new information. It also seems like the question you should be asking is how message passing works in Ruby, like this question: http://stackoverflow.com/a/155655/1255156 – Anthony Michael Cook Oct 12 '15 at 18:29
  • 1
    Thanks. Yeah, it looks like there might be another `balance` method hidden in your codebase, using tools like `pry` you can track down where it's coming from. Check out the `ancestors` of the object that's giving the wrong `balance`, something might've been hijacked. It's one of the rough aspects of a highly dynamic language. – Anthony Michael Cook Oct 14 '15 at 00:20