`const_missing': uninitialized constant (NameError) - Require - ruby-on-rails

I am getting `const_missing': uninitialized constant Disc::Foo1 (NameError) while executing the code
disc.rb
module Disc
MAPPING = {
:new => {
:first => Foo1,
:second => Foo2
},
:old => {
:third => Foo3,
:fourth => Foo4
}
def run
# :new and :first is example but will be any value decided based on the logic dynamically.
cls = MAPPING[:new][:first]
cls.new.execute
end
}
end
foo1.rb
class Foo1
def initialize
end
def execute
puts "f1"
end
end
foo2.rb
class Foo2
def initialize
end
def execute
puts "f2"
end
end
foo3.rb
class Foo3
def initialize
end
def execute
puts "f3"
end
end
foo4.rb
class Foo4
def initialize
end
def execute
puts "f4"
end
end
I am new to ruby. I tried to find the solution and found that I have to add require clause and tried many things but didn't work.
Can someone please help me out here?

Ruby can't autoload files — you need to explicitly tell it to load them. Just by sitting in the same directory they will not do anything. This is because, unlike some other languages, there is no predictable set mapping between file names and their contents, so Ruby can't know where the class you wanted is. You have to
require './foo1'
require './foo2'
require './foo3'
require './foo4'
Alternately, you could do it a bit smarter:
%w(./foo1 ./foo2 ./foo3 ./foo4).each do |file|
require file
end
Now that you know about require... Saying that Ruby can't autoload files was a lie to tell people new to Ruby. Ruby can totally autoload files. But you still have to tell it what they are and where to find them. Just by sitting in the same directory they will not do anything. You have to
autoload(:Foo1, './foo1.rb')
autoload(:Foo2, './foo2.rb')
autoload(:Foo3, './foo3.rb')
autoload(:Foo4, './foo4.rb')
The files will not be loaded, unless you try to access the named classes; when you do so, Ruby will remember you told it where to find those classes, and load the corresponding file. This saves the program start time and memory, as only the actually needed files will be loaded.
You can also do this instead:
# for each `./foo*.rb` file
Dir.glob('./foo*.rb').each do |file|
# turn it into the class name; e.g. `./foo1.rb' -> :Foo1
classname = file[%r{\./(.*)\.rb$}, 1].capitalize.to_sym
# then tell Ruby that that classname can be found in that file
autoload(classname, file)
end
However, since you explicitly have the classes in your MAPPING, autoloading will not help you — all of the foo files will be autoloaded at the moment MAPPING is defined, so there is no benefit to using autoload here. If you stored names instead of class objects themselves, and used Object.const_get to look up the class object constant, then it would work:
autoload(:Foo1, './foo1.rb')
# `./foo1.rb` is not yet loaded
MAPPING = {
new: {
first: :Foo1,
# ...
},
# ...
}
# `./foo1.rb` is still not loaded:
# we haven't used the `Foo1` constant yet
# (we did use `:Foo1` symbol, but that is a completely different thing)
cls = Object.const_get(MAPPING[:new][:first])
# `./foo1.rb` is now loaded, and `cls` is `Foo1`

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.

Reloading constant generated with inherited with rails

I have a concern with realoding of constant in rails. More specifically, with a constant that is filled by an inherited method :
EDIT : This concerns is relative to development mode, not production.
Here is the code :
# helpers/my_active_code/formatter.rb
module MyActiveCode
class Formatter
FORMATTER_MAPPING = ActiveSupport::HashWithIndifferentAccess.new # just a constant
# Will store something like :
# {
# "String" => ::MyActiveCode::Transforms::String,
# "Boolean" => ::MyActiveCode::Transforms::Boolean,
# }
end
end
# helpers/my_active_code/transforms/object.rb
module MyActiveCode::Transforms
class Object
# A superclass that implement an inherited method which store into the constant
private
def self.inherited(subclass)
ActiveRecordViewRewrite::Formatter::FORMATTER_MAPPING.store(identifier, subclass)
end
end
end
# helpers/my_active_code/transforms/string.rb
module MyActiveCode::Transforms
class String
end
end
I also got an initializer that require all files into the folder helpers/my_active_code/transforms.
When i start the rails console, all work fine, the FORMATTER_MAPPING constant is well filled.
However, after a reload (reload!) the FORMATTER_MAPPING constant is empty ({}).
This is perfectly normal due to the constant-reloading describe here : http://guides.rubyonrails.org/autoloading_and_reloading_constants.html#constant-reloading
So i try to reload all my transforms file into (helpers/my_active_code/transforms) at the end of the file helpers/my_active_code/formatter.rb but it does not work.
How can I solve this and always get this constant filled?

Why does Ruby pollute the global namespace with Classes and Functions but not variable?

Having this piece of code in test1.rb
my_var = 42
def my_func()
42
end
class MyCLS
attr_accessor :prop
def initialize()
#prop = 42
end
end
Then in the interpreter I require it in irb
> require './test1.rb'
> MyCLS.new().prop
=> 42
> my_func()
=> 42
> my_var
NameError: undefined local variable or method `my_var' for main:Object
I am confused, ruby seems quite happy to pollute the global namespace with classes and functions, but refuse to do the same with my_var? I imagine this is to avoid name collisions and bugs. But the problem is only partially solved as it is still present with Class and Function. Maybe just a little less prone to happen?
So now imagine this second file test2.rb
def my_func()
43
end
class MyCLS
attr_accessor :prop
def initialize()
#prop = 43
end
end
And then execute it
> require './test1.rb'
> require './test2.rb'
> MyCLS.new().prop
=> 43
> my_func()
=> 43
Is that normal that the previous globals MyCLS and my_func get silently overwritten? Isn't this highly likely to break a software because a gem decided to add/rename a Class or a function somewhere? All of this seems very brittle and dangerous.
I am aware of modules and I tried them with little success (awkward, and once again they are globals)
Is there ways to prevent this or mitigate what seems like a language design flaw?
Edit: Another example
# test1.rb
def my_func()
42
end
# test2.rb
puts my_func()
# test3.rb
class Example
require './test1.rb'
end
class AnotherExample
require './test2.rb'
end
# command line
$ ruby test3.rb
42
Constants in Ruby (things which start with an uppercase letter) are always created as constants on Object, unless they are explicitly a member of another constant (ie, module Foo::Bar creates a module under the Foo constant, which is itself under the Object constant).
Additionally, there is a special top-level Object instance named "main". Any methods defined at the top level are defined on Object as private methods, and thus accessible from main.
When you require a file, the mechanism is:
Create a new anonymous Module
Load the requested file into that module
Extend main with that module.
These rules are always obeyed; you can't define a top-level method in a file, then include that file into a namespace by cleverly placing the require statement. The file is parsed, Ruby finds a top-level method and extends main with it, without any consideration for where require is called from.
If you want a method that gets mixed into another class, then you typically would put it into a module, and then mix that module into your class.
# test1.rb
module Bar
def my_func
42
end
end
# test2.rb
require 'test1'
class Foo
include Bar
end
my_func => # NameError: undefined local variable or method `my_func' for main:Object
Foo.new.my_func # => 42
In Ruby, it is expected that each file will fully namespace the constants and methods that it intends to expose. You will practically never write top-level methods in most real Ruby projects; there's little fear of things being unintentionally overwritten, because someone would need to explicitly step into your namespace to overwrite things.
If you want to execute a file without extending the main object, then you can use Kernel#load with the wrap parameter, which wraps the load in an anonymous module (but makes its internals inaccessible, unless you were do do something in that file to expose the methods and constants in that file):
load "test1", true
MyCLS # => NameError: uninitialized constant MyCLS
You could get this scoped kind of loading via a custom loader:
# test1.rb
def foo
42
end
# test2.rb
def relative_load(file)
Module.new.tap {|m| m.module_eval open(file).read }
end
class Foo
include relative_load("test1.rb")
end
Foo.new.foo # => 42
foo # => NameError: undefined local variable or method `foo' for main:Object
As an aside, in your first example, the MyCLS class isn't overwritten; it's merged with the existing MyCLS class. Because both declare initialize, the latter declaration takes precedence. For example:
# test1.rb
class MyCLS
attr_accessor :prop
# This definition will get thrown away when we overwrite it from test2.
# It is test2's responsibility to make sure that behavior is preserved;
# this can be done with reimplementation, or by saving a copy of this
# method with `alias` or similar and invoking it.
def initialize(prop)
#prop = prop
end
end
# test2.rb
class MyCLS
attr_accessor :another_prop
def initialize(prop, another_prop)
#prop = prop
#another_prop = prop
end
end
# test3.rb
require 'test1'
c = MyCLS.new(1, 2) # => ArgumentError: wrong number of arguments (2 for 1)
c = MyCLS.new(1)
c.prop => 1
c.another_prop => # => NoMethodError: undefined method `another_prop'
require 'test2'
c = MyCLS.new(1) # => ArgumentError: wrong number of arguments (1 for 2)
c = MyCLS.new(1, 2)
c.prop => 1
c.another_prop => 2
Since you are using the global namespace, I am going to venture that you are diving into Ruby with a background in JavaScript, correct me if I am wrong.
Ruby is not polluting the global namespace because when you require a file such as test_1.rb or test_2.rb, their scope is then limited to the place where you required them. For instance, if you require 'test_1' in a Class called Example, that has no reach if you were to require 'test_2' in a Class called AnotherExample:
Class Example
require 'test_1'
end
Class AnotherExample
require 'test_2'
end
Your methods get overwritten because you required both files within the same scope, which you would not do within the context of a larger application. Namespaces prevent you from overriding similarly named variables, methods, etc.
my_var is a local variable whose context is bound to test_1.rb. As such, its scope is limited to within test_1.rb.

getting in-out from ruby methods

I am using jruby to run bunch of ruby scripts, though I am using pure ruby part of it.
It sometimes gets difficult to follow from output what exactly is happening or where exactly something went wrong.
I wanted to get something like this in my std out for every method:
entered in method A
out of method A
Now I can surely go and put those comments in every method ..which feels very wrong. Is there a way to run ruby in a little verbose more to get this information in my log. Hopefully I would avoid using a lot of gems etc .. since these are on some managed servers and I will have to spend some time to just get more s/f on it. Hoping something would be avail as part of jruby itself
Thanks!
You could use this code:
module InOutHook
module ClassMethods
def setup_hooks(*syms)
syms.each do |sym| # For each symbol
str_id = "__#{sym}__hooked__"
unless private_instance_methods.include?(str_id)
alias_method str_id, sym # Backup original method
private str_id # Make backup private
define_method sym do |*args| # Replace method
puts ">>> #{self.class}\##{sym} >>>"
ret = __send__ str_id, *args # Invoke backup
puts "<<< #{self.class}\##{sym} >>>"
ret
end
end
end
end
end
def InOutHook.included(base)
base.extend(ClassMethods)
end
end
class TestClass
def test1
puts "test!"
end
def test2(v)
puts "Value is #{v}"
end
include InOutHook
setup_hooks(:test1, :test2)
end
# works on existing classes too:
class Array
include InOutHook
setup_hooks(:[])
end
tc = TestClass.new
tc.test1
tc.test2(10)
ary = [1,2,3]
puts ary[1..2]
In case you want to add a hoot to every method, just add a splat asterisk:
setup_hooks(*[].methods)

Using classes in a module from the lib directory - Rails 3

I have a setup in the lib directory like so:
lib/
copy_process.rb
copy_process/
processor.rb
The copy_process.rb and processor.rb contain the module definition CopyProcess. The copy_process.rb defines the CopyFile class as well:
module CopyProcess
class CopyFile
end
end
The processor.rb is structured like so:
module CopyProcess
class Processer
end
end
In one of its methods, it creates a new copy file object:
def append_file_if_valid(file_contents, headers, files, file_name)
unless headers
raise "Headers not found"
else
files << CopyProcess::CopyFile.new()
end
end
When I used these files as part of a command line ruby program, it worked fine. However, i started putting it into a rails app, and I have written cucumber/capybara tests to hit the buttons and so forth where this is used. I initialize a Processor object from one of my AR models, and call the above method a few times. It cannot seem to find the CopyFile class, even though I have the following code in my application.rb
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Any ideas?
===============================================================
Edit Above was solved by extracting the copy file class into it's own file under lib.
I now have another issue:
The CopyFile class refers to module-level helper methods that sit in lib/copy_process.rb, like so:
module CopyProcess
# Gets the index of a value inside an array of the given array
def get_inner_index(value, arr)
idx = nil
arr.each_with_index do |e, i|
if e[0] == value
idx = i
end
end
return idx
end
def includes_inner?(value, arr)
bool = false
arr.each { |e| bool = true if e[0] == value }
return bool
end
# Encloses the string in double quotes, in case it contains a comma
# #param [String] - the string to enclose
# #return [String]
def enclose(string)
string = string.gsub(/\u2019/, '’')
if string.index(',')
return "\"#{string}\""
else
return string
end
end
end
When I run my cucumber tests, i get the following error:
undefined method `includes_inner?' for CopyProcess:Module (NoMethodError)
./lib/copy_process/copy_file.rb:64:in `set_element_name_and_counter'
Which refers to this method here:
def set_element_name_and_counter(element_names, name)
if !CopyProcess::includes_inner?(name, element_names)
element_names << [name, 1]
else
# if it's in the array already, find it and increment the counter
current_element = element_names[CopyProcess::get_inner_index(name, element_names)]
element_names[CopyProcess::get_inner_index(name, element_names)] = [current_element[0], current_element[1]+1]
end
element_names
end
I also tried moving the copy_file.rb and other files in the lib/copy_process/ directory up a level into the lib directory. I then received the following error:
Expected /Users/aaronmcleod/Documents/work/copy_process/lib/copy_file.rb to define CopyFile (LoadError)
./lib/processor.rb:48:in `append_file_if_valid'
The line that the error states creates an instance of CopyFile. I guess rails doesn't like loading the files in that fashion, and for the former setup, I think the copy_file.rb is having issues loading the rest of the module. I tried requiring it and so forth, but no luck. You can also find my most recent code here: https://github.com/agmcleod/Copy-Process/tree/rails
First config.autoload_paths += %W(#{config.root}/lib) should be sufficient. This tells rails to start looking for properly structured files at /lib.
Second, I think that you're running into issues because CopyFile isn't where rails expects it to be. As far as I know your setup 'should' work but have you tried seperating CopyFile out into its own file under the copy_process folder? My guess is that since the copy_process folder exists, it is expecting all CopyProcess::* classes to be defined there instead of the copy_process.rb.
EDIT: You may consider opening another question, but the second half of your question is a different problem entirely.
You define methods in your module like so,
module X
def method_one
puts "hi"
end
end
Methods of this form are instance methods on the module, and they have very special restrictions. For instance, you can not access them from outside the module definition (I'm skeptical how these worked previously). Executing the above gives
> X::method_one
NoMethodError: undefined method `method_one' for X:Module
If you want to access these methods from other scopes you have a few options.
Use Class Methods
module X
def self.method_one
puts "hi"
end
end
X::hi #=> "hi"
Use Mixins
module X
module Helpers
def method_one
puts "hi"
end
end
end
class CopyFile
include X::Helpers
def some_method
method_one #=> "hi"
self.method_one #=> "hi"
end
end

Resources