Library class methods aren't loaded by rails - ruby-on-rails

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!

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

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.

Related

How to use cattr_accessor to define config in a Rails initializer

Given the following class (this is the actual class there is no other code)
class PageRepo
cattr_accessor :root_path
end
and initializer file
PageRepo.root_path = Rails.root.join("content")
When I run
rails console
PageRepo.root_path
=> PageRepo.root_path
=> #<Pathname:/Users/blah/my_rails_app/content/pages>
However when I try to access this in my rails controller the root_path value is nil. (I've inspected this with web_console.)
No other class subclasses or is a parent of the PageRepo class and I'm not setting the root_path to nil anywhere at class level or instance level after the initializer stage. I have restarted spring and my server multiple times to no avail.
Is there something I'm not aware of when it comes to either Rails initializers or cattr_accessor?
Update
I'd like to set the path like this because throughout my code I will be initialising a PageRepo instance and the path will not change. However, it may change across different environments. I.e. in development it the path will be different than that of the test and production environments. Whilst I could just do
def initialize
#root_path = ENV['ROOT_PATH']
end
I'd prefer to not force the programmer to use ENV VARS to do this and hence my attempt above.
My solution would be just using a class method
class PageRepo
self.root_path
Rails.root.join("content")
end
end
PageRepo.root_path would return #<Pathname:/Users/blah/my_rails_app/content/pages>.
You want to have #<Pathname:/Users/blah/my_rails_app/content/pages>? or this depends on the action?
I believe your initializer is not working
an interesting post, what you are trying to achieve is writing something like this in the class initializer or constructor
class PageRepo
self.initialize
self.root_path = Rails.root.join("content")
end
end
but I never saw self.initialize being used with rails. so I believe the first approach is better.

How to properly customize/extend ApplicationRecord behavior

I'm trying to add some customized behavior to all my Rails models globally without having to include/extend within each model file.
I tried doing this inside an initializer in config/initializers/virtual_column.rb:
module VirtualColumns
#becomes a class-level function on any ApplicationRecord
def virtual_column ...
...
end
end
module VirtualColumnChecker
#becomes an instance function on any ApplicationRecord
def check_virtual_columns!
...
end
end
#I've tried making this work in the on_load callback, but the callback doesn't seem to execute?
#ApplicationSupport.on_load(:active_record) do
ApplicationRecord.extend VirtualColumns
ApplicationRecord.include VirtualColumnChecker
#end
What ends up happening is it works when I start the server, but after I make a change to one of my app files, the rails engine's hot reloading doesn't re-run the initializer. The class-level call to virtual_column throws a "no function defined" error. Restarting the server makes it work again. I'm sure this is normal behavior though; I'm thinking the initializers should just run once at startup, and not when the engine hot reloads.
My question is:
Where is the proper place to do what I'm trying to do?

Extending Rails 4 engine models with concerns

I am trying to extend a model from engine 1 with a concern from engine 2 through an app initializer, but I'm getting some weird behavior, here's what I've got:
Concern
module Engine2
module Concerns
module MyConcern
extend ActiveSupport::Concern
included do
puts "Concern included!"
end
def jump
puts 'Jumping!!!!'
end
end
end
end
Initializer
require 'engine2/my_concern'
module Engine1
class Member
include Engine2::Concerns::MyConcern
end
end
When I boot up the application, I see as expect the Concern included! message in the console, and the Member class can call the method jump, but as soon as I change any code in the host app I get the following error:
NoMethodError (undefined method 'jump' for #<Engine1::Member:0x007fe7533b4f10>)
and I have to reload the server, then it works fine again until I make another change in the host app, then it throws the error again, why is this happening and how can I avoid it?
Is there a better place where I should perform the class opening to include the concern instead of the initializer?
So I finally figured it out, basically what happens is that in development mode every model is reloaded on every code change but the initializers are only ran once at startup of the server, so once the code changes in the controller, the model is reloaded but doesn't include the concern anymore and therefore breaks.
I solved it by moving the code of the initializer to a to_prepare block in the application.rb.
For those who don't know, to_prepare adds a preparation callback that will run before every request in development mode, or before the first request in production.

Modifying a ruby class doesn't work as expected when running Spork

I have a plain Ruby class in my Rails app that I'm reopening in a test environment. It basically looks like
class A
def get_dependency
B
end
... some other methods ...
end
And in my test environment in cucumber (in a file loaded from features/env.rb) (and a similar place for rspec) I do
class A
def get_dependency
MockedB
end
end
This works fine in normal runs, but when I have Spork running, it fails strangely. Class A's get_dependency method is overwritten properly, but all its other public methods are now missing. Any ideas?
I'm assuming this is related to load order somehow, but I didn't get any changes when I moved the require for my file out of the preload section of Spork.
This isn't a great answer, but it's a workaround. Instead of reopening the class I just modified a singleton instance. The code is basically the same, except I added an instance method on A:
class A
def instance
##instance ||= A.new
end
end
Then in my test code I modified the instance
instance = A.instance
def instance.get_dependency
MockedB
end
And I just had to ensure that my actual code was always calling A.instance instead of A.new.
One possible scenario is that A is set to get autoloaded, but when you define the override for it in your cucumber environment, you do so before it has been autoloaded; since A now exists, it will never get autoloaded.
A possible solution, which invokes the autoloader before overriding the method is this:
A.class_exec do
def get_dependency
MockedB
end
end
It will raise a ConstMissing if A cannot be autoloaded at that point (perhaps the autoloaders have not yet been set up).

override lib module method for specific rails environment

I've got a library module I'd like to override based on the rails environment I'm running in
Module is located in lib/package/my_module.rb:
module Package
module MyModule
puts "Defining original module"
def foo
puts "This is the original foo"
end
end
end
I have been able to partially solve with the info at Overriding a module method from a gem in Rails - specifically, in my environments/dev_stub.rb:
Package::MyModule.module_eval do
puts "Defining override"
def foo
puts "This is foo override"
end
end
(The other solution at that link seems to cause errors when rails tries to lookup other classes related to package)
Now, this seems to get me most of the way there, and works if I set
config.cache_classes = true
...but I want to use this as a stub development environment, and the comment recommendation on this value for a dev environment is to use false... in which case the override only works the first time the module is included, and any subsequent times, it uses the original.
My question: Am I going about this the right way? I could hack up the lib module itself to conditionally override based on RAILS_ENV, but I'd like to keep it cleaner than that...
Edit
My use case for this is to reference it from a controller function. If I have
class SomethingController < ApplicationController
def show
Package::MyModule.foo
end
end
and config.cache_classes=false (which I ideally want since it is a development environment), and access the action through my web browser (http://localhost/something/show) then the first time I hit it, my override is loaded and it works, but the second and any subsequent times, the original library class is reloaded (outputs "Defining original module" on my console without "Defining override"), and the override is lost.
Another alternative I tried was add something like config.load_paths += %W( #{RAILS_ROOT}/lib_patch/#{RAILS_ENV}) to environment.rb - but defining the same module/class didn't quite work without putting in an explicit hook in the original library to basically load the patch if it existed
Edit 2 (in response to #apneadiving answer)
I've tried doing this without module_eval, and just using the following in development_stub.rb:
require 'package/my_module'
module Package
module MyModule
puts "Defining override"
def foo
puts "This is foo override"
end
end
end
The problem I initially had with doing this is that Rails no longer automatically finds all content in my lib directory, and I need to sprinkle 'require' statements throughout all other lib files (and my controllers that reference the libs) to cover all of their dependencies. Although this is all done, it does work, but it also has a similar effect as config.cache_classes=true does, in that all the lib classes are not reloaded on change, even in my regular development environment that does not have a monkey-patch (since all the 'require' statements are added).
Setting config.cache_classes=true in dev_stub.rb and using module_eval to define the patch as described in the question seems the way to go for what the goal is here - to create an environment specific patch for a module that doesn't impact the other environments in both code path and Rails class loading behavior.
you could simply override the module and it's instance without module_eval.
I guess your module is included as a Mixin and it's methods aren't impacted by your monkey patch.
That's where alias_method_chain comes in action.
Look at this great article to get how you should use it to fit your needs.

Resources