Bug in rails autoload_paths? - ruby-on-rails

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.

Related

Why does Ruby not throw an error in an instance where the class name and file name of the class are mismatched?

Why isn't the Ruby interpreter throwing a NameError in this instance here?
class OrangeTurtle
self.table_name = 'turtles'
end
Filename: orange_turtles.rb
This answer might sound like a cop out, but it doesn't throw an error because Ruby doesn't care even the slightest what your filenames are called.
e.g. in file asdfasdf.no_rb_ending_here we can have
#!/usr/bin/env ruby
module Something
class Test
def test
puts 'test'
end
end
end
class SomethingElse
def otherThings
puts 'haha'
end
end
Then to make things even weirder, I can have a separate file that modifies (monkey patches) the classes defined in that file.
in more_stuff.rb
#!/usr/bin/env ruby
require_relative 'asdfasdf.no_rb_ending_here'
module Something
class Test
def test2
test()
puts '2'
end
end
end
class SomethingElse
def moreThings
otherThings()
puts 'MOAR'
end
end
Something::Test.new.test2()
# test
# 2
SomethingElse.new.moreThings()
# haha
# MOAR
Ruby is pretty cool - you don't get errors for things that don't NEED to cause an error.
The name error, or uninitialized constant error only appears in Rails. The reason for that is, that active record (which is also a general design pattern) is mapping the tables in the database with the models (or with objects in general).
Active Record can only make that connection via the naming conventions for files and the classes.
As mentioned in the other answer, pure ruby doesn't need to comply with these conventions. However, it is a general rule to name the files like the classes they contain to have better organised code.

Reopening a namespaced class in ruby -- initialize overridden?

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

How to replace exiting class in Rails console?

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

What does :: do?

I have some inherited code that I am modifying. However, I am seeing something strange(to me).
I see some code like this:
::User.find_by_email(params[:user][:email]).update_attributes(:mag => 1)
I have never seen something like this(I am new to Ruby on Rails). What does this do and why doesn't my User.find_by_email(params[:user][:email]).update_attributes(:mag => 1) work? The error says something about the User constant.
I am using Rails 2.3.5 if that helps.
:: is a scope resolution operator, it effectively means "in the namespace", so ActiveRecord::Base means "Base, in the namespace of ActiveRecord"
A constant being resolved outside of any namespace means exactly what it sounds like - a constant not in any namespace at all.
It's used in places where code may be ambiguous without it:
module Document
class Table # Represents a data table
def setup
Table # Refers to the Document::Table class
::Table # Refers to the furniture class
end
end
end
class Table # Represents furniture
end
It makes sure to load the User model in the global namespace.
Imagine you have a global User model and another User model in your current module (Foo::User). By Calling ::User you make sure to get the global one.
Ruby uses (among other things) lexical scoping to find constant names. For example, if you have this code:
module Foo
class Bar
end
def self.get_bar
Bar.new
end
end
class Bar
end
The Foo.get_bar returns an instance of Foo::Bar. But if we put :: in front of a constant name, it forces Ruby to only look in the top level for the constant. So ::Bar always refers the top-level Bar class.
You will run into situations in Ruby where the way your code is being run will force you to use these 'absolute' constant references to get to the class you want.
You might find a lead here: What is Ruby's double-colon `::`?
The "::" operator is used to access Classes inside modules. That way you can also indirectly access methods. Example:
module Mathematics
class Adder
def Adder.add(operand_one, operand_two)
return operand_one + operand_two
end
end
end
You access this way:
puts “2 + 3 = “ + Mathematics::Adder.add(2, 3).to_s

Rails Reserved Class Names

I tried creating a model called "class" (as in a graduating class of students), and encountered all kinds of problems. What are some other words or class names to avoid in Rails?
Some links I've found:
http://juicebar.wordpress.com/2007/05/30/reserved-words-in-rails/
http://railsforum.com/viewtopic.php?id=22242
This page has a very long list of words not to use:
https://reservedwords.herokuapp.com/words
Because 'class' comes up very commonly as a name with metaprogamming, I think the accepted ruby alternative is 'klass'. This is obviously a different context from your graduating class situation, but maybe still helpful.
You've got most of them there. Obviously, you also need to avoid the Ruby keywords:
alias and BEGIN begin break case class def defined
do else elsif END end ensure false for if
in module next nil not or redo rescue retry
return self super then true undef unless until when
while yield
(from http://www.zenspider.com/Languages/Ruby/QuickRef.html#4).
Also, don't name a model Transaction (but the generator warns about that!).
Class is a built-in ruby class. It is what classes is an instance of.
class Foo
end
puts Foo.class
# => Class
Overriding that blows up the entire object structure in Ruby.

Resources