In console:
class Logger
end
l = Logger.new
Throws error:ArgumentError: wrong number of arguments (0 for 1)
from /home/zzz/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/logger.rb:268:in 'initialize'
Why is it using the Logger in /home/zzz/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/logger.rb?
I'll try to answer to your second question. So, why is Ruby using some other Logger class and not the one you tried to define yourself?
One of the fundamental features of Ruby is re-opening of classes. Let's say you have some class that is already defined and loaded in your app:
class A
def foo
puts 'foo'
end
end
A.new.foo
#=> foo
If after that Ruby interpreter encounters something like:
class A
def bar
puts 'bar'
end
end
it doesn't redefine class A, but simply appends this definition to the previous one. In the result already defined class A gets new instance method bar:
A.new.foo # still works
#=> foo
A.new.bar # new method
#=> bar
Because of the way Ruby handles methods calling, all instances of class A that were initialized before the second definition (actually, re-opening) of class A also get this new method bar. So every time you reopen a class you add new functionality to the class itself and to all previously initialized instances of this class.
Classes reopening also allows rewriting methods of an existing class:
class A
def foo
puts 'new foo'
end
end
A.new.foo
#=> new_foo
With that feature in mind and the fact that Rails has already loaded standard Logger class for you, your definition only reopens the class, but doesn't even change anything.
That class was already loaded, presumably because rails was using it: you haven't redefined the class, you were merely re-opening it.
You could remove the existing class
Object.send(:remove_const, :Logger)
In this case the class formerly known as Logger still exists, it just isn't bound to the constant Logger any more, so when you do
class Logger
end
you'll be creating a new class not reopening the old one. You may of course end up breaking code that assumed the presence of the old Logger class.
If you're doing this in tests, you may be interested in the new constant stubbing in rspec 2.11
Related
I am trying to figure this out on my journey to rails and ruby's deeper understanding.
What i know is that we can check if a method is defined in a ruby class by calling: method_defined? on the object. As all classes are also objects of class Class we can do same for class methods. For example if class Foo defines bar class method, this is what happens:
Foo.method_defined? :bar #-> true
But when applying same on models which are inherited from ActiveRecord::Base(directly or indirectly). this results in:
User.method_defined? :all #-> false
User.method_defined? :count #-> false
I can see the all method defined here, i am struggling to match the dots and make sense of whats going on. And how these methods work on models when they are not implemented as class methods neither is there any funky business of method_missing is going on(as it seems).
While on it, if i can get same explanation for instance methods which rails adds for model objects, like name method in User.first.name(assuming user table has name column). Would be a plus.
Lastly, some word on how to remove one of these methods if we ever need to.
EXTRA: If i can also get to know how to reset the User class to have the method defined again after removing, like if i remove count method with the suggestion from comments: User.instance_eval { undef :count } i also want to be able to redefine this back. Or kind of reset the User class. load 'app/models/user.rb' does not do the job here.
Update: I figured out how to reset the class after undefining a method in the eigenclass of model User before doing load 'app/models/user.rb' i had to explicitly do Object.send(:remove_const, :User) so ruby removes the class entirely than do the load thing.
Still struggling to digest all this and especially how the rails implementation works.
No magic here
module X
def test
end
end
class A
extend X
end
class B
include X
end
A.method_defined? :test #false
B.method_defined? :test #true
so it's not defined because it's a class method and class methods are defined in the singleton class.
method_defined? check if the method is defined in the class or its ancestors only.
B.ancestors #[B, X, Object, Kernel, BasicObject]
A.ancestors #[A, Object, Kernel, BasicObject]
so simply because it's a class method
UPDATE: Adding more trace to How all is defined
the method all is defined as mentioned in https://github.com/rails/rails/blob/b9ca94caea2ca6a6cc09abaffaad67b447134079/activerecord/lib/active_record/scoping/named.rb
this module extends ActiveSupport::Concern which mean if you included this module the methods in ClassMethods will be added as class methods to the includer (more about this https://api.rubyonrails.org/classes/ActiveSupport/Concern.html)
in the active record entry point here https://github.com/rails/rails/blob/b9ca94caea2ca6a6cc09abaffaad67b447134079/activerecord/lib/active_record.rb#L151 the module Named is autoloaded inside Scoping module which resulted in having module called ActiveRecord::Scoping::Named the module mentioned above
here in the base class, the Scoping module is included https://github.com/rails/rails/blob/b9ca94caea2ca6a6cc09abaffaad67b447134079/activerecord/lib/active_record/base.rb#L298 which define all as class method
so it's similar to the simple code above but using some of ActiveSupport magic like autoloading , egarloading and concerns.
We can remove a method using
User.instance_eval { undef :count }
and can redefine if its in parent class using
User.instance_eval do
def count
super
end
end
Hope it would help you
Not sure, but. Method method_defined? cares about instance public methods. Instead, if you want to check is any object responds to a method (as a class it's also object) use respond_to?.
I've got a class in a namespace with a few methods
module Foo
module Bar
class Baz
def initialize(arg1, arg2, arg3)
# do stuff
end
def delete
File.delete(#path)
end
end
end
end
In my test environment, I don't want delete to delete any files, so in a TestHelper, I do this
class Foo::Bar::Baz
def delete
puts "no delete in test"
end
end
When I initialize this class this in my test, I get ArgumentError: wrong number of arguments (3 for 0). That is, the initialize method of Baz is gone. And to be sure, if I take a look at self in my test helper, there are no methods defined at all for Baz. It's been completely overridden by the class keyword.
I can make it work by using class_eval instead of class, i,e.
Foo::Bar::Baz.class_eval do
def delete
# etc
end
end
My question is, what is the difference? Why does the latter work but not the former?
I could be wrong, but I think you're accidentally breaking the autoloader. Here's what I think is happening in your working case (using .class_eval):
Something, somewhere, loads code that defines Foo::Bar (you'd be getting other errors if this wasn't happening)
Test code is parsed; explicitly requires TestHelper
TestHelper references Foo::Bar::Baz, which does not exist
autoloader finds and loads foo/bar/baz.rb
TestHelper runs class_eval and redefines #delete
Test code runs
And here's my guess at the non-working case:
Again, something, somewhere, loads code that defines Foo::Bar
Test code is parsed; explicitly requires TestHelper
TestHelper creates Foo::Bar::Baz, since it didn't already exist
Test code runs
Notice in the second case the autoloader was never triggered, so your actual class definition is never loaded.
I'm not sure the best way to solve this. You could do an explicit require in your test, or just reference the class in your helper before redefining it:
Foo::Bar::Baz # trigger autoloading before we muck with the definition
class Foo::Bar::Baz
def delete
puts "no delete in test"
end
end
Let's say I have two classes with the same name ("Bar") in different modules (the global namespace / no namespace and "Foo") in a Rails application.
Both classes live in "app/models/bar.rb" or in "app/models/foo/bar.rb" respectively and are thereby autoloaded by rails.
# "app/models/foo/bar.rb"
module Foo
class Bar
def self.some_method
end
end
end
# "app/models/bar.rb"
class Bar
end
I have another class in the Foo namespace that uses the Bar class in a class method.
module Foo
class Qux
class << self
def something
Bar.some_method # This raises a NoMethod error because
# it uses the Bar defined in the global namespace
# and not the one in Foo
end
end
end
end
Rails autoloading tries to load Bar using the current classes name, which is nil in the singleton class, and defaults to "Object".
klass_name = name.presence || "Object" # from active_support/dependencies.rb
This causes Rails to load Bar from "app/models/bar.rb" instead of "app/models/foo/bar.rb".
If I define Qux.something with def self.something then .name is "Foo::Qux" instead of nil and the autoloading works correctly.
I see 3 options for at the moment to work around this problem:
1) Rename one of the Bar classes
2) use the self. syntax everywhere
3) Namespace Bar with Foo::Bar explicitly everywhere
I don't like any of these options because:
1) Bar is just the name that fits the best
2) class << self is perfectly fine by itself and used by most ruby developers, so the chance is high that the next poor guy stumbles over the same issues again soon
3) Same as 2), it doesn't really fix the underlying "Problem" and somebody will waste some time in the future to figure out why his code doesn't work as expected.
Is there another, better option you can think of?
I am encountering a strange bug in my code. I have a rails application with the following two files in the lib:
lib/module_one/module_two/class_one.rb
module ModuleOne
module Moduletwo
class ClassOne
class << self
def test
puts 'Class one'
ClassTwo.test
end
end
end
end
end
and
lib/module_one/module_two/class_two.rb
module ModuleOne
module ModuleTwo
class ClassTwo
def self.test
puts 'Class two'
end
end
end
end
Now my problem is, that when I go into the console and write:
ModuleOne::ModuleTwo::ClassOne.test
it throws the following: NameError: uninitialized constant ClassTwo
The strange thing is, that the problem seems to be connected to the use of class << self instead of self.method. If I change the class_one.rb file like this it works!:
module ModuleOne
module ModuleTwo
class ClassOne
def self.test
puts 'Class one'
ClassTwo.test
end
end
end
end
Im loading the files in application.rb like this:
config.autoload_paths += %W(#{config.root}/lib)
Is this a bug in rails, or is it just me getting something all wrong?
Im using rails 3.1.3 btw
Constant lookup
So first off, the basic process for constant resolution is that ruby first searches the lexical scope of the receiver (the class or module enclosing the reference) for ClassTwo and when it can't find it, it goes up a level (Module.nesting returns this search path) and so on. In your case that means looking for ModuleOne::ModuleTwo::ClassOne:ClassTwo, then ModuleOne::ModuleTwo::ClassTwo, then ModuleOne::ClassTwo and so on.
If that fails the ruby looks in the inheritance hierarchy of the enclosing class/module (e.g. has the superclass of ClassOne defined something. The ancestors method on module returns this search path. Lastly, the top level constants are searched.
Rails autoloading
Back to rails's magic loading. Here a const_missing hook is added by rails that is called when ruby can't find the class, which basically tries to replicate this search logic, seeing at each step whether a file could have been loaded which would contain the missing constant.
Ideally ruby would pass the search path (i.e. the nesting) to search through, but unfortunately it doesn't: when you reference ClassTwo, const_missing gets called with just 'ClassTwo'.
Rails guesses the nesting by prepending that with the name of the class on which const_missing is being called (i.e. the class enclosing the access to the constant). For example, in your second example it ends up with ModuleOne::ModuleTwo::ClassOne::ClassTwo. You can see this easily enough by defining const_missing to log what it's called with
class Object
def self.const_missing missing_name
puts "qualified name is #{self.name}::#{missing_name}"
super
end
end
Rails then strips off 'ClassOne' and tries ModuleOne::ModuleTwo::ClassTwo and so on up the chain.
So why does class << self make a difference? If you repeat your first case with the const_missing logging you'd see that the logged qualified name is now just ::ClassTwo. const_missing is now being called on ClassOne's metaclass, and because class << self hasn't been assigned to a constant, it has no name and thus rails' attempt to fudge the nesting doesn't work.
This opens the door to a horrible workaround:
module ModuleOne
module ModuleTwo
class ClassOne
class << self
def test
puts 'Class one'
ClassTwo.test
end
end
FOO = class << self; self; end
end
end
end
Because the class that const_missing gets called on now has a name (ModuleOne::ModuleTwo::ClassOne::FOO) rails' workaround now works.
Dave's workaround works I think because const_missing gets called on ModuleOne::ModuleTwo::ClassOne rather than the anonymous eigenclass/metaclass.
The real fix would be for ruby to pass const_missing a nesting. There is a bug logged against ruby to this effect although it has been open for a long time. So yes, this could be considered a bug in the magic loading stuff (there are other edge cases) but the underlying reason is a weakness in the ruby api that forces the use of brittle workarounds.
(Only a partial answer, but need formatting.)
It's because of how class << self works.
For example, if you change it to:
class << self
def test
self::ClassTwo.test
end
end
it works fine.
Edit; too long for reasonable comment.
I'm poking around a bit... On an intuitive level it makes sense to me, I'm just not sure why yet. Don't know if I knew a real reason once, or if I'm just making it up.
I'm not sure why self seems to refer to the module, though; the "Programming Ruby 1.9" book doesn't go in to enough depth on the class << semantics. I'll tweet something and refer to this question and someone smarter will create a real answer.
I fired up irb, and typed:
class Point
end
and then I typed that again, but added some other things.
Irb didn't complain that I was defining a class that already exists.
Actually you didn't redefine the Point class, you reopened it. A little code snippet to illustrate the difference:
class Point
def foo
end
end
class Point
def bar
end
end
Now Point has two methods: foo and bar. So the second definition of Point did not replace the previous definition, it added to it. This is possible in ruby scripts as well as in irb (it's also possible with classes from the standard library, not just your own).
It is also possible to really redefine classes, by using remove_const to remove the previous binding of the class name first:
class Point
def foo
end
end
Object.send(:remove_const, :Point)
class Point
def bar
end
end
Point.instance_methods(false) #=> ["bar"]
In Ruby, you can always add methods to an existing class, even if its a core one:
class String
def bar
"bar"
end
end
"foo".bar # => "bar"
This feature is called "Open Classes." It's a great feature, but you should be careful: use it carelessly and you will be patching like a monkey.