I am using a plugin in Rails, and I call its methods without problems:
plugin_module::class_inside_module.method_a(...)
I want to re-open the class_inside_module and add a new method, I tried in many different ways. I can't figure out why in this way doesn't work:
class plugin_module::class_inside_module
def new_method
puts 'new method'
end
end
I get the error: uninitialized constant plugin_module, but how is possible if I can call without problem plugin_module::class_inside_module.any_methods ?
Do you know why I get that error ? why "uninitialized constant" ? (it is a class declaration :-O )
Do you have any ideas how I can add a new methods in a class inside a module (that is part of a plugin) ?
Thank you,
Alessandro
If you have written your class and module-names like you did, so plugin_module instead of PluginModule this is against ruby/rails standards, and rails will not be able to automatically find the class and module.
If you write something like
module MyModule
class MyClass
end
end
Rails will expect this file to be located in lib\my_module\my_class.
But this can always easily be overwritten by explicitly doing a require.
So in your case, when you write
module plugin_module::class_inside_module
Rails will not know where to find the module plugin_module.
This way of writing only works if module plugin_module is previously defined (and loaded).
So either add the correct require, or rename your modules to standard rails naming, or write it as follows:
module plugin_module
class class_inside_module
This way will also work, because now the order no longer matters.
If the module is not known yet, this will define the module as well.
Either you are re-opening the class, or you define it first (and the actual definition will actually reopen it).
Hope this helps.
Have you tried reopening the module that's wrapping the class, rather than relying on ::?
module plugin_module
class class_inside_module
def new_method
puts 'new_method'
end
end
end
By the way, you know that the proper name for modules and classes is use CamelCase with a capital first letter?
module PluginModule
class ClassInsideModule
def new_method
puts 'new_method'
end
end
end
Related
I'm running Rails 3.2.7,
I have a folder '/app/jobs'
and the following in my 'config/application.rb' file
config.autoload_paths += %W(#{Rails.root}/app/jobs)
And everything is okay.
However if I want to namespace my classes eg
class Jobs::UpdateGameStatus
#methods etc
end
Rather than
class UpdateGameStatus
#methods etc
end
Then I get
uninitialized constant Jobs (NameError)
It's not the end of the world but I'd love to know why...
I fixed it in the end, wrapping all my classes with a Jobs module was what I needed to do.
my files were located in 'app/jobs'
and looked like this
module Jobs
class JobName
#methods etc
end
end
and are used like so
Jobs::JobName.method(args)
I know you have already sorted this out, and this is old, but in ruby, it is also possible to declare the namespaced class directly using class Jobs::JobName. It's a little less typing, and achieves the same result.
Edit: As #D-side pointed out, Jobs has to already be defined. My own code that uses this is based around STI, which presumes that the previous class/module I am extending already exists.
My application code is structured as shown below in Rails 3.2. If I go into the Rails console and enter Foo::Bar::FooBar it will return this warning:
warning: toplevel constant FooBar referenced by Foo::Bar::FooBar
Application code and files they are located in:
# app/models/foo/bar/foo_bar.rb
module Foo
class Bar
class FooBar
end
end
end
# app/models/foo/bar.rb
module Foo
class Bar
end
end
# app/models/foo_bar.rb
class FooBar
end
My autoload paths have not been changed from the Rails defaults.
One way I've been able to fix the issue has been to add the following code to Foo::Bar::FooBar. However, it feels dirty and I was wondering if there was a configuration option or some other thing I'm doing wrong that would fix the issue.
# app/models/foo/bar/foo_bar.rb
module Foo
# This line of code removes the warning and makes class methods execute
# on the Foo::Bar::FooBar class instead of the FooBar class.
class Bar; end
class Bar
class FooBar
end
end
end
The basic problem is that you're reusing the same name for different classes, in an overlapping scope. You might be able to do something clever to make Ruby resolve the constant name in the way you want, but this kind of "solution" is fundamentally brittle. (What if a new version of Ruby makes a tiny change to how constants are looked up, or you move to another Ruby implementation?)
Why don't you just namespace the top-level FooBar model class within a Module? (You'll have to move the file into a subdirectory with the same name as the Module.)
I have a file under /lib with its own method.
# lib/file.rb
class File < ApplicationController
def my_method
...
end
end
However I can't reach the method through the console
ruby-1.9.2-p290 :044 > File.my_method
NoMethodError: undefined method `my_method' for File:Class
Any idea how?
my_method is an instance method of the File class. It means that you can call it only on the instance of the File class.
file = File.new
file.my_method
You can declare my_method as class method using def self.my_method syntax.
class File < ApplicationController
def self.my_method
...
end
end
File.my_method
But in class methods you can't use instance variables of the File object.
You're trying to call my_method as a class method, but you've defined it as an instance method.
You should either define it as def self.my_method, or create an instance of the controller to call it as an instance method.
In addition, you are going to run into problems for a couple of reasons - (1) Rails expects controllers to be named like FilesController, and (2) File is a class in the standard library. I would encourage you to change the class name to FilesController, and rename the file itself to files_controller.rb to prevent both issues.
Well... there are several interesting things going on with this example. The first would be that this class name is call File which is already defined in Ruby.
That is most likely why when you are in the console you didn't get an undefined class error. Since my_method is not defined on Ruby's File class, this is why you are seeing undefined method.
Now to your question. I would try naming your class something different first and trying again from lib. I believe it should be loaded by default again with the rails environment. For a version or two that functionality was taken out but I want to say it's back in. If not, just go into your config/application.rb file and look for a declaration along the lines of config.autoload_paths. Add the lib directory there and you should be good to go.
Lastly, is there a reason you want a controller in lib?
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.
I successfully created a gem having some classes and modules to be'ing able to to something like that in ANY kind of class in a Rails project:
class AnyRubyOrActiveModelClass
acts_as_anything [:foo, :bar]
def foo
.. do some foo
end
def self.bar
.. do some bar
end
end
To do so I created a Module in my gem that looked something like that:
module InstanceMagic
class << self.class.superclass
define_method(:acts_as_anything) do |methods|
self.class_eval do
include ClassMagic
.. do some alias_method with given methods
end
end
end
This module successfully aliased my method :foo from the example above, the second module ClassMagic did the same for my :bar class method (following the advice from here).
In a testproject that approach worked very well. In a real life project it led to interference with another gem taking a - maybe similar - approach. This gem complained about missing methods in a class even when I only integrated my gem into the project - not even integrated acts_as_anything into the class.
So I broke down my code to only that:
module InstanceMagic
class << self.class.superclass
define_method(:acts_as_anything) do |methods|
#really empty here
end
end
As a result the other gem still breaks.
I used class << self.class.superclass to explicitly extend Object, so that even non ActiveSomething classes but ALL classes are affected and my acts_as_anything is available. So I remain with three questions.
Why do the last 5 lines of code break another gem and making it complain about missing methods it's trying to dynamically create? I would like to understand.
Is there a better approach to achieve my goal?
When I use method_added and singleton_method_added (what I actually do inside my modules), should I look for these methods whether they already exist, alias the "original" ones, insert my ones and call the original ones after I have done my job?
Knowing this is a lot I still hope someone can point me into the right direction.
Thank you.
Felix
The only thing I can think of is some sort of order dependency (long shot). Try to put your code in an initializer in the rails app and see if it still causes the same problems.