The Kernel#load method loads and executes a program from the specified file. This method has the second parameter which can be set as a module. In this case “the loaded script will be executed under the given module”.
Could you provide any examples of this usage and explain when it can be useful and why?
An excerpt from the documentation
If the optional wrap parameter is true, the loaded script will be executed under an anonymous module, protecting the calling program's global namespace.
Meaning if the script defines any methods, or constants (also modules/classes), then they won't affect your environment where you call it from.
loaded.rb
def foo
puts 'in loaded'
end
foo
main program
def foo
puts 'in main program'
end
foo # 'in main program'
load 'loaded.rb' # will output 'in loaded'
foo # 'in loaded
The foo method was overridden by the load. If you want to avoid that, set the 2nd parameter to true.
def foo
puts 'in main program'
end
foo # 'in main program'
load 'loaded.rb', true # will output 'in loaded'
foo # 'in main program'
The other option for the 2nd argument introduced recently in ruby 3.1 is to give it a module. The loaded code will then run in the context of that module.
It can be used for example to give a set of methods for the loaded script that it expects without defining them on the global scope.
loaded.rb
puts foo
main program
load 'loaded.rb' # NameError
You can define the method it needs globally
def foo = 'baz'
load 'loaded.rb' # 'baz'
But then the method is defined globally which you might not want.
Alternatively you can define it in a module.
module Bar
def foo = 'baz'
end
load 'loaded.rb' # NameError
load 'loaded.rb', true # NameError
load 'loaded.rb', Bar, # 'baz'
Related
I am in a task in a rake file.
# gottaRunThis.rake
task [:var] => :environment do |t, args|
Foo::Bar::Bell::this_function(args.var)
#runs Bell::this_function(var) but fails to complete.
# errors out with the words:
# NoMethodError: undefined method `put' for nil:NilClass
# Because Bell needs a #connection variable that doesn't exist in
# it's context when called directly.
end
The thing is, the target module and def are meant to be included in another class.
# mainclass.rb
module Foo
module Bar
class TheMainClass
include Foo::Bar::Bell
def initialize
#site = A_STATIC_SITE_VARIABLE
#connection = self.connection
end
def connection
# Connection info here, too verbose to type up
end
end
end
end
And Bell looks like.
# bell.rb
module Foo
module Bar
module Bell
def do_the_thing(var)
#things happen here
#var converted to something here
response = #connection.put "/some/restful/interface, var_converted
end
end
end
Should I modify Bell so it somehow is including TheMainClass? (And if so, I don't know how?) Or is there some syntax I should use in my rake file like
Foo::Bar::TheMainClass::Bell::do_the_thing(args.var)
#I don't think this works... should it? What would the equivilent but working version look like?
Included methods are available as instance methods, so you can call do_the_thing like this:
main_class = Foo::Bar::TheMainClass.new
main_class.do_the_thing(args.var)
When I want to include a module into a Minitest/spec test, I can access the functions from the module, but not the classes defined in it. Example:
module Foo
def do_stuff
end
class Bar
end
end
x=describe Foo do
include Foo
end
p x.constants # shows :Bar
describe Foo do
include Foo
it "foos" do
do_stuff # works
Bar.new # raises a NameError
end
end
Running this snippet gives me a "NameError: uninitialized constant Bar", however, the p x.constantsshows that Bar is defined. I looked into the Minitest source code for describe and it uses class_eval on the block in the context of some anonymous class. When I do that in the context of a normal class it works fine and I can access Bar. Why doesn't it work with describe/it or what do I have to do in order to access the classes directly?
EDIT:
Interestingly, if you call class_eval directly on some class the included class Bar can be found, e.g.
class Quux
def it_foos
do_stuff # works
Bar.new # does NOT raise a NameError
end
end
Quux.class_eval do
include Foo
end
Quux.new.it_foos
won't throw a NameError...
If you check the documentation for #class_eval (for example, https://ruby-doc.org/core-2.5.0/Module.html#method-i-class_eval) you will see the answer there: "Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected".
So, include within class_eval simply doesn't affect constants resolution.
As far as I understand from the short look at minitest's source code, describe internally creates a new anonymous class (let's name it C) and casts class_eval on it with the block you provide. During this call its create the respective test instance methods that are executed later. But include doesn't affect constants resolution for C, so Bar stays unknown.
There is an obvious (and quite ugly) solution - the following should work because you include Foo into outer context, so Bar goes into lexical scope accessible for describe:
include Foo
describe Foo do
it "foos" do
do_stuff
Bar.new
end
end
But tbh I'd avoid such code. Probably it's better to set up the class mock explicitly, smth like
module Foo
def do_stuff
"foo"
end
class Bar
def do_stuff
"bar"
end
end
end
...
describe Foo do
let(:cls) { Class.new }
before { cls.include(Foo) }
it "foos" do
assert cls.new.do_stuff == "foo"
end
it "bars" do
assert cls::Bar.new.do_stuff == "bar"
end
end
(but take pls the latter with a grain of salt - I almost never use Minitest so have no idea of its "common idioms")
I noticed something odd while I was adding methods to Kernel to make them available globally. It's interesting, and I'm looking for some documentation or good explanation.
Let's look at the code:
file: ./demo.rb
# example 1
module Kernel
def foo
puts "I'm defined inside the module!"
end
end
# example 2
module Bar
def bar
puts "I'm included! (bar)"
end
end
Kernel.send :include, Bar
# example 3
module Baz
def baz
puts "I'm included! (baz)"
end
end
module Kernel
include Baz
end
Then, in bash and IRB
$ irb -r ./demo.rb
> foo
# I'm defined inside the module!
> bar
# NameError: undefined local variable or method `bar' for main:Object
> baz
# NameError: undefined local variable or method `baz' for main:Object
>
> self.class.ancestors
# => [Object, Kernel, BasicObject]
>
> include Kernel
>
> self.class.ancestors
# => [Object, Kernel, Baz, Bar, BasicObject]
>
> foo
# I'm defined inside the module!
> bar
# I'm included! (bar)
> baz
# I'm included! (baz)
foo works as expected, and is available for all objects that include Kernel. bar and baz, on the other hand, are not immediately available.
I imagine it's because the evaluation context of IRB (an Object) already includes Kernel, and including a module A inside a module B will not "reload" all previous inclusions of B.
Ok, it makes perfect sense, and in fact re-including Kernel will add the other two methods.
Then, my questions are:
Why does opening Kernel work? (example 1)
if opening the module is treated differently, why isn't the 3rd example working as well?
What happens you call foo.bar in Ruby? Something like this:
foo.class.ancestors.each do |klass|
if klass.public_instance_methods.include? :bar
return klass.instance_method(:bar).bind(foo).call
end
end
raise NameError
i.e. Ruby searches through the ancestors to find a matching instance method.
And what happens when you call A.include B in Ruby? Something like this:
B.ancestors.each do |mod|
A.ancestors << mod unless A.ancestors.include? mod
end
B and all of its ancestors become ancestors of A. These two behaviors explain everything:
Opening Kernel works because it's included in Object and is thus an ancestor of every object, meaning its methods (including new ones) can be searched whenever you call a method on any object.
Opening a module isn't treated differently. Your second and third examples are effectively the same. They both don't work because Kernel's ancestors are only searched when it is included, which was before you added new ancestors to it.
In the global context (main), when you include a module you can use its methods directly from the global context. In a class, including the module defines instance methods which can't be called from the same context that include was called from. Why is this?
Example:
module Foo
def hello
puts "Hello, world!"
end
end
# In a class:
class Bar
include Foo
hello # Hello is an instance method so it won't work here.
end
# In main
include Foo
hello # Works fine. Why?
In main
include Foo
hello # Works fine. Why?
What is the Ruby Top-Level? a good blog on this concept you must go thorugh first.
Because in top level you are calling the instance method #hello on the top level object main,which is an instance of Object class.On top level you are doing include Foo means you are including the module in the Object class.which is why instance method #hello of the module Foo becomes the instance method of the class object.
module Foo
def hello
puts "Hello, world!"
end
end
Object.include?(Foo) # => false
include Foo
Object.include?(Foo) # => true
self # => main
self.class # => Object
self.instance_of? Object # => true
hello # => Hello, world!
On top level if you are calling the method as hello,but ruby is doing internally as self.hello. And self is there main,which I explained before.
In my lib folder I have billede.rb:
class Billede
require 'RMagick'
#some code that creates a watermark for a image
image.write(out)
end
How do I call/activate the class? Is the only way to change it to a Rake task?
You can't call a class directly. You have to call a method on that class. For example:
class Billede
def self.foobar
# some kind of code here...
end
end
Then you can call it via Billede.foobar
Perhaps you should read some documentation on basic ruby syntax before trying to do more complex things (such as manipulating images w/ Rmagick).
Code 'inside a class' is run just like any other code. If you have a Ruby file like this:
puts "Hello from #{self}"
class Foo
puts "Hello from #{self}"
end
and you run the file (either via ruby foo.rb on the command line or require "./foo" or load "foo.rb" in a script) it then you will see the output:
Hello from main
Hello from Foo
If you want to load a utility that 'does something' that you can then invoke from a REPL like IRB or the Rails console, then do this:
module MyStuff
def self.do_it
# your code here
end
end
You can require "./mystuff" to load the code, and when you're ready to run it type MyStuff.do_it
And, as you may guess, you can also create methods that accept arguments.
If you want to define a file that can be included in others (with no immediate side effects) but which also "does its thing" whenever the file is run by itself, you can do this:
module MyStuff
def self.run!
# Go
end
end
MyStuff.run! if __FILE__==$0
Now if you require or load this file the run! method won't be invoked, but if you type ruby mystuff.rb from the command line it will.
# in /lib/billede.rb
class Billede
def self.do_something(arg)
# ...
end
def do_anotherthing(arg)
# ...
end
end
# inside a model or controller
require 'billede'
Billede::do_something("arg")
# or
billede_instance = Billede.new
billede_instance.do_anotherthing("arg")