I'm trying to apply typing to a repo that uses the Sequel gem. It's got a non-standard Ruby syntax for a particular use case: initializing a model when a table name doesn't follow a convention
Example:
# This is necessary to get around
# "Superclasses must only contain constant literals"
FooParent = Sequel::Model(:foo_bars)
class Foo < FooParent
end
I've been able to tell Sorbet that certain attributes exist on my Foo model with this code in sorbet/rbi/shims/foo.rbi
# Raises an error:
# "Redefining constant `FooModelParent`"
class FooParent < Sequel::Model
end
This makes errors such as "Method join does not exist on T.class_of(Foo)7003" go away, but I'm not sure how to get rid of this "Redefining Constant" error or if there's a better way. Can someone help me get rid of this error, or solve the inheritance typing problem a different way?
Related
While debugging an unrelated issue in rspec, I'm coming across issues with constant loading.
The setup is as follows:
# app/models/foo.rb
class Foo << ApplicationRecord
include Foo::Searchable
end
# app/models/foo/searchable.rb
module Foo::Searchable
extend ActiveSupport::Concern
included do
#yada yada
end
end
I received the following error while debugging. NameError: uninitialized constant #<Class:0x00007fadd32ea580>::Searchable
Changing the naming to Foos::Searchable with corresponding folder move does fix the issue but I would like to understand what is actually happening.
Rails 6.0.3.1 Ruby 2.6.6
As well as using << instead of < you have fallen victim to really common issue due to missusing the scope resolution operator ::. It should not be used when declaring nested classes or modules as it leads to the wrong module nesting and will lead to a missing constant error unless the module which you are nesting in is already loaded.
You should always explitly declare nested modules/classes:
class Foo < ApplicationRecord
module Searchable
extend ActiveSupport::Concern
included do
#yada yada
end
end
end
The reason this issue is popping up everywhere after the transition to Rails 6 is that the old classic autoloader overloaded Module#const_missing and glassed over these bugs as it would autoload Foo.
Zeitwork which replaces the classic autoloader instead uses Module#autoload which is a relatively new addition to Ruby and a lot less hacky.
This turns out to be caused by incompatibility with Byebug. Byebug stops the events Zeitwerk uses for autoloading to fire off in a debugging session. The issue is described here.
The << was a typo and would not result in that error.
Perhaps you should replace << with <. You inherit, not redirect
class Foo < ApplicationRecord
include Foo::Searchable
end
I found some unexpected (to me) behavior in Ruby. Is this behavior a bug or a feature? I stumbled on this situation while writing my project.
To put it as succinctly as I can: if a class' name ends with ::File and that class inherits from another class, then the usual File class isn't used with "File", but instead the current class is used.
(Yes, that's as succinct as I could figure out how to make it.)
Consider the following code, which produces the [feature|bug]
class MyClass
end
class MyClass::File < MyClass
def check
File.exist?('./whatever.txt')
end
end
myfile = MyClass::File.new
myfile.check
That code produces the following error:
./dev.rb:8:in `check': undefined method `exist?' for MyClass::File:Class (NoMethodError)
Did you mean? exit
exit!
from ./dev.rb:13:in `<main>'
What's weird (to me, anyway) is that the error doesn't happen if MyClass::File doesn't inherit from MyClass. That is, if you remove the "< MyClass" part then there's no error.
Now, I can just hear some programmers muttering over their latte: you shouldn't end a class name with ::File, that's just bad form. Well, I really want my class called ::File because that's the type of object it represents. I just have this thing that I like to give classes meaningful names.
I found a work around by calling Object::File instead of just File:
Object::File.exist?('./whatever.txt')
I don't even know how I figured that out... I couldn't google anything about this [feature|bug] but I figured it out within 20 guesses. Got lucky, I guess.
So...
1) Is this a feature or a bug?
2) Did I use the right workaround?
I'm experiencing a failure to understand how Ruby module based namespacing is supposed to work. I've always understood it to be that if you have two classes with the same name you can put one in a module to clarify namespace.
At the moment this doesn't work and I can't see why. In app/models/something/baz.rb I have:
class Baz < ActiveRecord::Base
self.table_name = 'baz'
end
I'm not sure why the table_name assignment is in there. Perhaps because of the fact that this file is nested under something?
In lib/foo/bar/baz.rb I have:
module Foo
module Bar
class Baz
end
end
end
I've included, in order, the paths lib/foo, lib/bar in my autoload_paths config value in config/application.rb:
config.autoload_paths += %W(
#{config.root}/lib
#{config.root}/lib/import
#{config.root}/lib/foo
#{config.root}/lib/foo/bar
#{config.root}/app/models/concerns
#{config.root}/app/services
#{config.root}/app/controllers/concerns
#{config.root}/app
)
at the console I can successfully reference the Foo, Foo::Bar modules but when I try to do Foo::Bar::Baz I get the usual error
expected Baz to be defined in lib/foo/bar/baz.rb
now I change that class to be in lib/foo/bar/baz_thing.rb and refer to it in console as Foo::Bar::BazThing everything works. I've come to the possibly erroneous conclusion that the autoload implementation is mightly confused because there are two classes with the same name.
I've double, triple and quadruple checked names and paths on all of this.
I also read through all the suggested possible alternative/duplicate questions shown when creating this ticket and could find none that addressed my situation closely enough to answer my question.
Can anyone identify where I've gone wrong and why my expectations on module namespacing behavior are incorrect?
This is on ruby 2.2.2 and Rails 4.2.6
I have the following model in a Rails app set up:
# app/models/event_list/peak.rb
class EventList::Peak < AR
# ...
end
in a gem I use for import (activerecord-import) the following line triggers a NameError exception:
Module.const_get(class_name)
# class_name evals to :'EventList::Peak'
So, I fire up the Rails console and try to manually get the right symbol I need, by doing:
Module.const_get(EventList::Peak.to_s.to_sym)
but I get the same error. When I simply type EventList::Peak in the console I get the correct class object.
Am I missing something here?
Module::const_get does not support arbitrary depth; it only gets children of the calling module. So in your case EventList.const_get(Peak.to_s.to_sym) should work.
You can use ActiveSupport's qualified_const_get to do what you are asking, e.g.:
require 'active_support/core_ext'
Module.qualified_const_get(EventList::Peak.to_s.to_sym)
If you don't want to use ActiveSupport, you could also do:
Module.const_get(EventList.to_s.to_sym).const_get(Peak.to_s.to_sym).
It would also be easy to create a wrapper around this using inject to support arbitrary depth and duplicate the qualified_const_get functionality.
What is the different between the following statements?
#(not working)
File.exists?("path to file")
#(working)
::File.exists?("path to file")
I used above statements in Chef framework of Ruby.
There is another constant named File in the scope where you are using File.exists?("path to file"). But when you use the :: operator, you are telling ruby to find the File constant in Object (Object::File)
Here is possible try to replicate your issue :
Not working :
class Foo< BasicObject
def self.file_size
File.size(__FILE__)
end
end
p Foo.file_size # uninitialized constant Foo::File (NameError)
The reason is File class is available to the top level ( i.e. in the scope of the class Object) and inside any class which is a direct/ indirect subclass of Object. But Foo has no relation with Object, you wouldn't be able to access it inside Foo, if you don't tell it, from where File class ( or constant ) actually accessible.
Working :
class Foo< BasicObject
def self.file_size
::File.size(__FILE__)
end
end
p Foo.file_size # => 132
Although here also, Foo has no relation with Object, but we are explicitly ( by using :: constant scope resolution operator ) telling Ruby that from where we are trying to access the File class ( Remember class(s) are also constant in Ruby) inside Foo class. Thus here is no objection from Ruby.
Check out if such situation is present in your code too.
On a side-note, File.exists? is deprecated - use File.exist?
According to maty, the question should be asked in this way:
"object, do you exist?"
"object.exist?"
Keep this in mind - yes, "if file exist" is not proper english,
but asking it that way would be wrong from the ruby object
point of view.
As for the leading :: - this refers to toplevel scope.
It is not often required, usually only when you have the same name
of a class or a module.