2

I have a library that defines a base class that all other classes derive from. Rails, on initialization, should provide all of the classes from this library. Rails, however, doesn't recognize class methods inherited from my library class. Here is an example:

Model: app/models/mailing_address.rb:

require 'account_model'
class MailingAddress < AccountModel
  # class accessors, initializer and method
end

Library class: lib/account_model.rb

###################################
# AccountModel
# 
# This is a super class to be inherited by 
# the domain classes of this application.
# Responsible for api calls and 
# error handling.
##
class AccountModel
  class << self
    def get_all id = nil
     # class method implementation
    end
    ##
    # get id
    # fetch method, parses url provided by child class in path hash
    # and performs appropriate fetch
    # method returns class instance set by fetch
    def get id
      # class method implementation
    end

    def update_existing options
      # class method implementation
    end

    def create_new options
      #class method implementation
    end

    def delete_existing id
      # class method implementation
    end   
end

These methods wrap api calls using the HTTParty gem. On load, the methods get and get_all are recognized. However, create_new, and update_existing are not:

app/controllers/mailing_address_controller.rb:

def update
    @mailing_address = MailingAddress.update_existing params[:mailing_address]
   . . .
end

Throws the following error:

Processing by MailingAddressesController#update as JSON
. . .
Completed 500 Internal Server Error in 133ms

NoMethodError (undefined method `update_existing' for MailingAddress:Class):
  app/controllers/mailing_addresses_controller.rb:17:in `update

In Passenger, I need to reload tmp/restart.txt, in WEBRick, I need to restart the server.

I do not see this behavior in the IRB.

Here is what I've tried so far with no success:

  • Adding an initializer file in config/initializers
  • Adding require statements (as in the example) in each model class
  • Wrapping the library class in a module
  • renaming the troubling methods (update_existing => update, foo, etc)

I have never seen this behavior in a rails app before, I have another library class that works just fine.

I'm running: - ruby-2.1.1 - rails 4.03 - passenger 5.07

UPDATE:

While attempting to investigate this further, I uncovered yet another issue:

I added a class method to MailingAddress:

class MailingAddress < AccountModel
. . .
def self.debug_methods
  return self.methods
end

Which also throws a "MethodNotFound" exception. Since this does work in the rails console, but not in WEBRick or Passenger, I'm tempted that there is some server caching going on.

UPDATE

After shutting everything down and restarting, Now the situation is reversed:

-WEBRick processes the request successfully -Passenger processess the request successfull -Rails console throws an error:

Webrick and passenger:

Processing by MailingAddressesController#update as JSON
. . .
Completed 200 OK

Console:

MailingAddress.update_existing params
NoMethodError: undefined method `update_existing' for MailingAddress:Class

I'm guessing it's first come first serve as to whomever gets the loaded class.

config.autoload_paths is set correctly:

config.autoload_paths += %W(#{config.root}/lib)

LAST UPDATE The only workaround that seems to work is clobbering my tmp/ directory and restarting everything (Passenger need to have touch tmp/restart.txt ran).

This, however, is a crappy workaround. I say bug!

eggmatters
  • 1,130
  • 12
  • 28
  • `update_exsisting` looks like a typo in your second last update – fylooi Jul 10 '15 at 04:19
  • Thanks! Probably explains the error. Still get it though, when spelled correctly. – eggmatters Jul 10 '15 at 15:43
  • Are you able to see the same behavior in both environments by setting `config.cache_classes = true` in development? – zetetic Jul 10 '15 at 18:32
  • Hmm, setting `config.cache_classes = true` looks like it may have done the trick. I set it and restarted apache and it looks like the method was found. I'll have to do more thorough testing but let's keep our fingers crossed. – eggmatters Jul 10 '15 at 19:52
  • Still requires a server restart after the first method call fails. – eggmatters Jul 10 '15 at 20:02
  • Setting `config.action_controller.perform_caching = false` and bouncing the server seemed to work. Subsequent deploys are recognizing the method. – eggmatters Jul 10 '15 at 20:17

2 Answers2

0

You have to call that method on an instance of the class:

MailingAddress.new.update_existing params[:mailing_address]

or

@mailing_address.update_existing params[:mailing_address]

If you don't need update_existing to be an instance method you can move it outside of the self block.

Also, you might consider just putting your account_model.rb file in app/models. That way you won't have to require it at the top of mailing_address.rb

NM Pennypacker
  • 6,704
  • 11
  • 36
  • 38
  • Yeah, I wish that worked but to no avail. From the irb: `@mailing_address = MailingAddress.get 30` `@mailing_address.update_existing params` where params is a hash `NoMethodError: undefined method `update_existing' for #` – eggmatters Jul 09 '15 at 16:50
  • Right, but what object are you calling the method on? Does the first example work? You have to call the method on an instance of MailingAddress, so mailing_address = MailingAddress.new mailing_address.update_existing should work. – NM Pennypacker Jul 09 '15 at 16:53
  • The first example didn't work. But update existing is a class method so it should not be tied to a specific instance. the method `update_existing` performs an api update and then returns a call to `get` But, maybe this should not be implemented as a class method. – eggmatters Jul 09 '15 at 17:00
  • Additionally, from the IRB, I can call @mailing_address = MailingAddress.update_existing with valid parameters and get an expected response. This likely is an issue with rails registering class constants against some bizarre convention. Why would that work in the console and not in WEBRick or Passenger? It does work in Passenger but only after a manual restart. – eggmatters Jul 09 '15 at 17:06
  • Weird. It still doesn't work after moving the class into the models. Calling require or no. Thanks for tackling this tho. I'll dig in and get to the bottom of this. – eggmatters Jul 09 '15 at 17:13
  • Yeah, definitely post an answer if you figure it out. – NM Pennypacker Jul 09 '15 at 18:27
0

The server (Passenger, WEBRick) doesn't reliably load library classes as expected. This could be due to the fact that the library class has static methods as well as the derived classes. Those may not be accurately namespaced.

This behavior occurs when a change is made to the application (controller, model, etc.) Even though the library code may not change, the namespacing gets messed up. The console loads application state each time it is invoked where Servers possibly cache application state.

Clearing the tmp directory, and restarting passenger is a valid workaround. Any changes to the code would have to be treated as library changes.

eggmatters
  • 1,130
  • 12
  • 28